mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-22 04:06:18 +00:00
Compare commits
6 Commits
9e8f4e1c71
...
v0.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
| f024109327 | |||
| 287022f2b8 | |||
| 68df923bea | |||
| ddbec8d72c | |||
| 70d4e0c40f | |||
| f30658ffc2 |
43
lib/api.rb
43
lib/api.rb
@@ -47,7 +47,9 @@ class W3DHub
|
||||
W3DHUB_API_ENDPOINT = "https://secure.w3dhub.com".freeze # "https://example.com" # "http://127.0.0.1:9292".freeze #
|
||||
ALT_W3DHUB_API_ENDPOINT = "https://w3dhub-api.w3d.cyberarm.dev".freeze # "https://secure.w3dhub.com".freeze # "https://example.com" # "http://127.0.0.1:9292".freeze #
|
||||
|
||||
def self.async_http(method, url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
HTTP_CLIENTS = {}
|
||||
|
||||
def self.async_http(method, path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
case backend
|
||||
when :w3dhub
|
||||
endpoint = W3DHUB_API_ENDPOINT
|
||||
@@ -57,7 +59,15 @@ class W3DHub
|
||||
endpoint = SERVER_LIST_ENDPOINT
|
||||
end
|
||||
|
||||
url = "#{endpoint}#{url}" unless url.start_with?("http")
|
||||
# Handle arbitrary urls that may come through
|
||||
url = nil
|
||||
if path.start_with?("http")
|
||||
uri = URI(path)
|
||||
endpoint = uri.origin
|
||||
path = uri.request_uri
|
||||
else
|
||||
url = "#{endpoint}#{path}"
|
||||
end
|
||||
|
||||
logger.debug(LOG_TAG) { "Fetching #{method.to_s.upcase} \"#{url}\"..." }
|
||||
|
||||
@@ -70,7 +80,7 @@ class W3DHub
|
||||
|
||||
Sync do
|
||||
begin
|
||||
response = Async::HTTP::Internet.send(method, url, headers, body)
|
||||
response = provision_http_client(endpoint).send(method, path, headers, body)
|
||||
|
||||
Response.new(status: response.status, body: response.read)
|
||||
rescue Async::TimeoutError => e
|
||||
@@ -88,17 +98,32 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
def self.post(url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
async_http(:post, url, headers, body, backend)
|
||||
def self.provision_http_client(hostname)
|
||||
# Pin http clients to their host Thread so the fiber scheduler doesn't get upset and raise an error
|
||||
HTTP_CLIENTS[Thread.current] ||= {}
|
||||
return HTTP_CLIENTS[Thread.current][hostname.downcase] if HTTP_CLIENTS[Thread.current][hostname.downcase]
|
||||
|
||||
ssl_context = W3DHub.ca_bundle_path ? OpenSSL::SSL::SSLContext.new : nil
|
||||
ssl_context&.set_params(
|
||||
ca_file: W3DHub.ca_bundle_path,
|
||||
verify_mode: OpenSSL::SSL::VERIFY_PEER
|
||||
)
|
||||
|
||||
endpoint = Async::HTTP::Endpoint.parse(hostname, ssl_context: ssl_context)
|
||||
HTTP_CLIENTS[Thread.current][hostname.downcase] = Async::HTTP::Client.new(endpoint)
|
||||
end
|
||||
|
||||
def self.get(url, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
async_http(:get, url, headers, body, backend)
|
||||
def self.post(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
async_http(:post, path, headers, body, backend)
|
||||
end
|
||||
|
||||
def self.get(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
||||
async_http(:get, path, headers, body, backend)
|
||||
end
|
||||
|
||||
# Api.get but handles any URL instead of known hosts
|
||||
def self.fetch(url, headers = DEFAULT_HEADERS, body = nil, backend = nil)
|
||||
async_http(:get, url, headers, body, backend)
|
||||
def self.fetch(path, headers = DEFAULT_HEADERS, body = nil, backend = nil)
|
||||
async_http(:get, path, headers, body, backend)
|
||||
end
|
||||
|
||||
# Method: POST
|
||||
|
||||
@@ -5,6 +5,7 @@ class W3DHub
|
||||
|
||||
def initialize
|
||||
@tasks = [] # :installer, :importer, :repairer, :uninstaller
|
||||
@running_applications = {}
|
||||
end
|
||||
|
||||
def install(app_id, channel)
|
||||
@@ -22,9 +23,7 @@ class W3DHub
|
||||
# unpack packages
|
||||
# install dependencies (e.g. visual C runtime)
|
||||
|
||||
installer = Installer.new(app_id, channel)
|
||||
|
||||
@tasks.push(installer)
|
||||
@tasks.push(Installer.new(app_id, channel))
|
||||
end
|
||||
|
||||
def update(app_id, channel)
|
||||
@@ -32,9 +31,7 @@ class W3DHub
|
||||
|
||||
return false unless installed?(app_id, channel)
|
||||
|
||||
updater = Updater.new(app_id, channel)
|
||||
|
||||
@tasks.push(updater)
|
||||
@tasks.push(Updater.new(app_id, channel))
|
||||
end
|
||||
|
||||
def import(app_id, channel)
|
||||
@@ -169,11 +166,16 @@ class W3DHub
|
||||
def wine_command(app_id, channel)
|
||||
return "" if W3DHub.windows?
|
||||
|
||||
if !Store.settings[:wine_prefix].to_s.empty?
|
||||
"WINEPREFIX=\"#{Store.settings[:wine_prefix]}\" \"#{Store.settings[:wine_command]}\" "
|
||||
else
|
||||
"#{Store.settings[:wine_command]} "
|
||||
"\"#{Store.settings[:wine_command]}\" "
|
||||
end
|
||||
|
||||
def wine_enviroment_variables(app_id, channel)
|
||||
vars = {}
|
||||
return vars if W3DHub.windows?
|
||||
|
||||
vars["WINEPREFIX"] = Store.settings[:wine_prefix] unless Store.settings[:wine_prefix].to_s.empty?
|
||||
|
||||
vars
|
||||
end
|
||||
|
||||
def mangohud_command(app_id, channel)
|
||||
@@ -188,6 +190,13 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
def mangohud_enviroment_variables(app_id, channel)
|
||||
vars = {}
|
||||
return vars if W3DHub.windows?
|
||||
|
||||
vars
|
||||
end
|
||||
|
||||
def dxvk_command(app_id, channel)
|
||||
return "" if W3DHub.windows?
|
||||
|
||||
@@ -201,6 +210,13 @@ class W3DHub
|
||||
end
|
||||
end
|
||||
|
||||
def dxvk_enviroment_variables(app_id, channel)
|
||||
vars = {}
|
||||
return vars if W3DHub.windows?
|
||||
|
||||
vars
|
||||
end
|
||||
|
||||
def start_command(path, exe)
|
||||
if W3DHub.windows?
|
||||
"start /D \"#{path}\" /B #{exe}"
|
||||
@@ -212,16 +228,32 @@ class W3DHub
|
||||
def run(app_id, channel, *args)
|
||||
if (app_data = installed?(app_id, channel))
|
||||
install_directory = app_data[:install_directory]
|
||||
exe_path = app_id == "ecw" ? "#{install_directory}/game500.exe" : "#{install_directory}/game.exe"
|
||||
exe_path = app_id == "ecw" ? "#{install_directory}/game500.exe" : app_data[:install_path]
|
||||
exe_path.gsub!("/", "\\") if W3DHub.windows?
|
||||
exe_path.gsub!("\\", "/") if W3DHub.unix?
|
||||
|
||||
exe = File.basename(exe_path)
|
||||
path = File.dirname(exe_path)
|
||||
|
||||
env = {}
|
||||
if W3DHub.unix?
|
||||
env.merge!(
|
||||
dxvk_enviroment_variables(app_id, channel),
|
||||
mangohud_enviroment_variables(app_id, channel),
|
||||
wine_enviroment_variables(app_id, channel)
|
||||
)
|
||||
end
|
||||
|
||||
attempted = false
|
||||
begin
|
||||
pid = Process.spawn("#{dxvk_command(app_id, channel)}#{mangohud_command(app_id, channel)}#{wine_command(app_id, channel)}#{attempted ? start_command(path, exe) : "\"#{exe_path}\""} -launcher #{args.join(' ')}")
|
||||
pid = Process.spawn(
|
||||
env,
|
||||
"#{dxvk_command(app_id, channel)}"\
|
||||
"#{mangohud_command(app_id, channel)}"\
|
||||
"#{wine_command(app_id, channel)}"\
|
||||
"#{attempted ? start_command(path, exe) : "\"#{exe_path}\""} "\
|
||||
"-launcher #{args.join(' ')}"
|
||||
)
|
||||
Process.detach(pid)
|
||||
rescue Errno::EINVAL => e
|
||||
retryable = !attempted
|
||||
@@ -236,13 +268,15 @@ class W3DHub
|
||||
end
|
||||
|
||||
def join_server(app_id, channel, server, username = Store.settings[:server_list_username], password = nil, multi = false)
|
||||
if installed?(app_id, channel) && username.to_s.length.positive?
|
||||
return unless installed?(app_id, channel) && username.to_s.length.positive?
|
||||
|
||||
run(
|
||||
app_id, channel,
|
||||
"+connect #{server.address}:#{server.port} +netplayername #{username}#{password ? " +password \"#{password}\"" : ""}#{multi ? " +multi" : ""}"
|
||||
"+connect #{server.address}:#{server.port} "\
|
||||
"+netplayername #{username}#{password ? " +password \"#{password}\"" : ""}"\
|
||||
"#{multi ? " +multi" : ""}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def play_now_server(app_id, channel)
|
||||
app_data = installed?(app_id, channel)
|
||||
@@ -255,13 +289,18 @@ class W3DHub
|
||||
!server.status.password &&
|
||||
server.status.player_count < server.status.max_players
|
||||
end
|
||||
# sort by player count HIGH to LOW
|
||||
# and by ping LOW to HIGH
|
||||
server_options.sort! do |a, b|
|
||||
[b.status.player_count, a.ping] <=> [a.status.player_count, b.ping]
|
||||
end
|
||||
|
||||
# try to find server with lowest ping and matching version
|
||||
found_server = server_options.find { |server| server.version == app_data[:installed_version] }
|
||||
# try to find server with lowest ping and undefined version
|
||||
found_server ||= server_options.find { |server| server.version == Api::ServerListServer::NO_OR_DEFAULT_VERSION }
|
||||
|
||||
found_server ? found_server : nil
|
||||
found_server
|
||||
end
|
||||
|
||||
def play_now(app_id, channel)
|
||||
|
||||
@@ -746,6 +746,8 @@ class W3DHub
|
||||
end
|
||||
patch_entry = patch_mix.entries.find { |e| e.name.casecmp?(".w3dhub.patch") || e.name.casecmp?(".bhppatch") }
|
||||
patch_entry.read
|
||||
# "remove" patch meta file from patch before copying patch data
|
||||
patch_mix.entries.delete(patch_entry)
|
||||
|
||||
patch_info = JSON.parse(patch_entry.blob, symbolize_names: true)
|
||||
|
||||
@@ -765,20 +767,15 @@ class W3DHub
|
||||
patch_info[:updatedFiles].each do |file|
|
||||
logger.debug(LOG_TAG) { " #{file}" }
|
||||
|
||||
patch = patch_mix.entries.find { |e| e.name.casecmp?(file) }
|
||||
target = target_mix.entries.find { |e| e.name.casecmp?(file) }
|
||||
|
||||
if target
|
||||
target_mix.entries[target_mix.entries.index(target)] = patch
|
||||
else
|
||||
target_mix.entries << patch
|
||||
patch_mix.entries.each do |entry|
|
||||
target_mix.add_entry(entry: entry, replace: true)
|
||||
end
|
||||
end
|
||||
|
||||
logger.info(LOG_TAG) { " Writing updated #{file_path}..." } if patch_info[:updatedFiles].size.positive?
|
||||
temp_mix_path = "#{temp_path}/#{File.basename(file_path)}"
|
||||
temp_mix = W3DHub::WWMix.new(path: temp_mix_path)
|
||||
target_mix.entries.each { |e| temp_mix.add_entry(entry: e) }
|
||||
temp_mix = W3DHub::WWMix.new(path: temp_mix_path, encrypted: target_mix.encrypted?)
|
||||
target_mix.entries.each { |e| temp_mix.add_entry(entry: e, replace: true) }
|
||||
unless temp_mix.save
|
||||
raise temp_mix.error_reason
|
||||
end
|
||||
|
||||
19
lib/cache.rb
19
lib/cache.rb
@@ -75,14 +75,13 @@ class W3DHub
|
||||
|
||||
result = false
|
||||
Sync do
|
||||
response = nil
|
||||
uri = URI(endpoint_download_url)
|
||||
|
||||
Async::HTTP::Internet.send(package.download_url ? :get : :post, endpoint_download_url, headers, body) do |r|
|
||||
response = r
|
||||
if r.success?
|
||||
response = W3DHub::Api.provision_http_client(uri.origin).send((package.download_url ? :get : :post), uri.request_uri, headers, body)
|
||||
if response.success?
|
||||
total_bytes = package.size
|
||||
|
||||
r.each do |chunk|
|
||||
response.each do |chunk|
|
||||
file.write(chunk)
|
||||
|
||||
block.call(chunk, total_bytes - file.pos, total_bytes)
|
||||
@@ -90,9 +89,10 @@ class W3DHub
|
||||
|
||||
result = true
|
||||
end
|
||||
end
|
||||
|
||||
if response.status == 200 || response.status == 206
|
||||
binding.irb unless response
|
||||
|
||||
if response&.status == 200 || response&.status == 206
|
||||
result = true
|
||||
else
|
||||
logger.debug(LOG_TAG) { " Failed to retrieve package: (#{package.category}:#{package.subcategory}:#{package.name}:#{package.version})" }
|
||||
@@ -114,11 +114,12 @@ class W3DHub
|
||||
logger.debug(LOG_TAG) { " Download URL: #{endpoint_download_url}, response: #{response&.status || -1}" }
|
||||
|
||||
result = false
|
||||
ensure
|
||||
file&.close
|
||||
response&.close
|
||||
end
|
||||
|
||||
result
|
||||
ensure
|
||||
file&.close
|
||||
end
|
||||
|
||||
# Download a W3D Hub package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class W3DHub
|
||||
DIR_NAME = "W3DHubAlt".freeze
|
||||
VERSION = "0.9.0".freeze
|
||||
VERSION = "0.9.2".freeze
|
||||
end
|
||||
|
||||
@@ -16,7 +16,14 @@ class W3DHub
|
||||
yield(self)
|
||||
|
||||
Sync do |task|
|
||||
endpoint = Async::HTTP::Endpoint.parse(endpoint, alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
|
||||
ssl_context = W3DHub.ca_bundle_path ? OpenSSL::SSL::SSLContext.new : nil
|
||||
ssl_context&.alpn_protocols = Async::HTTP::Protocol::HTTP11.names
|
||||
ssl_context&.set_params(
|
||||
ca_file: W3DHub.ca_bundle_path,
|
||||
verify_mode: OpenSSL::SSL::VERIFY_PEER
|
||||
)
|
||||
|
||||
endpoint = Async::HTTP::Endpoint.parse(endpoint, alpn_protocols: Async::HTTP::Protocol::HTTP11.names, ssl_context: ssl_context)
|
||||
|
||||
Async::WebSocket::Client.connect(endpoint, headers: headers) do |connection|
|
||||
@connection = connection
|
||||
|
||||
@@ -228,27 +228,34 @@ class W3DHub
|
||||
@encrypted
|
||||
end
|
||||
|
||||
def add_file(path:)
|
||||
def add_file(path:, replace: false)
|
||||
return false unless File.exist?(path)
|
||||
return false if File.directory?(path)
|
||||
|
||||
info = EntryInfoHeader.new(0, 0, File.size(path))
|
||||
@entries << Entry.new(name: File.basename(path), path: path, info: info)
|
||||
|
||||
true
|
||||
entry = Entry.new(name: File.basename(path), path: path, info: EntryInfoHeader.new(0, 0, File.size(path)))
|
||||
add_entry(entry: entry, replace: replace)
|
||||
end
|
||||
|
||||
def add_blob(path:, blob:)
|
||||
def add_blob(path:, blob:, replace: false)
|
||||
info = EntryInfoHeader.new(0, 0, blob.size)
|
||||
@entries << Entry.new(name: File.basename(path), path: path, info: info, blob: blob)
|
||||
entry = Entry.new(name: File.basename(path), path: path, info: info, blob: blob)
|
||||
into.crc32 = @entries.last.calculate_crc32
|
||||
|
||||
true
|
||||
add_entry(entry: entry, replace: replace)
|
||||
end
|
||||
|
||||
def add_entry(entry:)
|
||||
@entries << entry
|
||||
def add_entry(entry:, replace: false)
|
||||
duplicate = @entries.find { |e| e.name.upcase == entry.name.upcase }
|
||||
|
||||
if duplicate
|
||||
if replace
|
||||
@entries.delete(duplicate)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
@entries << entry
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user