mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2025-12-15 16:52:34 +00:00
Removed dependence on the bsdtar command, fixed Play Now button not doing anything if a server nickname wasn't set, refactor a bit so that the Server List's 'Join Server' button functionality can be reused for the Play Now button, installer now always unpacks into 'data/*' instead of 'Data/*'
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -9,6 +9,7 @@ gem "ffi"
|
|||||||
gem "websocket-client-simple"
|
gem "websocket-client-simple"
|
||||||
gem "thread-local"
|
gem "thread-local"
|
||||||
gem "ircparser"
|
gem "ircparser"
|
||||||
|
gem "rubyzip"
|
||||||
gem "win32-security", platforms: [:x64_mingw, :mingw]
|
gem "win32-security", platforms: [:x64_mingw, :mingw]
|
||||||
gem "win32-process", platforms: [:x64_mingw, :mingw]
|
gem "win32-process", platforms: [:x64_mingw, :mingw]
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ GEM
|
|||||||
digest-crc (0.6.4)
|
digest-crc (0.6.4)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
event_emitter (0.2.6)
|
event_emitter (0.2.6)
|
||||||
excon (0.93.1)
|
excon (0.94.0)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
ffi (1.15.5-x64-mingw-ucrt)
|
ffi (1.15.5-x64-mingw-ucrt)
|
||||||
ffi (1.15.5-x64-mingw32)
|
ffi (1.15.5-x64-mingw32)
|
||||||
@@ -27,6 +27,7 @@ GEM
|
|||||||
public_suffix (5.0.0)
|
public_suffix (5.0.0)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
|
rubyzip (2.3.2)
|
||||||
thread-local (1.1.0)
|
thread-local (1.1.0)
|
||||||
websocket (1.2.9)
|
websocket (1.2.9)
|
||||||
websocket-client-simple (0.6.0)
|
websocket-client-simple (0.6.0)
|
||||||
@@ -51,10 +52,11 @@ DEPENDENCIES
|
|||||||
ircparser
|
ircparser
|
||||||
launchy
|
launchy
|
||||||
rexml
|
rexml
|
||||||
|
rubyzip
|
||||||
thread-local
|
thread-local
|
||||||
websocket-client-simple
|
websocket-client-simple
|
||||||
win32-process
|
win32-process
|
||||||
win32-security
|
win32-security
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.17
|
2.3.26
|
||||||
|
|||||||
15
lib/api.rb
15
lib/api.rb
@@ -45,7 +45,18 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Excon.post(url, headers: headers, body: body, tcp_nodelay: true, write_timeout: API_TIMEOUT, read_timeout: API_TIMEOUT, connection_timeout: API_TIMEOUT)
|
Excon.post(
|
||||||
|
url,
|
||||||
|
headers: headers,
|
||||||
|
body: body,
|
||||||
|
tcp_nodelay: true,
|
||||||
|
write_timeout: API_TIMEOUT,
|
||||||
|
read_timeout: API_TIMEOUT,
|
||||||
|
connection_timeout: API_TIMEOUT,
|
||||||
|
idempotent: true,
|
||||||
|
retry_limit: 6,
|
||||||
|
retry_interval: 5
|
||||||
|
)
|
||||||
rescue Excon::Errors::Timeout
|
rescue Excon::Errors::Timeout
|
||||||
logger.error(LOG_TAG) { "Connection to \"#{url}\" timed out after: #{API_TIMEOUT} seconds" }
|
logger.error(LOG_TAG) { "Connection to \"#{url}\" timed out after: #{API_TIMEOUT} seconds" }
|
||||||
DummyResponse.new
|
DummyResponse.new
|
||||||
@@ -233,7 +244,7 @@ class W3DHub
|
|||||||
|
|
||||||
logger.debug(LOG_TAG) { "Fetching GET \"#{url}\"..." }
|
logger.debug(LOG_TAG) { "Fetching GET \"#{url}\"..." }
|
||||||
|
|
||||||
Excon.get(url, headers: headers, body: body, persistent: true)
|
Excon.get(url, headers: headers, body: body, persistent: true, idempotent: true, retry_limit: 6, retry_interval: 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Method: GET
|
# Method: GET
|
||||||
|
|||||||
@@ -231,7 +231,24 @@ class W3DHub
|
|||||||
|
|
||||||
return false unless server
|
return false unless server
|
||||||
|
|
||||||
join_server(app_id, channel, server)
|
if Store.settings[:server_list_username].to_s.length.zero?
|
||||||
|
W3DHub.prompt_for_nickname(
|
||||||
|
accept_callback: proc do |entry|
|
||||||
|
Store.settings[:server_list_username] = entry
|
||||||
|
Store.settings.save_settings
|
||||||
|
|
||||||
|
if server.status.password
|
||||||
|
W3DHub.prompt_for_password(
|
||||||
|
accept_callback: proc do |password|
|
||||||
|
join_server(app_id, channel, server)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
join_server(app_id, channel, server)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def favorive(app_id, bool)
|
def favorive(app_id, bool)
|
||||||
|
|||||||
@@ -146,10 +146,7 @@ class W3DHub
|
|||||||
|
|
||||||
# FIXME: Check that there is enough disk space
|
# FIXME: Check that there is enough disk space
|
||||||
|
|
||||||
# tar present?
|
# TODO: Is missing wine/proton really a failure condition?
|
||||||
bsdtar_present = W3DHub.command("#{W3DHub.tar_command} --help")
|
|
||||||
fail!("FAIL FAST: `#{W3DHub.tar_command} --help` command failed, #{W3DHub.tar_command} is not installed. Will be unable to unpack packages.") unless bsdtar_present
|
|
||||||
|
|
||||||
# Wine present?
|
# Wine present?
|
||||||
if W3DHub.unix?
|
if W3DHub.unix?
|
||||||
wine_present = W3DHub.command("which #{Store.settings[:wine_command]}")
|
wine_present = W3DHub.command("which #{Store.settings[:wine_command]}")
|
||||||
@@ -286,8 +283,8 @@ class W3DHub
|
|||||||
|
|
||||||
manifest.files.each do |file|
|
manifest.files.each do |file|
|
||||||
safe_file_name = file.name.gsub("\\", "/")
|
safe_file_name = file.name.gsub("\\", "/")
|
||||||
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
# Fix borked Data -> data 'cause Windows don't care about capitalization
|
||||||
safe_file_name.sub!("data/", "Data/") # unless File.exist?("#{path}/#{safe_file_name}")
|
safe_file_name.sub!("Data/", "data/")
|
||||||
|
|
||||||
file_path = "#{path}/#{safe_file_name}"
|
file_path = "#{path}/#{safe_file_name}"
|
||||||
|
|
||||||
@@ -490,8 +487,6 @@ class W3DHub
|
|||||||
unpack_package(package, path)
|
unpack_package(package, path)
|
||||||
end
|
end
|
||||||
|
|
||||||
repair_windows_case_insensitive(package, path)
|
|
||||||
|
|
||||||
if status
|
if status
|
||||||
@status.operations[:"#{package.checksum}"].value = package.custom_is_patch ? "Patched" : "Unpacked"
|
@status.operations[:"#{package.checksum}"].value = package.custom_is_patch ? "Patched" : "Unpacked"
|
||||||
@status.operations[:"#{package.checksum}"].progress = 1.0
|
@status.operations[:"#{package.checksum}"].progress = 1.0
|
||||||
@@ -635,8 +630,9 @@ class W3DHub
|
|||||||
logger.info(LOG_TAG) { " #{package.name}:#{package.version}" }
|
logger.info(LOG_TAG) { " #{package.name}:#{package.version}" }
|
||||||
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\"" }
|
logger.info(LOG_TAG) { " Unpacking package \"#{package_path}\" in \"#{path}\"" }
|
||||||
return W3DHub.command("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\"")
|
|
||||||
|
return unzip(package_path, path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_patch(package, path)
|
def apply_patch(package, path)
|
||||||
@@ -647,19 +643,18 @@ class W3DHub
|
|||||||
|
|
||||||
Cache.create_directories(temp_path, true)
|
Cache.create_directories(temp_path, true)
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\"" }
|
logger.info(LOG_TAG) { " Unpacking patch \"#{package_path}\" in \"#{temp_path}\"" }
|
||||||
W3DHub.command("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\"")
|
unzip(package_path, temp_path)
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Loading #{temp_path}/#{manifest_file.name}.patch..." }
|
# Fix borked Data -> data 'cause Windows don't care about capitalization
|
||||||
patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_path}/#{manifest_file.name}.patch", ignore_crc_mismatches: false)
|
safe_file_name = "#{manifest_file.name.sub('Data/', 'data/')}"
|
||||||
|
|
||||||
|
logger.info(LOG_TAG) { " Loading #{temp_path}/#{safe_file_name}.patch..." }
|
||||||
|
patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_path}/#{safe_file_name}.patch", ignore_crc_mismatches: false)
|
||||||
patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name == ".w3dhub.patch" || f.name == ".bhppatch" }.data, symbolize_names: true)
|
patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name == ".w3dhub.patch" || f.name == ".bhppatch" }.data, symbolize_names: true)
|
||||||
|
|
||||||
repaired_path = "#{path}/#{manifest_file.name}"
|
logger.info(LOG_TAG) { " Loading #{path}/#{safe_file_name}..." }
|
||||||
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
target_mix = W3DHub::Mixer::Reader.new(file_path: "#{path}/#{safe_file_name}", ignore_crc_mismatches: false)
|
||||||
repaired_path = "#{path}/#{manifest_file.name.sub('data', 'Data')}" unless File.exist?(repaired_path) && path
|
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Loading #{repaired_path}..." }
|
|
||||||
target_mix = W3DHub::Mixer::Reader.new(file_path: repaired_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|
|
||||||
@@ -681,27 +676,38 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Writing updated #{repaired_path}..." } if patch_info[:updatedFiles].size.positive?
|
logger.info(LOG_TAG) { " Writing updated #{path}/#{safe_file_name}..." } if patch_info[:updatedFiles].size.positive?
|
||||||
W3DHub::Mixer::Writer.new(file_path: repaired_path, package: target_mix.package, memory_buffer: true)
|
W3DHub::Mixer::Writer.new(file_path: "#{path}/#{safe_file_name}", package: target_mix.package, memory_buffer: true)
|
||||||
|
|
||||||
FileUtils.remove_dir(temp_path)
|
FileUtils.remove_dir(temp_path)
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def repair_windows_case_insensitive(package, path)
|
def unzip(package_path, path)
|
||||||
# Windows is just confused
|
stream = Zip::InputStream.new(File.open(package_path))
|
||||||
return true if W3DHub.windows?
|
|
||||||
|
|
||||||
# Force data/ to Data/
|
while (entry = stream.get_next_entry)
|
||||||
return true unless File.exist?("#{path}/data") && File.directory?("#{path}/data")
|
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Moving #{path}/data/ to #{path}/Data/" }
|
safe_file_name = entry.name.gsub("\\", "/")
|
||||||
|
# Fix borked Data -> data 'cause Windows don't care about capitalization
|
||||||
|
safe_file_name.sub!("Data/", "data/")
|
||||||
|
|
||||||
FileUtils.mv(Dir.glob("#{path}/data/**"), "#{path}/Data", force: true)
|
dir_path = "#{path}/#{File.dirname(safe_file_name)}"
|
||||||
FileUtils.remove_dir("#{path}/data", force: true)
|
unless dir_path.end_with?("/.") || Dir.exist?(dir_path)
|
||||||
|
FileUtils.mkdir_p(dir_path)
|
||||||
|
end
|
||||||
|
|
||||||
true
|
File.open("#{path}/#{safe_file_name}", "wb") do |f|
|
||||||
|
i = entry.get_input_stream
|
||||||
|
|
||||||
|
while (chunk = i.read(32_000_000)) # Read up to ~32 MB per chunk
|
||||||
|
f.write chunk
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,13 +32,48 @@ class W3DHub
|
|||||||
linux? || mac?
|
linux? || mac?
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.tar_command
|
def self.prompt_for_nickname(accept_callback: nil, cancel_callback: nil)
|
||||||
if windows?
|
CyberarmEngine::Window.instance.push_state(
|
||||||
"tar"
|
W3DHub::States::PromptDialog,
|
||||||
else
|
title: I18n.t(:"server_browser.set_nickname"),
|
||||||
"bsdtar"
|
message: I18n.t(:"server_browser.set_nickname_message"),
|
||||||
end
|
prefill: Store.settings[:server_list_username],
|
||||||
end
|
accept_callback: accept_callback,
|
||||||
|
cancel_callback: cancel_callback,
|
||||||
|
# See: https://gitlab.com/danpaul88/brenbot/-/blob/master/Source/renlog.pm#L136-175
|
||||||
|
valid_callback: proc do |entry|
|
||||||
|
entry.length > 1 && entry.length < 30 && (entry =~ /(:|!|&|%| )/i).nil? &&
|
||||||
|
(entry =~ /[\001\002\037]/).nil? && (entry =~ /\\/).nil?
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prompt_for_password(accept_callback: nil, cancel_callback: nil)
|
||||||
|
CyberarmEngine::Window.instance.push_state(
|
||||||
|
W3DHub::States::PromptDialog,
|
||||||
|
title: I18n.t(:"server_browser.enter_password"),
|
||||||
|
message: I18n.t(:"server_browser.enter_password_message"),
|
||||||
|
input_type: :password,
|
||||||
|
accept_callback: accept_callback,
|
||||||
|
cancel_callback: cancel_callback,
|
||||||
|
valid_callback: proc { |entry| entry.length.positive? }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.join_server(server, password)
|
||||||
|
if (
|
||||||
|
(server.status.password && password.length.positive?) ||
|
||||||
|
!server.status.password) &&
|
||||||
|
Store.settings[:server_list_username].to_s.length.positive?
|
||||||
|
|
||||||
|
Store.application_manager.join_server(
|
||||||
|
server.game,
|
||||||
|
server.channel, server, password
|
||||||
|
)
|
||||||
|
else
|
||||||
|
CyberarmEngine::Window.instance.push_state(W3DHub::States::MessageDialog, type: "?", title: "?", message: "?")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.command(command, &block)
|
def self.command(command, &block)
|
||||||
if windows?
|
if windows?
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class W3DHub
|
|||||||
@nickname_label = inscription "#{Store.settings[:server_list_username]}"
|
@nickname_label = inscription "#{Store.settings[:server_list_username]}"
|
||||||
image "#{GAME_ROOT_PATH}/media/ui_icons/wrench.png", height: 16, hover: { color: 0xaa_ffffff }, tip: I18n.t(:"server_browser.set_nickname") do
|
image "#{GAME_ROOT_PATH}/media/ui_icons/wrench.png", height: 16, hover: { color: 0xaa_ffffff }, tip: I18n.t(:"server_browser.set_nickname") do
|
||||||
# Prompt for player name
|
# Prompt for player name
|
||||||
prompt_for_nickname(
|
W3DHub.prompt_for_nickname(
|
||||||
accept_callback: proc do |entry|
|
accept_callback: proc do |entry|
|
||||||
@nickname_label.value = entry
|
@nickname_label.value = entry
|
||||||
Store.settings[:server_list_username] = entry
|
Store.settings[:server_list_username] = entry
|
||||||
@@ -390,32 +390,32 @@ class W3DHub
|
|||||||
# prompt for password
|
# prompt for password
|
||||||
# Launch game
|
# Launch game
|
||||||
if Store.settings[:server_list_username].to_s.length.zero?
|
if Store.settings[:server_list_username].to_s.length.zero?
|
||||||
prompt_for_nickname(
|
W3DHub.prompt_for_nickname(
|
||||||
accept_callback: proc do |entry|
|
accept_callback: proc do |entry|
|
||||||
@nickname_label.value = entry
|
@nickname_label.value = entry
|
||||||
Store.settings[:server_list_username] = entry
|
Store.settings[:server_list_username] = entry
|
||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
|
|
||||||
if server.status.password
|
if server.status.password
|
||||||
prompt_for_password(
|
W3DHub.prompt_for_password(
|
||||||
accept_callback: proc do |password|
|
accept_callback: proc do |password|
|
||||||
join_server(server, password)
|
W3DHub.join_server(server, password)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
join_server(server, nil)
|
W3DHub.join_server(server, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
if server.status.password
|
if server.status.password
|
||||||
prompt_for_password(
|
W3DHub.prompt_for_password(
|
||||||
accept_callback: proc do |password|
|
accept_callback: proc do |password|
|
||||||
join_server(server, password)
|
W3DHub.join_server(server, password)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
join_server(server, nil)
|
W3DHub.join_server(server, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -599,49 +599,6 @@ class W3DHub
|
|||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
def prompt_for_nickname(accept_callback: nil, cancel_callback: nil)
|
|
||||||
push_state(
|
|
||||||
W3DHub::States::PromptDialog,
|
|
||||||
title: I18n.t(:"server_browser.set_nickname"),
|
|
||||||
message: I18n.t(:"server_browser.set_nickname_message"),
|
|
||||||
prefill: Store.settings[:server_list_username],
|
|
||||||
accept_callback: accept_callback,
|
|
||||||
cancel_callback: cancel_callback,
|
|
||||||
# See: https://gitlab.com/danpaul88/brenbot/-/blob/master/Source/renlog.pm#L136-175
|
|
||||||
valid_callback: proc do |entry|
|
|
||||||
entry.length > 1 && entry.length < 30 && (entry =~ /(:|!|&|%| )/i).nil? &&
|
|
||||||
(entry =~ /[\001\002\037]/).nil? && (entry =~ /\\/).nil?
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def prompt_for_password(accept_callback: nil, cancel_callback: nil)
|
|
||||||
push_state(
|
|
||||||
W3DHub::States::PromptDialog,
|
|
||||||
title: I18n.t(:"server_browser.enter_password"),
|
|
||||||
message: I18n.t(:"server_browser.enter_password_message"),
|
|
||||||
input_type: :password,
|
|
||||||
accept_callback: accept_callback,
|
|
||||||
cancel_callback: cancel_callback,
|
|
||||||
valid_callback: proc { |entry| entry.length.positive? }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def join_server(server, password)
|
|
||||||
if (
|
|
||||||
(server.status.password && password.length.positive?) ||
|
|
||||||
!server.status.password) &&
|
|
||||||
Store.settings[:server_list_username].to_s.length.positive?
|
|
||||||
|
|
||||||
Store.application_manager.join_server(
|
|
||||||
server.game,
|
|
||||||
server.channel, server, password
|
|
||||||
)
|
|
||||||
else
|
|
||||||
window.push_state(W3DHub::States::MessageDialog, type: "?", title: "?", message: "?")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def formatted_score(int)
|
def formatted_score(int)
|
||||||
int.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
int.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require "rexml"
|
|||||||
require "logger"
|
require "logger"
|
||||||
require "time"
|
require "time"
|
||||||
require "base64"
|
require "base64"
|
||||||
|
require "zip"
|
||||||
|
|
||||||
class W3DHub
|
class W3DHub
|
||||||
W3DHUB_DEBUG = ARGV.join.include?("--debug")
|
W3DHUB_DEBUG = ARGV.join.include?("--debug")
|
||||||
@@ -55,8 +56,8 @@ end
|
|||||||
begin
|
begin
|
||||||
require_relative "../cyberarm_engine/lib/cyberarm_engine"
|
require_relative "../cyberarm_engine/lib/cyberarm_engine"
|
||||||
rescue LoadError => e
|
rescue LoadError => e
|
||||||
logger.warn(W3D::LOG_TAG) { "Failed to load local cyberarm_engine:" }
|
logger.warn(W3DHub::LOG_TAG) { "Failed to load local cyberarm_engine:" }
|
||||||
logger.warn(W3D::LOG_TAG) { e }
|
logger.warn(W3DHub::LOG_TAG) { e }
|
||||||
|
|
||||||
require "cyberarm_engine"
|
require "cyberarm_engine"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user