14 Commits

Author SHA1 Message Date
3c565e6fee Bump version 2025-10-08 11:33:05 -05:00
2dc750a686 Task#normalize_path now takes the base path as an argument and attempts to find the case-insensitive file path to target path 2025-10-08 11:32:35 -05:00
ed119a4925 Fixed All Games section was failing to load app icons 2025-09-19 13:55:06 -05:00
e4d99aac00 Added functional support for developer multi join 2025-09-12 23:41:03 -05:00
e9b8638c27 Fixed (soft) crash when downloading package with a space in its name 2025-09-05 20:02:58 -05:00
4997cfabb0 Fixed not handling filename case for patches 2025-08-26 09:23:54 -05:00
The Unnamed Engineer
0c906464f0 case desensitize unzip 2025-08-26 08:55:27 -05:00
The Unnamed Engineer
5bafc77d97 Update task.rb
Modify all potentially case sensitive file operations to operate in a case-insensitive manner.
2025-08-26 08:55:27 -05:00
30aa44312d Fixed failing to download application manifests unless logged in by checking which source the application/channel orginated from, updated gems. 2025-08-26 08:51:08 -05:00
2031f589b7 Moved processed app icons to cache directory, removed app logos (banners) from media since we now download them and app backgrounds from the api 2025-08-04 22:09:00 -05:00
b909952790 Use fresh logos and backgrounds 2025-08-04 12:25:27 -05:00
6d651c7ad6 Download game logos and backgrounds from backend 2025-08-04 12:25:13 -05:00
60909b0963 Fixed W3DHub.ask_folder crashing on windows 2025-08-04 12:18:38 -05:00
48617b26da Minor post-merge refactor, mainly moved duplicated method ca_bundle_path into common.rb 2025-08-04 10:50:07 -05:00
24 changed files with 192 additions and 111 deletions

View File

@@ -8,7 +8,7 @@ GEM
digest-crc (0.7.0) digest-crc (0.7.0)
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 (1.2.7) excon (1.3.0)
logger logger
ffi (1.17.2-x64-mingw-ucrt) ffi (1.17.2-x64-mingw-ucrt)
ffi (1.17.2-x86_64-linux-gnu) ffi (1.17.2-x86_64-linux-gnu)
@@ -22,8 +22,8 @@ GEM
logger (1.7.0) logger (1.7.0)
mutex_m (0.3.0) mutex_m (0.3.0)
rake (13.3.0) rake (13.3.0)
rexml (3.4.1) rexml (3.4.2)
rubyzip (2.4.1) rubyzip (3.0.2)
sdl2-bindings (0.2.3) sdl2-bindings (0.2.3)
ffi (~> 1.15) ffi (~> 1.15)
websocket (1.2.11) websocket (1.2.11)
@@ -58,4 +58,4 @@ DEPENDENCIES
win32-security win32-security
BUNDLED WITH BUNDLED WITH
2.6.7 2.6.8

View File

@@ -1,16 +1,8 @@
class W3DHub class W3DHub
class Api class Api
# Detect CA bundle path for Excon
def self.ca_bundle_path
redhat_path = '/etc/pki/tls/certs/ca-bundle.crt'
debian_path = '/etc/ssl/certs/ca-certificates.crt'
[redhat_path, debian_path].find { |path| File.exist?(path) }
end
# Set Excon default CA file if found # Set Excon default CA file if found
ca_file = ca_bundle_path if (ca_file = W3DHub.ca_bundle_path)
if ca_file
Excon.defaults[:ssl_ca_file] = ca_file Excon.defaults[:ssl_ca_file] = ca_file
end end
@@ -248,7 +240,7 @@ class W3DHub
response = post("/apis/launcher/1/get-applications", DEFAULT_HEADERS, nil, backend) response = post("/apis/launcher/1/get-applications", DEFAULT_HEADERS, nil, backend)
if response.status == 200 if response.status == 200
Applications.new(response.body) Applications.new(response.body, backend)
else else
logger.error(LOG_TAG) { "Failed to fetch applications list:" } logger.error(LOG_TAG) { "Failed to fetch applications list:" }
logger.error(LOG_TAG) { response } logger.error(LOG_TAG) { response }
@@ -302,7 +294,7 @@ class W3DHub
next next
end end
# If the access levels doen't match then overwrite alternate's channel with primary's channel # If the access levels don't match then overwrite alternate's channel with primary's channel
if channel.user_level != _channel.user_level if channel.user_level != _channel.user_level
# Replace alternate's channel with primary's channel # Replace alternate's channel with primary's channel
_game.channels[_game.channels.index(_channel)] = channel _game.channels[_game.channels.index(_channel)] = channel
@@ -311,7 +303,7 @@ class W3DHub
next next
end end
# If versions doen't match then pick whichever one is higher # If versions don't match then pick whichever one is higher
if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version) if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version)
# Replace alternate's channel with primary's channel # Replace alternate's channel with primary's channel
_game.channels[_game.channels.index(_channel)] = channel _game.channels[_game.channels.index(_channel)] = channel

View File

@@ -3,14 +3,14 @@ class W3DHub
class Applications class Applications
attr_reader :data attr_reader :data
def initialize(response) def initialize(response, source = nil)
@data = JSON.parse(response, symbolize_names: true) @data = JSON.parse(response, symbolize_names: true)
games = @data[:applications].select { |a| a[:category] == "games" } games = @data[:applications].select { |a| a[:category] == "games" }
@games = [] @games = []
games.each { |hash| @games << Game.new(hash) } games.each { |hash| @games << Game.new(hash, source) }
@games.sort_by!(&:name).reverse @games.sort_by!(&:name).reverse
end end
@@ -20,9 +20,11 @@ class W3DHub
class Game class Game
attr_reader :id, :name, :type, :category, :studio_id, :channels, :web_links, :color attr_reader :id, :name, :type, :category, :studio_id, :channels, :web_links, :color
attr_reader :___source
def initialize(hash) def initialize(hash, source = nil)
@data = hash @data = hash
@data[:___source] = source if source
@id = @data[:id].to_s @id = @data[:id].to_s
@name = @data[:name] @name = @data[:name]
@@ -31,7 +33,7 @@ class W3DHub
@studio_id = @data[:"studio-id"] @studio_id = @data[:"studio-id"]
# TODO: Do processing # TODO: Do processing
@channels = @data[:channels].map { |channel| Channel.new(channel) } @channels = @data[:channels].map { |channel| Channel.new(channel, source) }
@web_links = @data[:"web-links"]&.map { |link| WebLink.new(link) } || [] @web_links = @data[:"web-links"]&.map { |link| WebLink.new(link) } || []
@extended_data = @data[:"extended-data"] @extended_data = @data[:"extended-data"]
@@ -55,17 +57,34 @@ class W3DHub
@uses_ren_folder @uses_ren_folder
end end
def source
@data[:___source]&.to_sym || :w3dhub
end
def source=(sym)
@data[:___source] = sym
end
class Channel class Channel
attr_reader :id, :name, :user_level, :current_version attr_reader :id, :name, :user_level, :current_version
def initialize(hash) def initialize(hash, source = nil)
@data = hash @data = hash
@data[:___source] = source
@id = @data[:id].to_s @id = @data[:id].to_s
@name = @data[:name] @name = @data[:name]
@user_level = @data[:"user-level"] @user_level = @data[:"user-level"]
@current_version = @data[:"current-version"] @current_version = @data[:"current-version"]
end end
def source
@data[:___source]&.to_sym || :w3dhub
end
def source=(sym)
@data[:___source] = sym
end
end end
class WebLink class WebLink

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,6 +112,31 @@ class W3DHub
@task_state == :failed @task_state == :failed
end end
def normalize_path(path, base_path)
path = path.to_s.gsub("\\", "/")
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
def failure_reason def failure_reason
@task_failure_reason || "" @task_failure_reason || ""
end end
@@ -259,7 +284,7 @@ class W3DHub
next if packages.detect do |pkg| next if packages.detect do |pkg|
pkg.category == "games" && pkg.category == "games" &&
pkg.subcategory == @app_id && pkg.subcategory == @app_id &&
pkg.name == file.package && pkg.name.to_s.casecmp?(file.package.to_s) &&
pkg.version == file.version pkg.version == file.version
end end
@@ -295,17 +320,13 @@ class W3DHub
@files.reverse.each do |file| @files.reverse.each do |file|
break unless folder_exists break unless folder_exists
safe_file_name = file.name.gsub("\\", "/") file_path = normalize_path(file.name, path)
# Fix borked Data -> data 'cause Windows don't care about capitalization
safe_file_name.sub!("Data/", "data/")
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 }
@@ -325,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 }
@@ -369,7 +390,7 @@ class W3DHub
} }
end end
package_details = Api.package_details(hashes) package_details = Api.package_details(hashes, @channel.source || :w3dhub)
unless package_details unless package_details
fail!("Failed to fetch package details") fail!("Failed to fetch package details")
@@ -383,9 +404,9 @@ class W3DHub
end end
package = @packages.find do |pkg| package = @packages.find do |pkg|
pkg.category == rich.category && pkg.category.to_s.casecmp?(rich.category.to_s) &&
pkg.subcategory == rich.subcategory && pkg.subcategory.to_s.casecmp?(rich.subcategory.to_s) &&
"#{pkg.name}.zip" == rich.name && "#{pkg.name}.zip".casecmp?(rich.name) &&
pkg.version == rich.version pkg.version == rich.version
end end
@@ -529,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)
@@ -562,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}")
@@ -596,7 +617,7 @@ class W3DHub
# Check for and integrity of local manifest # Check for and integrity of local manifest
package = nil package = nil
array = Api.package_details([{ category: category, subcategory: subcategory, name: name, version: version }]) array = Api.package_details([{ category: category, subcategory: subcategory, name: name, version: version }], @channel.source || :w3dhub)
if array.is_a?(Array) if array.is_a?(Array)
package = array.first package = array.first
else else
@@ -707,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)
# Fix borked Data -> data 'cause Windows don't care about capitalization file_path = normalize_path(manifest_file.name, path)
safe_file_name = "#{manifest_file.name.sub('Data/', 'data/')}" 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|
@@ -737,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)
@@ -749,17 +770,15 @@ class W3DHub
stream = Zip::InputStream.new(File.open(package_path)) stream = Zip::InputStream.new(File.open(package_path))
while (entry = stream.get_next_entry) while (entry = stream.get_next_entry)
# Normalize the path to handle case-insensitivity consistently
file_path = normalize_path(entry.name, path)
safe_file_name = entry.name.gsub("\\", "/") dir_path = File.dirname(file_path)
# Fix borked Data -> data 'cause Windows don't care about capitalization
safe_file_name.sub!("Data/", "data/")
dir_path = "#{path}/#{File.dirname(safe_file_name)}"
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

@@ -10,15 +10,6 @@ class W3DHub
TAG = "IRCClient" TAG = "IRCClient"
class SSL class SSL
# Detect system CA bundle path for SSL verification
def self.ca_bundle_path
[
'/etc/ssl/certs/ca-certificates.crt', # Debian/Ubuntu
'/etc/pki/tls/certs/ca-bundle.crt', # RHEL/Fedora/CentOS
'/etc/ssl/ca-bundle.pem' # Some other distros
].find { |path| File.exist?(path) }
end
def self.default_context def self.default_context
verify_peer_and_hostname verify_peer_and_hostname
end end
@@ -33,8 +24,7 @@ class W3DHub
no_verify.tap do |context| no_verify.tap do |context|
context.verify_mode = OpenSSL::SSL::VERIFY_PEER context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.cert_store = OpenSSL::X509::Store.new context.cert_store = OpenSSL::X509::Store.new
ca_file = ca_bundle_path if (ca_file = W3DHub.ca_bundle_path)
if ca_file
context.cert_store.add_file(ca_file) context.cert_store.add_file(ca_file)
else else
context.cert_store.set_default_paths context.cert_store.set_default_paths

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

@@ -32,6 +32,15 @@ class W3DHub
linux? || mac? linux? || mac?
end end
# Detect system CA bundle path for SSL verification
def self.ca_bundle_path
[
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu
"/etc/pki/tls/certs/ca-bundle.crt", # RHEL/Fedora/CentOS
"/etc/ssl/ca-bundle.pem" # Some other distros
].find { |path| File.exist?(path) }
end
def self.url(path) def self.url(path)
raise "Hazardous input: #{path}" if path.include?("&&") || path.include?(";") raise "Hazardous input: #{path}" if path.include?("&&") || path.include?(";")
@@ -72,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: "?")
@@ -193,7 +206,7 @@ class W3DHub
path.strip path.strip
else else
result_ptr = LibUI.open_folder(window) result_ptr = LibUI.open_folder(LIBUI_WINDOW)
result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/") result = result_ptr.null? ? "" : result_ptr.to_s.gsub("\\", "/")
result.strip result.strip

View File

@@ -36,7 +36,7 @@ class W3DHub
background app_color background app_color
flow(width: 0.70, height: 1.0) do flow(width: 0.70, height: 1.0) do
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png" image_path = File.exist?("#{CACHE_PATH}/#{task.app_id}.png") ? "#{CACHE_PATH}/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
@application_image = image image_path, height: 1.0 @application_image = image image_path, height: 1.0
stack(margin_left: 8, width: 0.75) do stack(margin_left: 8, width: 0.75) do

View File

@@ -17,7 +17,7 @@ class W3DHub
end end
# Game Menu # Game Menu
@game_page_container = stack(width: 1.0, fill: true, background_image: "#{GAME_ROOT_PATH}/media/textures/noiseb.png", background_image_mode: :tiled) do @game_page_container = stack(width: 1.0, fill: true) do
end end
end end
end end
@@ -85,7 +85,7 @@ class W3DHub
padding_left: 4, padding_right: 4, tip: game.name) do padding_left: 4, padding_right: 4, tip: game.name) do
background game.color if selected background game.color if selected
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"
image_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff image_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff
flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do
@@ -118,28 +118,25 @@ class W3DHub
@game_page_container.clear do @game_page_container.clear do
game_color = Gosu::Color.new(game.color) game_color = Gosu::Color.new(game.color)
game_color.alpha = 0x88 game_color.alpha = 0xaa
background game_color background_image_path = Cache.package_path(game.category, game.id, "background.png", "")
@game_page_container.style.background_image_color = game_color if File.exist?(background_image_path)
@game_page_container.style.default[:background_image_color] = game_color States::Interface.instance&.instance_variable_get(:"@interface_container")&.style&.background_image = get_image(background_image_path)
@game_page_container.update_background_image States::Interface.instance&.instance_variable_get(:"@interface_container")&.style&.default[:background_image] = get_image(background_image_path)
end
# Game Stuff # Game Stuff
flow(width: 1.0, fill: true) do flow(width: 1.0, fill: true) do
# background 0xff_9999ff
# Game options # Game options
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do stack(width: 360, height: 1.0, padding: 8, scroll: true, background: game_color, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
background 0x55_000000 # Game Logo
logo_image_path = Cache.package_path(game.category, game.id, "logo.png", "")
# Game Banner if File.exist?(logo_image_path)
image_path = "#{GAME_ROOT_PATH}/media/banners/#{game.id}.png" image logo_image_path, width: 1.0
if File.exist?(image_path)
image image_path, width: 1.0
else else
banner game.name unless File.exist?(image_path) banner game.name unless File.exist?(logo_image_path)
end end
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
@@ -331,11 +328,6 @@ class W3DHub
def populate_all_games_view def populate_all_games_view
@game_page_container.clear do @game_page_container.clear do
background 0x88_353535
@game_page_container.style.background_image_color = 0x88_353535
@game_page_container.style.default[:background_image_color] = 0x88_353535
@game_page_container.update_background_image
@focused_game = nil @focused_game = nil
@focused_channel = nil @focused_channel = nil
@@ -383,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
@@ -438,6 +430,9 @@ class W3DHub
return unless @focused_game == game return unless @focused_game == game
if (feed = @game_news[game.id]) if (feed = @game_news[game.id])
game_color = Gosu::Color.new(game.color)
game_color.alpha = 0xaa
@game_news_container.clear do @game_news_container.clear do
# Patch Notes # Patch Notes
if false # Patch notes if false # Patch notes
@@ -467,9 +462,7 @@ class W3DHub
feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item| feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item|
image_path = Cache.path(item.image) image_path = Cache.path(item.image)
flow(width: 1.0, max_width: 869, height: 200, margin: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do flow(width: 1.0, max_width: 869, height: 200, margin: 8, background: game_color, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
background 0x44_000000
if File.file?(image_path) if File.file?(image_path)
image image_path, height: 1.0 image image_path, height: 1.0
end end

View File

@@ -43,7 +43,7 @@ class W3DHub
app = Store.applications.games.find { |a| a.id == app_id.to_s } app = Store.applications.games.find { |a| a.id == app_id.to_s }
next unless app next unless app
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png" image_path = File.exist?("#{CACHE_PATH}/#{app.id}.png") ? "#{CACHE_PATH}/#{app.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
image image_path, tip: "#{app.name}", height: 1.0, image image_path, tip: "#{app.name}", height: 1.0,
border_thickness_bottom: 1, border_color_bottom: 0x00_000000, border_thickness_bottom: 1, border_color_bottom: 0x00_000000,
@@ -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)
@@ -534,7 +540,7 @@ class W3DHub
end end
def game_icon(server) def game_icon(server)
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png" image_path = File.exist?("#{CACHE_PATH}/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{CACHE_PATH}/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
if server.status.password if server.status.password
@server_locked_icons[server.game] ||= Gosu.render(96, 96) do @server_locked_icons[server.game] ||= Gosu.render(96, 96) do

View File

@@ -85,6 +85,8 @@ class W3DHub
"Español" "Español"
else else
logger.warn("W3DHub::Settings") { "Unknown language code: #{string.inspect}" } logger.warn("W3DHub::Settings") { "Unknown language code: #{string.inspect}" }
"UNKNOWN"
end end
end end

View File

@@ -17,7 +17,8 @@ class W3DHub
refresh_user_token: { started: false, complete: false }, refresh_user_token: { started: false, complete: false },
service_status: { started: false, complete: false }, service_status: { started: false, complete: false },
applications: { started: false, complete: false }, applications: { started: false, complete: false },
app_icons: { started: false, complete: false } app_icons: { started: false, complete: false },
app_logos_and_backgrounds: { started: false, complete: false }
} }
@offline_mode = false @offline_mode = false
@@ -207,6 +208,8 @@ class W3DHub
def app_icons def app_icons
return unless Store.applications return unless Store.applications
@status_label.value = "Retrieving application icons, this might take a moment..." # I18n.t(:"boot.checking_for_updates")
packages = [] packages = []
Store.applications.games.each do |app| Store.applications.games.each do |app|
packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" } packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" }
@@ -219,7 +222,7 @@ class W3DHub
next if package.error? next if package.error?
path = Cache.package_path(package.category, package.subcategory, package.name, package.version) path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png" generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png"
regenerate = false regenerate = false
@@ -241,6 +244,34 @@ class W3DHub
end end
end end
def app_logos_and_backgrounds
return unless Store.applications
@status_label.value = "Retrieving application image assets, this might take a moment..." # I18n.t(:"boot.checking_for_updates")
packages = []
Store.applications.games.each do |app|
packages << { category: app.category, subcategory: app.id, name: "logo.png", version: "" }
packages << { category: app.category, subcategory: app.id, name: "background.png", version: "" }
end
Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details|
package_details ||= nil
package_details&.each do |package|
next if package.error?
package_cache_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
missing_or_broken_image = File.exist?(package_cache_path) ? Digest::SHA256.new.hexdigest(File.binread(package_cache_path)).upcase != package.checksum.upcase : true
Cache.fetch_package(package, proc {}) if missing_or_broken_image
end
@tasks[:app_logos_and_backgrounds][:complete] = true
end
end
def server_list def server_list
@status_label.value = I18n.t(:"server_browser.fetching_server_list") @status_label.value = I18n.t(:"server_browser.fetching_server_list")

View File

@@ -4,6 +4,8 @@ class W3DHub
APPLICATIONS_UPDATE_INTERVAL = 10 * 60 * 1000 # ten minutes APPLICATIONS_UPDATE_INTERVAL = 10 * 60 * 1000 # ten minutes
SERVER_LIST_UPDATE_INTERVAL = 5 * 60 * 1000 # five minutes SERVER_LIST_UPDATE_INTERVAL = 5 * 60 * 1000 # five minutes
DEFAULT_BACKGROUND_IMAGE = "#{GAME_ROOT_PATH}/media/banners/background.png".freeze
attr_accessor :interface_task_update_pending attr_accessor :interface_task_update_pending
@@instance = nil @@instance = nil
@@ -33,10 +35,12 @@ class W3DHub
theme(W3DHub::THEME) theme(W3DHub::THEME)
@interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR, background_image: "#{GAME_ROOT_PATH}/media/banners/background.png", background_image_color: 0xff_525252, background_image_mode: :fill) do @interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR, background_image: DEFAULT_BACKGROUND_IMAGE, background_image_mode: :fill) do
background 0xff_252525 background 0xff_252525
@header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) do @header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) do
background 0xaa_151515
flow(width: 148, height: 1.0) do flow(width: 148, height: 1.0) do
flow(fill: true) flow(fill: true)
image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 84 image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 84
@@ -54,18 +58,26 @@ class W3DHub
end end
link I18n.t(:"interface.servers").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do link I18n.t(:"interface.servers").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
page(W3DHub::Pages::ServerBrowser) page(W3DHub::Pages::ServerBrowser)
end end
link I18n.t(:"interface.community").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do link I18n.t(:"interface.community").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
page(W3DHub::Pages::Community) page(W3DHub::Pages::Community)
end end
link I18n.t(:"interface.downloads").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do link I18n.t(:"interface.downloads").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
page(W3DHub::Pages::DownloadManager) page(W3DHub::Pages::DownloadManager)
end end
link I18n.t(:"interface.settings").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do link I18n.t(:"interface.settings").upcase, text_size: 34, font: BOLD_FONT, margin_left: 12 do
@interface_container.style.background_image = DEFAULT_BACKGROUND_IMAGE
@interface_container.style.default[:background_image] = DEFAULT_BACKGROUND_IMAGE
page(W3DHub::Pages::Settings) page(W3DHub::Pages::Settings)
end end
end end

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB