5 Commits

7 changed files with 68 additions and 36 deletions

View File

@@ -235,11 +235,11 @@ class W3DHub
end end
end end
def join_server(app_id, channel, server, password = nil) def join_server(app_id, channel, server, username = Store.settings[:server_list_username], password = nil, multi = false)
if installed?(app_id, channel) && Store.settings[:server_list_username].to_s.length.positive? if installed?(app_id, channel) && username.to_s.length.positive?
run( run(
app_id, channel, app_id, channel,
"+connect #{server.address}:#{server.port} +netplayername #{Store.settings[:server_list_username]}#{password ? " +password \"#{password}\"" : ""}" "+connect #{server.address}:#{server.port} +netplayername #{username}#{password ? " +password \"#{password}\"" : ""}#{multi ? " +multi" : ""}"
) )
end end
end end

View File

@@ -112,9 +112,29 @@ class W3DHub
@task_state == :failed @task_state == :failed
end end
# Helper method to normalize file paths for case-insensitive comparison def normalize_path(path, base_path)
def normalize_path(path) path = path.to_s.gsub("\\", "/")
path.to_s.gsub("\\", "/").downcase return path if W3DHub.windows? # Windows is easy, or annoying, depending how you look at it...
constructed_path = base_path
split_path = path.split("/")
split_path.each do |segment|
Dir.glob("#{constructed_path}/*").each do |part|
next unless "#{constructed_path}/#{segment}".downcase == part.downcase
constructed_path = part
break if File.file?(constructed_path)
end
end
# Find file if it exists, otherwise downcase the `path` sans `base_path`
if "#{base_path}/#{path}".length == constructed_path.length
constructed_path
else
"#{base_path}/#{path.downcase}"
end
end end
def failure_reason def failure_reason
@@ -300,15 +320,13 @@ class W3DHub
@files.reverse.each do |file| @files.reverse.each do |file|
break unless folder_exists break unless folder_exists
# Normalize file paths to handle case-insensitive comparisons file_path = normalize_path(file.name, path)
safe_file_name = normalize_path(file.name)
file_path = "#{path}/#{safe_file_name}"
processed_files += 1 processed_files += 1
@status.progress = processed_files.to_f / file_count @status.progress = processed_files.to_f / file_count
next if file.removed_since next if file.removed_since
next if accepted_files.key?(safe_file_name) next if accepted_files.key?(file_path)
unless File.exist?(file_path) unless File.exist?(file_path)
rejected_files << { file: file, manifest_version: file.version } rejected_files << { file: file, manifest_version: file.version }
@@ -328,7 +346,7 @@ class W3DHub
logger.info(LOG_TAG) { file.inspect } if file.checksum.nil? logger.info(LOG_TAG) { file.inspect } if file.checksum.nil?
if digest.hexdigest.upcase == file.checksum.upcase if digest.hexdigest.upcase == file.checksum.upcase
accepted_files[safe_file_name] = file.version accepted_files[file_path] = file.version
logger.info(LOG_TAG) { "[#{file.version}] Verified file: #{file_path}" } logger.info(LOG_TAG) { "[#{file.version}] Verified file: #{file_path}" }
else else
rejected_files << { file: file, manifest_version: file.version } rejected_files << { file: file, manifest_version: file.version }
@@ -532,7 +550,7 @@ class W3DHub
logger.info(LOG_TAG) { " #{file.name}" } logger.info(LOG_TAG) { " #{file.name}" }
path = Cache.install_path(@application, @channel) path = Cache.install_path(@application, @channel)
file_path = "#{path}/#{file.name}".sub('Data/', 'data/') file_path = normalize_path(file.name, path)
File.delete(file_path) if File.exist?(file_path) File.delete(file_path) if File.exist?(file_path)
@@ -565,7 +583,7 @@ class W3DHub
def write_paths_ini def write_paths_ini
path = Cache.install_path(@application, @channel) path = Cache.install_path(@application, @channel)
File.open("#{path}/data/paths.ini", "w") do |file| File.open(normalize_path("data/paths.ini", path), "w") do |file|
file.puts("[paths]") file.puts("[paths]")
file.puts("RegBase=W3D Hub") file.puts("RegBase=W3D Hub")
file.puts("RegClient=#{@application.category}\\#{@application.id}-#{@channel.id}") file.puts("RegClient=#{@application.category}\\#{@application.id}-#{@channel.id}")
@@ -710,15 +728,15 @@ class W3DHub
logger.info(LOG_TAG) { " Unpacking patch \"#{package_path}\" in \"#{temp_path}\"" } logger.info(LOG_TAG) { " Unpacking patch \"#{package_path}\" in \"#{temp_path}\"" }
unzip(package_path, temp_path) unzip(package_path, temp_path)
# Normalize the path to handle case-insensitivity consistently file_path = normalize_path(manifest_file.name, path)
safe_file_name = normalize_path(manifest_file.name) temp_file_path = normalize_path(manifest_file.name, temp_path)
logger.info(LOG_TAG) { " Loading #{temp_path}/#{safe_file_name}.patch..." } logger.info(LOG_TAG) { " Loading #{temp_file_path}.patch..." }
patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_path}/#{safe_file_name}.patch", ignore_crc_mismatches: false) patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_file_path}.patch", ignore_crc_mismatches: false)
patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name.casecmp?(".w3dhub.patch") || f.name.casecmp?(".bhppatch") }.data, symbolize_names: true) patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name.casecmp?(".w3dhub.patch") || f.name.casecmp?(".bhppatch") }.data, symbolize_names: true)
logger.info(LOG_TAG) { " Loading #{path}/#{safe_file_name}..." } logger.info(LOG_TAG) { " Loading #{file_path}..." }
target_mix = W3DHub::Mixer::Reader.new(file_path: "#{path}/#{safe_file_name}", ignore_crc_mismatches: false) target_mix = W3DHub::Mixer::Reader.new(file_path: "#{file_path}", ignore_crc_mismatches: false)
logger.info(LOG_TAG) { " Removing files..." } if patch_info[:removedFiles].size.positive? logger.info(LOG_TAG) { " Removing files..." } if patch_info[:removedFiles].size.positive?
patch_info[:removedFiles].each do |file| patch_info[:removedFiles].each do |file|
@@ -740,8 +758,8 @@ class W3DHub
end end
end end
logger.info(LOG_TAG) { " Writing updated #{path}/#{safe_file_name}..." } if patch_info[:updatedFiles].size.positive? logger.info(LOG_TAG) { " Writing updated #{file_path}..." } if patch_info[:updatedFiles].size.positive?
W3DHub::Mixer::Writer.new(file_path: "#{path}/#{safe_file_name}", package: target_mix.package, memory_buffer: true, encrypted: target_mix.encrypted?) W3DHub::Mixer::Writer.new(file_path: "#{file_path}", package: target_mix.package, memory_buffer: true, encrypted: target_mix.encrypted?)
FileUtils.remove_dir(temp_path) FileUtils.remove_dir(temp_path)
@@ -753,14 +771,14 @@ class W3DHub
while (entry = stream.get_next_entry) while (entry = stream.get_next_entry)
# Normalize the path to handle case-insensitivity consistently # Normalize the path to handle case-insensitivity consistently
safe_file_name = normalize_path(entry.name) file_path = normalize_path(entry.name, path)
dir_path = "#{path}/#{File.dirname(safe_file_name)}" dir_path = File.dirname(file_path)
unless dir_path.end_with?("/.") || Dir.exist?(dir_path) unless dir_path.end_with?("/.") || Dir.exist?(dir_path)
FileUtils.mkdir_p(dir_path) FileUtils.mkdir_p(dir_path)
end end
File.open("#{path}/#{safe_file_name}", "wb") do |f| File.open(file_path, "wb") do |f|
i = entry.get_input_stream i = entry.get_input_stream
while (chunk = i.read(32_000_000)) # Read up to ~32 MB per chunk while (chunk = i.read(32_000_000)) # Read up to ~32 MB per chunk

View File

@@ -90,6 +90,10 @@ class W3DHub
# Download a W3D Hub package # Download a W3D Hub package
def self.fetch_package(package, block) def self.fetch_package(package, block)
endpoint_download_url = package.download_url || "#{Api::W3DHUB_API_ENDPOINT}/apis/launcher/1/get-package" endpoint_download_url = package.download_url || "#{Api::W3DHUB_API_ENDPOINT}/apis/launcher/1/get-package"
if package.download_url
uri_path = package.download_url.split("/").last
endpoint_download_url = package.download_url.sub(uri_path, URI.encode_uri_component(uri_path))
end
path = package_path(package.category, package.subcategory, package.name, package.version) path = package_path(package.category, package.subcategory, package.name, package.version)
headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": Api::USER_AGENT } headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": Api::USER_AGENT }
headers["Authorization"] = "Bearer #{Store.account.access_token}" if Store.account && !package.download_url headers["Authorization"] = "Bearer #{Store.account.access_token}" if Store.account && !package.download_url

View File

@@ -81,15 +81,19 @@ class W3DHub
) )
end end
def self.join_server(server, password) def self.join_server(server:, username: Store.settings[:server_list_username], password: nil, multi: false)
if ( if (
(server.status.password && password.length.positive?) || (server.status.password && password.length.positive?) ||
!server.status.password) && !server.status.password) &&
Store.settings[:server_list_username].to_s.length.positive? username.to_s.length.positive?
Store.application_manager.join_server( Store.application_manager.join_server(
server.game, server.game,
server.channel, server, password server.channel,
server,
username,
password,
multi
) )
else else
CyberarmEngine::Window.instance.push_state(W3DHub::States::MessageDialog, type: "?", title: "?", message: "?") CyberarmEngine::Window.instance.push_state(W3DHub::States::MessageDialog, type: "?", title: "?", message: "?")

View File

@@ -375,7 +375,7 @@ class W3DHub
end end
container = stack(fill: true, width: 1.0, padding: 8) do container = stack(fill: true, width: 1.0, padding: 8) do
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{game.id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png" image_path = File.exist?("#{CACHE_PATH}/#{game.id}.png") ? "#{CACHE_PATH}/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
flow(width: 1.0, margin_top: 8) do flow(width: 1.0, margin_top: 8) do
flow(fill: true) flow(fill: true)
image image_path, width: 0.5 image image_path, width: 0.5

View File

@@ -410,11 +410,11 @@ class W3DHub
if server.status.password if server.status.password
W3DHub.prompt_for_password( W3DHub.prompt_for_password(
accept_callback: proc do |password| accept_callback: proc do |password|
W3DHub.join_server(server, password) W3DHub.join_server(server: server, password: password)
end end
) )
else else
W3DHub.join_server(server, nil) W3DHub.join_server(server: server)
end end
end end
) )
@@ -422,18 +422,24 @@ class W3DHub
if server.status.password if server.status.password
W3DHub.prompt_for_password( W3DHub.prompt_for_password(
accept_callback: proc do |password| accept_callback: proc do |password|
W3DHub.join_server(server, password) W3DHub.join_server(server: server, password: password)
end end
) )
else else
W3DHub.join_server(server, nil) W3DHub.join_server(server: server)
end end
end end
end end
if W3DHUB_DEVELOPER if W3DHUB_DEVELOPER
list_box(items: (1..12).to_a.map(&:to_s), margin_left: 16, width: 72, tip: "Number of game clients", enabled: (game_installed && !game_updatable), **TESTING_BUTTON) client_instances = list_box(items: (1..12).to_a.map(&:to_s), margin_left: 16, width: 72, tip: "Number of game clients", enabled: (game_installed && !game_updatable), **TESTING_BUTTON)
button "Multijoin", tip: "Launch multiple clients with configured username_\#{number}", enabled: (game_installed && !game_updatable), **TESTING_BUTTON button("Multijoin", tip: "Launch multiple clients with configured username_\#{number}", enabled: (game_installed && !game_updatable), **TESTING_BUTTON) do
username = Store.settings[:server_list_username]
client_instances.value.to_i.times do |i|
W3DHub.join_server(server: server, username: format("%s_%d", username, i), multi: true)
end
end
end end
flow(fill: true) flow(fill: true)

View File

@@ -1,4 +1,4 @@
class W3DHub class W3DHub
DIR_NAME = "W3DHubAlt" DIR_NAME = "W3DHubAlt"
VERSION = "0.7.0" VERSION = "0.8.0"
end end