Compare commits

...

4 Commits

13 changed files with 169 additions and 99 deletions

View File

@@ -4,6 +4,7 @@ gem "base64"
gem "excon" gem "excon"
gem "cyberarm_engine" gem "cyberarm_engine"
gem "sdl2-bindings" gem "sdl2-bindings"
gem "libui"
gem "digest-crc" gem "digest-crc"
gem "i18n" gem "i18n"
gem "ircparser" gem "ircparser"
@@ -13,11 +14,14 @@ gem "websocket-client-simple"
gem "win32-process", platforms: [:x64_mingw, :mingw] gem "win32-process", platforms: [:x64_mingw, :mingw]
gem "win32-security", platforms: [:x64_mingw, :mingw] gem "win32-security", platforms: [:x64_mingw, :mingw]
# Packaging on 3.3.0 is... painful. Using 3.2.0 for now. # PACKAGING NOTES
# bundler 2.5.x doesn't seem to play nice with ocra[n] # bundler 2.5.x doesn't seem to play nice with ocra[n]
# use `bundle _x.y.z_ COMMAND` to use this one... # use `bundle _x.y.z_ COMMAND` to use this one...
# NOTE: Releasy needs to be installed as a system gem i.e. `rake install`
# NOTE: contents of the `gemhome` folder in the packaged folder need to be moved into the lib/ruby/gems\<RUBY_VERSION> folder
# group :windows_packaging do # group :windows_packaging do
# gem "bundler", "~>2.4.3" # gem "bundler", "~>2.4.3"
# gem "rake" # gem "rake"
# gem "releasy", github: "cyberarm/releasy" # gem "ocran"
# gem "releasy"#, path: "../releasy"
# end # end

View File

@@ -3,22 +3,20 @@ GEM
specs: specs:
base64 (0.2.0) base64 (0.2.0)
concurrent-ruby (1.2.3) concurrent-ruby (1.2.3)
cyberarm_engine (0.24.2) cyberarm_engine (0.24.4)
excon (~> 0.88)
gosu (~> 1.1) gosu (~> 1.1)
gosu_more_drawables (~> 0.3)
digest-crc (0.6.5) digest-crc (0.6.5)
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.109.0) excon (0.109.0)
ffi (1.16.3-x64-mingw-ucrt) ffi (1.16.3)
ffi-win32-extensions (1.0.4) ffi-win32-extensions (1.0.4)
ffi ffi
gosu (1.4.6) gosu (1.4.6)
gosu_more_drawables (0.3.1)
i18n (1.14.1) i18n (1.14.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ircparser (1.0.0) ircparser (1.0.0)
libui (0.0.15)
rake (13.1.0) rake (13.1.0)
rexml (3.2.6) rexml (3.2.6)
rubyzip (2.3.2) rubyzip (2.3.2)
@@ -41,8 +39,10 @@ DEPENDENCIES
base64 base64
cyberarm_engine cyberarm_engine
digest-crc digest-crc
excon
i18n i18n
ircparser ircparser
libui
rexml rexml
rubyzip rubyzip
sdl2-bindings sdl2-bindings

View File

@@ -1,7 +1,7 @@
class W3DHub class W3DHub
class Api class Api
class Package class Package
attr_reader :category, :subcategory, :name, :version, :size, :checksum, :checksum_chunk_size, :checksum_chunks, attr_reader :category, :subcategory, :name, :version, :size, :checksum, :checksum_chunk_size, :checksum_chunks, :error,
:custom_partially_valid_at_bytes, :custom_is_patch :custom_partially_valid_at_bytes, :custom_is_patch
def initialize(hash) def initialize(hash)
@@ -16,6 +16,7 @@ class W3DHub
@checksum = @data[:checksum] @checksum = @data[:checksum]
@checksum_chunk_size = @data[:"checksum-chunk-size"] @checksum_chunk_size = @data[:"checksum-chunk-size"]
@checksum_chunks = @data[:"checksum-chunks"] @checksum_chunks = @data[:"checksum-chunks"]
@error = @data[:error] || nil
@custom_partially_valid_at_bytes = 0 @custom_partially_valid_at_bytes = 0
@custom_is_patch = false @custom_is_patch = false
@@ -25,6 +26,10 @@ class W3DHub
@checksum_chunks[:"#{key}"] @checksum_chunks[:"#{key}"]
end end
def error?
@error
end
def partially_valid_at_bytes=(i) def partially_valid_at_bytes=(i)
@custom_partially_valid_at_bytes = i @custom_partially_valid_at_bytes = i
end end

View File

@@ -73,18 +73,35 @@ class W3DHub
hash = JSON.parse(msg, symbolize_names: true) hash = JSON.parse(msg, symbolize_names: true)
# pp hash if hash[:target] != "ServerStatusChanged" && hash[:type] != 6 && hash[:type] != 3
# Send PING(?) # Send PING(?)
if hash.empty? || hash[:type] == 6 if hash.empty? || hash[:type] == 6
ws.send({ type: 6 }.to_json + "\x1e") ws.send({ type: 6 }.to_json + "\x1e")
else else
case hash[:type] case hash[:type]
when 1 when 1
if hash[:target] == "ServerStatusChanged" case hash[:target]
when "ServerRegistered"
data = hash[:arguments].first
server = ServerListServer.new(data)
Store.server_list.push(server)
when "ServerStatusChanged"
id, data = hash[:arguments] id, data = hash[:arguments]
server = Store.server_list.find { |s| s.id == id } server = Store.server_list.find { |s| s.id == id }
server_updated = server&.update(data) server_updated = server&.update(data)
BackgroundWorker.foreground_job(-> {}, ->(result){ States::Interface.instance&.update_server_browser(server) }) if server_updated BackgroundWorker.foreground_job(-> {}, ->(result){ States::Interface.instance&.update_server_browser(server, :update) }) if server_updated
when "ServerUnregistered"
id = hash[:arguments].first
server = Store.server_list.find { |s| s.id == id }
if server
Store.server_list.delete(server)
BackgroundWorker.foreground_job(-> {}, ->(result){ States::Interface.instance&.update_server_browser(server, :remove) })
end
end end
end end
end end

View File

@@ -201,6 +201,14 @@ class W3DHub
end end
end end
def start_command(path, exe)
if W3DHub.windows?
"start /D \"#{path}\" /B #{exe}"
else
"#{path}/#{exe}"
end
end
def run(app_id, channel, *args) def run(app_id, channel, *args)
if (app_data = installed?(app_id, channel)) if (app_data = installed?(app_id, channel))
install_directory = app_data[:install_directory] install_directory = app_data[:install_directory]
@@ -208,8 +216,22 @@ class W3DHub
exe_path.gsub!("/", "\\") if W3DHub.windows? exe_path.gsub!("/", "\\") if W3DHub.windows?
exe_path.gsub!("\\", "/") if W3DHub.unix? exe_path.gsub!("\\", "/") if W3DHub.unix?
pid = Process.spawn("#{dxvk_command(app_id, channel)}#{mangohud_command(app_id, channel)}#{wine_command(app_id, channel)}\"#{exe_path}\" -launcher #{args.join(' ')}") exe = File.basename(exe_path)
path = File.dirname(exe_path)
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(' ')}")
Process.detach(pid) Process.detach(pid)
rescue Errno::EINVAL => e
retryable = !attempted
attempted = true
# Assume that we're on windoze and that the game requires admin
retry if retryable
# TODO: Show an error message if we reach here...
end
end end
end end

View File

@@ -78,7 +78,7 @@ class W3DHub
@task_state = :complete unless @task_state == :failed @task_state = :complete unless @task_state == :failed
hide_application_taskbar if @task_state == :failed hide_application_taskbar if @task_state == :failed
send_message_dialog(:failure, "Task #{type.inspect} failed for #{@application.name}", @task_failure_reason) if @task_state == :failed && !@fail_silently send_message_dialog(:failure, "#{type.to_s.capitalize} Task failed for #{@application.name}", @task_failure_reason) if @task_state == :failed && !@fail_silently
# end # end
end end
end end
@@ -370,6 +370,11 @@ class W3DHub
end end
[package_details].flatten.each do |rich| [package_details].flatten.each do |rich|
if rich.error?
fail!("Failed to retrieve package details! (#{rich.category}:#{rich.subcategory}:#{rich.name}:#{rich.version})\nError: #{rich.error.gsub("-", " ").capitalize}")
return
end
package = @packages.find do |pkg| package = @packages.find do |pkg|
pkg.category == rich.category && pkg.category == rich.category &&
pkg.subcategory == rich.subcategory && pkg.subcategory == rich.subcategory &&
@@ -572,7 +577,12 @@ class W3DHub
if array.is_a?(Array) if array.is_a?(Array)
package = array.first package = array.first
else else
fail!("Failed to fetch manifest package details!") fail!("Failed to fetch manifest package details! (#{category}:#{subcategory}:#{name}:#{version})")
return
end
if package.error?
fail!("Failed to retrieve manifest package details! (#{category}:#{subcategory}:#{name}:#{version})\nError: #{package.error.gsub("-", " ").capitalize}")
return return
end end

View File

@@ -31,7 +31,7 @@ class W3DHub
flow(width: 1.0, fill: true) do flow(width: 1.0, fill: true) do
@game_path = edit_line "#{@game&.path}", fill: true, height: 1.0 @game_path = edit_line "#{@game&.path}", fill: true, height: 1.0
button "Browse...", width: 128, height: 1.0, enabled: W3DHub.unix?, tip: W3DHub.unix? ? "Browse for game executable" : "Not available on Windows" do button "Browse...", width: 128, height: 1.0, tip: "Browse for game executable" do
path = W3DHub.ask_file path = W3DHub.ask_file
@game_path.value = path if !path.empty? && File.exist?(path) @game_path.value = path if !path.empty? && File.exist?(path)
end end

View File

@@ -147,26 +147,16 @@ class W3DHub
end end
def self.ask_file(title: "Open File", filter: "*game*.exe") def self.ask_file(title: "Open File", filter: "*game*.exe")
if W3DHub.unix? result_ptr = LibUI.open_file(LIBUI_WINDOW)
# search for command result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/")
cmds = %w{ zenity matedialog qarma kdialog }
command = cmds.find do |cmd| result
cmd if system("which #{cmd}")
end end
path = case File.basename(command) def self.ask_folder(title: "Open Folder")
when "zenity", "matedialog", "qarma" result_ptr = LibUI.open_folder(window)
`#{command} --file-selection --title "#{title}" --file-filter "#{filter}"` result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/")
when "kdialog"
`#{command} --title "#{title}" --getopenfilename . "#{filter}"`
else
raise "No known command found for system file selection dialog!"
end
path.strip result
else
raise NotImplementedError
end
end end
end end

View File

@@ -231,12 +231,23 @@ class W3DHub
return list.first return list.first
end end
def refresh_server_list(server) def refresh_server_list(server, mode = :update) # :remove
@refresh_server_list = Gosu.milliseconds + 3_000 @refresh_server_list = Gosu.milliseconds + 3_000
@refresh_server = server if @selected_server&.id == server.id @refresh_server = server if @selected_server&.id == server.id
server_container = find_element_by_tag(@server_list_container, server.id) server_container = find_element_by_tag(@server_list_container, server.id)
case mode
when :update
if server.status && !server_container
@server_list_container.append do
create_server_container(server)
end
end
when :remove
@server_list_container.remove(server_container) if server_container
return
end
return unless server_container return unless server_container
game_icon = find_element_by_tag(server_container, :game_icon) game_icon = find_element_by_tag(server_container, :game_icon)
@@ -247,6 +258,7 @@ class W3DHub
player_count = find_element_by_tag(server_container, :player_count) player_count = find_element_by_tag(server_container, :player_count)
server_ping = find_element_by_tag(server_container, :ping) server_ping = find_element_by_tag(server_container, :ping)
game_icon.value = game_icon(server)
server_name.value = "<b>#{server&.status&.name}</b>" server_name.value = "<b>#{server&.status&.name}</b>"
server_channel.value = Store.application_manager.channel_name(server.game, server.channel).to_s server_channel.value = Store.application_manager.channel_name(server.game, server.channel).to_s
server_region.value = server.region server_region.value = server.region
@@ -299,18 +311,21 @@ class W3DHub
Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list
@server_list_container.clear do @server_list_container.clear do
i = -1
Store.server_list.each do |server| Store.server_list.each do |server|
next unless @filters[server.game.to_sym] create_server_container(server)
next unless server.region == @filter_region || @filter_region == "Any" end
next unless Store.application_manager.channel_name(server.game, server.channel) # can user access required game and channel for this server? end
i += 1 reorder_server_list
end
def create_server_container(server)
return unless @filters[server.game.to_sym]
return unless server.status
return unless server.region == @filter_region || @filter_region == "Any"
return unless Store.application_manager.channel_name(server.game, server.channel) # can user access required game and channel for this server?
server_container = flow(width: 1.0, height: 56, hover: { background: 0xaa_555566 }, active: { background: 0xaa_555588 }, tag: server.id, tip: ping_tip(server)) do server_container = flow(width: 1.0, height: 56, hover: { background: 0xaa_555566 }, active: { background: 0xaa_555588 }, tag: server.id, tip: ping_tip(server)) do
background 0x88_333333 if i.even?
flow(width: 56, height: 1.0, padding: 4) do flow(width: 56, height: 1.0, padding: 4) do
image game_icon(server), height: 1.0, tag: :game_icon image game_icon(server), height: 1.0, tag: :game_icon
end end
@@ -358,8 +373,6 @@ class W3DHub
stylize_selected_server(server_container) if server.id == @selected_server&.id stylize_selected_server(server_container) if server.id == @selected_server&.id
end end
end
end
def populate_server_info(server) def populate_server_info(server)
@game_server_info_container.clear do @game_server_info_container.clear do

View File

@@ -26,7 +26,7 @@ class W3DHub
flow(width: 1.0, fill: true) do flow(width: 1.0, fill: true) do
@game_path = edit_line "", fill: true, height: 1.0 @game_path = edit_line "", fill: true, height: 1.0
button "Browse...", width: 128, height: 1.0, enabled: W3DHub.unix?, tip: W3DHub.unix? ? "Browse for game executable" : "Not available on Windows" do button "Browse...", width: 128, height: 1.0, tip: "Browse for game executable" do
path = W3DHub.ask_file path = W3DHub.ask_file
@game_path.value = path if !path.empty? && File.exist?(path) @game_path.value = path if !path.empty? && File.exist?(path)
end end

View File

@@ -70,8 +70,8 @@ class W3DHub
@application_taskbar_container = flow(width: 1.0, height: 0.5) do @application_taskbar_container = flow(width: 1.0, height: 0.5) do
stack(width: 1.0, height: 1.0, margin_left: 16, margin_right: 16) do stack(width: 1.0, height: 1.0, margin_left: 16, margin_right: 16) do
flow(width: 1.0, height: 0.65) do flow(width: 1.0, height: 0.65) do
@application_taskbar_label = para "", width: 0.60, text_wrap: :none @application_taskbar_label = para "", fill: true, text_wrap: :none
@application_taskbar_status_label = para "", width: 0.40, text_align: :right, text_wrap: :none @application_taskbar_status_label = para "", width: 0.4, min_width: 256, text_align: :right, text_wrap: :none
end end
@application_taskbar_progressbar = progress fraction: 0.0, height: 2, width: 1.0 @application_taskbar_progressbar = progress fraction: 0.0, height: 2, width: 1.0
@@ -163,10 +163,10 @@ class W3DHub
@page @page
end end
def update_server_browser(server) def update_server_browser(server, mode = :update)
return unless @page.is_a?(Pages::ServerBrowser) return unless @page.is_a?(Pages::ServerBrowser)
@page.refresh_server_list(server) @page.refresh_server_list(server, mode)
end end
def update_server_ping(server) def update_server_ping(server)

View File

@@ -1,4 +1,4 @@
class W3DHub class W3DHub
DIR_NAME = "W3DHubAlt" DIR_NAME = "W3DHubAlt"
VERSION = "0.4.1" VERSION = "0.4.2"
end end

View File

@@ -80,6 +80,15 @@ require "i18n"
require "websocket-client-simple" require "websocket-client-simple"
require "English" require "English"
require "sdl2" require "sdl2"
require "libui"
# Using a WHOLE ui library for: native file/folder open dialogs...
LibUI.init
LIBUI_WINDOW = LibUI.new_window("", 100, 100, 0)
at_exit do
LibUI.control_destroy(LIBUI_WINDOW)
LibUI.uninit
end
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"] I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en I18n.default_locale = :en