mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-22 12:16:15 +00:00
Compare commits
11 Commits
d630e5044e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f024109327 | |||
| 287022f2b8 | |||
| 68df923bea | |||
| ddbec8d72c | |||
| 70d4e0c40f | |||
| f30658ffc2 | |||
| 9e8f4e1c71 | |||
| b7e2e69af9 | |||
| 3dbfd23b10 | |||
| d1d667056b | |||
| c881296ac8 |
12
Gemfile
12
Gemfile
@@ -18,9 +18,9 @@ gem "win32-security", platforms: [:windows]
|
||||
# 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
|
||||
gem "bundler", "~>2.4.3"
|
||||
gem "rake"
|
||||
gem "ocran"
|
||||
gem "releasy"#, path: "../releasy"
|
||||
end
|
||||
# group :windows_packaging do
|
||||
# gem "bundler", "~>2.4.3"
|
||||
# gem "rake"
|
||||
# gem "ocran"
|
||||
# gem "releasy"#, path: "../releasy"
|
||||
# end
|
||||
|
||||
21
Gemfile.lock
21
Gemfile.lock
@@ -1,13 +1,13 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
async (2.35.1)
|
||||
async (2.35.2)
|
||||
console (~> 1.29)
|
||||
fiber-annotation
|
||||
io-event (~> 1.11)
|
||||
metrics (~> 0.12)
|
||||
traces (~> 0.18)
|
||||
async-http (0.93.0)
|
||||
async-http (0.94.0)
|
||||
async (>= 2.10.2)
|
||||
async-pool (~> 0.11)
|
||||
io-endpoint (~> 0.14)
|
||||
@@ -30,7 +30,6 @@ GEM
|
||||
fiber-annotation
|
||||
fiber-local (~> 1.1)
|
||||
json
|
||||
cri (2.15.12)
|
||||
cyberarm_engine (0.25.0)
|
||||
gosu (~> 1.1)
|
||||
digest-crc (0.7.0)
|
||||
@@ -52,13 +51,11 @@ GEM
|
||||
libui (0.2.0-x64-mingw-ucrt)
|
||||
fiddle
|
||||
metrics (0.15.0)
|
||||
ocran (1.3.17)
|
||||
fiddle (~> 1.0)
|
||||
protocol-hpack (1.5.1)
|
||||
protocol-http (0.58.0)
|
||||
protocol-http1 (0.36.0)
|
||||
protocol-http (~> 0.58)
|
||||
protocol-http2 (0.23.0)
|
||||
protocol-http2 (0.24.0)
|
||||
protocol-hpack (~> 1.4)
|
||||
protocol-http (~> 0.47)
|
||||
protocol-rack (0.21.0)
|
||||
@@ -70,11 +67,6 @@ GEM
|
||||
protocol-http (~> 0.2)
|
||||
rack (3.2.4)
|
||||
rake (13.3.1)
|
||||
releasy (0.2.4)
|
||||
bundler (>= 1.2.1)
|
||||
cri (~> 2.15.0)
|
||||
ocran (~> 1.3.0)
|
||||
rake (>= 0.9.2.2)
|
||||
rexml (3.4.4)
|
||||
rubyzip (3.2.2)
|
||||
sdl2-bindings (0.2.3)
|
||||
@@ -88,19 +80,16 @@ GEM
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw-ucrt
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
async-http
|
||||
async-websocket
|
||||
base64
|
||||
bundler (~> 2.4.3)
|
||||
cyberarm_engine
|
||||
digest-crc
|
||||
ircparser
|
||||
libui
|
||||
ocran
|
||||
rake
|
||||
releasy
|
||||
rexml
|
||||
rubyzip
|
||||
sdl2-bindings
|
||||
@@ -108,4 +97,4 @@ DEPENDENCIES
|
||||
win32-security
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.22
|
||||
2.6.8
|
||||
|
||||
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)
|
||||
@@ -86,15 +83,15 @@ class W3DHub
|
||||
|
||||
# open wwconfig.exe or config.exe for ecw
|
||||
|
||||
if (app_data = installed?(app_id, channel) && W3DHub.unix?)
|
||||
exe = if Store.settings[:wine_prefix]
|
||||
"WINEPREFIX=\"#{Store.settings[:wine_prefix]}\" winecfg"
|
||||
else
|
||||
"winecfg"
|
||||
end
|
||||
return unless (app_data = installed?(app_id, channel) && W3DHub.unix?)
|
||||
|
||||
Process.spawn("#{exe}")
|
||||
end
|
||||
exe = if !Store.settings[:wine_prefix].to_s.empty?
|
||||
"WINEPREFIX=\"#{Store.settings[:wine_prefix]}\" winecfg"
|
||||
else
|
||||
"winecfg"
|
||||
end
|
||||
|
||||
Process.spawn(exe)
|
||||
end
|
||||
|
||||
def repair(app_id, channel)
|
||||
@@ -169,11 +166,16 @@ class W3DHub
|
||||
def wine_command(app_id, channel)
|
||||
return "" if W3DHub.windows?
|
||||
|
||||
if Store.settings[:wine_prefix]
|
||||
"WINEPREFIX=\"#{Store.settings[:wine_prefix]}\" \"#{Store.settings[:wine_command]}\" "
|
||||
else
|
||||
"#{Store.settings[:wine_command]} "
|
||||
end
|
||||
"\"#{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,12 +268,14 @@ 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?
|
||||
run(
|
||||
app_id, channel,
|
||||
"+connect #{server.address}:#{server.port} +netplayername #{username}#{password ? " +password \"#{password}\"" : ""}#{multi ? " +multi" : ""}"
|
||||
)
|
||||
end
|
||||
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" : ""}"
|
||||
)
|
||||
end
|
||||
|
||||
def play_now_server(app_id, channel)
|
||||
@@ -251,9 +285,14 @@ class W3DHub
|
||||
|
||||
server_options = Store.server_list.select do |server|
|
||||
server.game == app_id &&
|
||||
server.channel == channel &&
|
||||
!server.status.password &&
|
||||
server.status.player_count < server.status.max_players
|
||||
server.channel == channel &&
|
||||
!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
|
||||
@@ -261,7 +300,7 @@ class W3DHub
|
||||
# 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)
|
||||
|
||||
@@ -183,7 +183,8 @@ class W3DHub
|
||||
# Wine present?
|
||||
if W3DHub.unix?
|
||||
wine_present = W3DHub.command("which #{Store.settings[:wine_command]}")
|
||||
fail!("FAIL FAST: `which #{Store.settings[:wine_command]}` command failed, wine is not installed. Will be unable to create prefixes or launch games.") unless wine_present
|
||||
fail!("FAIL FAST: `which #{Store.settings[:wine_command]}` command failed, wine is not installed.\n\n"\
|
||||
"Will be unable to launch game.\n\nCheck wine options in launcher's settings.") unless wine_present
|
||||
end
|
||||
end
|
||||
|
||||
@@ -745,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)
|
||||
|
||||
@@ -764,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
|
||||
|
||||
29
lib/cache.rb
29
lib/cache.rb
@@ -75,24 +75,24 @@ 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?
|
||||
total_bytes = package.size
|
||||
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|
|
||||
file.write(chunk)
|
||||
response.each do |chunk|
|
||||
file.write(chunk)
|
||||
|
||||
block.call(chunk, total_bytes - file.pos, total_bytes)
|
||||
end
|
||||
|
||||
result = true
|
||||
block.call(chunk, total_bytes - file.pos, total_bytes)
|
||||
end
|
||||
|
||||
result = true
|
||||
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
|
||||
|
||||
@@ -140,18 +140,16 @@ class W3DHub
|
||||
end
|
||||
|
||||
status.zero?
|
||||
else
|
||||
if block
|
||||
IO.popen(command, "r") do |io|
|
||||
io.each_line do |line|
|
||||
block&.call(line)
|
||||
end
|
||||
elsif block
|
||||
IO.popen(command, "r") do |io|
|
||||
io.each_line do |line|
|
||||
block&.call(line)
|
||||
end
|
||||
|
||||
$CHILD_STATUS.success?
|
||||
else
|
||||
system(command)
|
||||
end
|
||||
|
||||
$CHILD_STATUS.success?
|
||||
else
|
||||
system(command)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -159,23 +157,26 @@ class W3DHub
|
||||
File.expand_path("~")
|
||||
end
|
||||
|
||||
def self.ask_file(title: "Open File", filter: "*game*.exe")
|
||||
def self.ask_file(title: "Open File", filter: "*game*.exe", filters: [])
|
||||
filters << filter if filters.empty?
|
||||
|
||||
if W3DHub.unix?
|
||||
# search for command
|
||||
cmds = %w{ zenity matedialog qarma kdialog }
|
||||
cmds = %w[zenity matedialog qarma kdialog]
|
||||
|
||||
command = cmds.find do |cmd|
|
||||
cmd if system("which #{cmd}")
|
||||
end
|
||||
|
||||
path = case File.basename(command)
|
||||
when "zenity", "matedialog", "qarma"
|
||||
`#{command} --file-selection --title "#{title}" --file-filter "#{filter}"`
|
||||
when "kdialog"
|
||||
`#{command} --title "#{title}" --getopenfilename . "#{filter}"`
|
||||
else
|
||||
raise "No known command found for system file selection dialog!"
|
||||
end
|
||||
when "zenity", "matedialog", "qarma"
|
||||
options = filters.map { |s| format("--file-filter=\"%s\"", s) }.join(" ")
|
||||
`#{command} --file-selection --title \"#{title}\" #{options}`
|
||||
when "kdialog"
|
||||
`#{command} --title "#{title}" --getopenfilename . "#{filters.join(" ")}"`
|
||||
else
|
||||
raise "No known command found for system file selection dialog!"
|
||||
end
|
||||
|
||||
path.strip
|
||||
else
|
||||
@@ -189,20 +190,20 @@ class W3DHub
|
||||
def self.ask_folder(title: "Open Folder")
|
||||
if W3DHub.unix?
|
||||
# search for command
|
||||
cmds = %w{ zenity matedialog qarma kdialog }
|
||||
cmds = %w[zenity matedialog qarma kdialog]
|
||||
|
||||
command = cmds.find do |cmd|
|
||||
cmd if system("which #{cmd}")
|
||||
end
|
||||
|
||||
path = case File.basename(command)
|
||||
when "zenity", "matedialog", "qarma"
|
||||
`#{command} --file-selection --directory --title "#{title}"`
|
||||
when "kdialog"
|
||||
`#{command} --title "#{title}" --getexistingdirectory #{Dir.home}"`
|
||||
else
|
||||
raise "No known command found for system file selection dialog!"
|
||||
end
|
||||
when "zenity", "matedialog", "qarma"
|
||||
`#{command} --file-selection --directory --title "#{title}"`
|
||||
when "kdialog"
|
||||
`#{command} --title "#{title}" --getexistingdirectory #{Dir.home}"`
|
||||
else
|
||||
raise "No known command found for system file selection dialog!"
|
||||
end
|
||||
|
||||
path.strip
|
||||
else
|
||||
|
||||
@@ -278,7 +278,7 @@ class W3DHub
|
||||
end
|
||||
|
||||
# Game Events
|
||||
@game_events_container = flow(width: 1.0, height: 128, padding: 8, visible: false) do
|
||||
@game_events_container = stack(width: 1.0, height: 128, padding: 8, scroll: true, visible: false) do
|
||||
end
|
||||
|
||||
# Game News
|
||||
@@ -513,15 +513,15 @@ class W3DHub
|
||||
@game_events_container.show unless events.empty?
|
||||
@game_events_container.hide if events.empty?
|
||||
|
||||
@game_events_container.clear do
|
||||
events.flatten.each do |event|
|
||||
stack(fill: true, height: 1.0, margin_left: 8, margin_right: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
|
||||
background 0x44_000000
|
||||
return unless (event = events.flatten.first)
|
||||
|
||||
title event.title, width: 1.0, text_align: :center
|
||||
title event.start_time.strftime("%A"), width: 1.0, text_align: :center
|
||||
caption event.start_time.strftime("%B %e, %Y %l:%M %p"), width: 1.0, text_align: :center
|
||||
end
|
||||
@game_events_container.clear do
|
||||
stack(width: 1.0, fill: true, margin_left: 8, margin_right: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
|
||||
background 0x44_000000
|
||||
|
||||
title event.title, width: 1.0, text_align: :center
|
||||
title event.start_time.strftime("%A"), width: 1.0, text_align: :center
|
||||
caption event.start_time.strftime("%B %e, %Y %l:%M %p"), width: 1.0, text_align: :center
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,47 +7,74 @@ class W3DHub
|
||||
background 0xaa_252525
|
||||
|
||||
stack(width: 1.0, fill: true, max_width: 720, h_align: :center, scroll: true) do
|
||||
stack(width: 1.0, height: 112) do
|
||||
tagline "Launcher Language"
|
||||
@language_menu = list_box items: I18n.available_locales.map { |l| expand_language_code(l.to_s) }, choose: expand_language_code(Store.settings[:language]), width: 1.0, margin_left: 16
|
||||
para "Select the UI language you'd like to use in the W3D Hub Launcher.", margin_left: 16
|
||||
tagline "Launcher Language"
|
||||
@language_menu = list_box items: I18n.available_locales.map { |l| expand_language_code(l.to_s) }, choose: expand_language_code(Store.settings[:language]), width: 1.0, margin_left: 16
|
||||
para "Select the UI language you'd like to use in the W3D Hub Launcher.", margin_left: 16
|
||||
|
||||
|
||||
tagline "Launcher Directories", margin_top: 16
|
||||
caption "Applications Install Directory", margin_left: 16
|
||||
flow(width: 1.0, margin_left: 16) do
|
||||
@app_install_dir_input = edit_line Store.settings[:app_install_dir], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for applications install directory" do
|
||||
path = W3DHub.ask_folder
|
||||
@app_install_dir_input.value = path unless path.empty?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
stack(width: 1.0, height: 200, margin_top: 16) do
|
||||
tagline "Launcher Directories"
|
||||
caption "Applications Install Directory", margin_left: 16
|
||||
flow(width: 1.0, fill: true, margin_left: 16) do
|
||||
@app_install_dir_input = edit_line Store.settings[:app_install_dir], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for applications install directory" do
|
||||
path = W3DHub.ask_folder
|
||||
@app_install_dir_input.value = path unless path.empty?
|
||||
end
|
||||
end
|
||||
|
||||
caption "Package Cache Directory", margin_left: 16, margin_top: 16
|
||||
flow(width: 1.0, fill: true, margin_left: 16) do
|
||||
@package_cache_dir_input = edit_line Store.settings[:package_cache_dir], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for package cache directory" do
|
||||
path = W3DHub.ask_folder
|
||||
@package_cache_dir_input.value = path unless path.empty?
|
||||
end
|
||||
caption "Package Cache Directory", margin_left: 16, margin_top: 16
|
||||
flow(width: 1.0, margin_left: 16) do
|
||||
@package_cache_dir_input = edit_line Store.settings[:package_cache_dir], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for package cache directory" do
|
||||
path = W3DHub.ask_folder
|
||||
@package_cache_dir_input.value = path unless path.empty?
|
||||
end
|
||||
end
|
||||
|
||||
if W3DHub.unix?
|
||||
stack(width: 1.0, height: 224, margin_top: 16) do
|
||||
tagline "Wine - Windows compatibility layer"
|
||||
caption "Wine Command", margin_left: 16
|
||||
@wine_command_input = edit_line Store.settings[:wine_command], width: 1.0, margin_left: 16
|
||||
para "Command to use to for Windows compatiblity layer.", margin_left: 16
|
||||
|
||||
caption "Wine Prefix", margin_left: 16, margin_top: 16
|
||||
flow(width: 1.0, height: 48, margin_left: 16) do
|
||||
@wine_prefix_toggle = toggle_button checked: Store.settings[:wine_prefix], enabled: false
|
||||
para "Whether each game gets its own prefix. Uses global/default prefix by default."
|
||||
tagline "Wine - Windows compatibility layer", margin_top: 16
|
||||
caption "Wine Command", margin_left: 16
|
||||
flow(width: 1.0, margin_left: 16) do
|
||||
@wine_command_input = edit_line Store.settings[:wine_command], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for wine executable" do
|
||||
path = W3DHub.ask_file(filters: %w[wine proton])
|
||||
@wine_command_input.value = path unless path.empty?
|
||||
end
|
||||
end
|
||||
para "Command to use to for Windows compatiblity layer.", margin_left: 16
|
||||
|
||||
caption "Wine Prefix", margin_left: 16, margin_top: 16
|
||||
flow(width: 1.0, margin_left: 16) do
|
||||
@wine_prefix_input = edit_line Store.settings[:wine_prefix], fill: true
|
||||
button "Browse...", width: 128, tip: "Browse for wine prefix directory" do
|
||||
path = W3DHub.ask_folder
|
||||
@wine_prefix_input.value = path unless path.empty?
|
||||
end
|
||||
end
|
||||
para "Leave empty to use default global prefix.", margin_left: 16
|
||||
|
||||
link "Wiki: Getting Started With Wine", tip: "https://github.com/cyberarm/w3d_hub_linux_launcher/wiki/Getting-Started-With-Wine", margin_top: 16, margin_left: 16, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://github.com/cyberarm/w3d_hub_linux_launcher/wiki/Getting-Started-With-Wine")
|
||||
end
|
||||
|
||||
# TODO: support winetricks stuff
|
||||
# tagline "Winetricks", margin_top: 16
|
||||
# caption "Winetricks Command", margin_left: 16
|
||||
# flow(width: 1.0, margin_left: 16) do
|
||||
# @winetricks_command_input = edit_line Store.settings[:winetricks_command], fill: true, enabled: false
|
||||
# button "Browse...", width: 128, tip: "Browse for winetricks executable", enabled: false do
|
||||
# path = W3DHub.ask_file(filters: %w[winetricks protontricks])
|
||||
# @winetricks_command_input.value = path unless path.empty?
|
||||
# end
|
||||
# end
|
||||
|
||||
# caption "Fixups", margin_left: 16, margin_top: 16
|
||||
# button "Install d3dcompiler_47", margin_left: 16, enabled: false
|
||||
# para "Fixes games instantly crashing at startup due to not being able to compile shaders.", margin_left: 16
|
||||
|
||||
# button "Install DXVK", margin_left: 16, margin_top: 16, enabled: false
|
||||
# para "Use Vulkan-based DirectX translation layers.", margin_left: 16
|
||||
# para "WARNING: Games will stop working if your hardware does not support Vulkan!", margin_left: 16
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,10 +82,9 @@ class W3DHub
|
||||
button "Save", width: 1.0 do
|
||||
save_settings!
|
||||
end
|
||||
|
||||
flow(fill: true)
|
||||
|
||||
end
|
||||
|
||||
button("Clear package cache: #{W3DHub.format_size(Dir.glob("#{Store.settings[:package_cache_dir]}/**/**").map { |f| File.file?(f) ? File.size(f) : 0}.sum)}", tip: "Purge #{Store.settings[:package_cache_dir]}", **DANGEROUS_BUTTON) do |btn|
|
||||
logger.info(LOG_TAG) { "Purging cache (#{Store.settings[:package_cache_dir]})..." }
|
||||
FileUtils.remove_dir(Store.settings[:package_cache_dir], force: true)
|
||||
@@ -106,7 +132,9 @@ class W3DHub
|
||||
Store.settings[:package_cache_dir] = @package_cache_dir_input.value
|
||||
|
||||
Store.settings[:wine_command] = @wine_command_input.value
|
||||
Store.settings[:wine_prefix] = @wine_prefix_toggle.value
|
||||
Store.settings[:wine_prefix] = @wine_prefix_input.value
|
||||
|
||||
Store.settings[:winetricks_command] = @winetricks_command_input.value if @winetricks_command_input
|
||||
|
||||
Store.settings.save_settings
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ class W3DHub
|
||||
package_cache_dir: default_package_cache_dir,
|
||||
parallel_downloads: 4,
|
||||
wine_command: "wine",
|
||||
create_wine_prefixes: true,
|
||||
wine_prefix: "",
|
||||
winetricks_command: "winetricks",
|
||||
allow_diagnostic_reports: false,
|
||||
server_list_username: "",
|
||||
server_list_filters: {},
|
||||
@@ -66,6 +67,14 @@ class W3DHub
|
||||
|
||||
def load_settings
|
||||
@settings = JSON.parse(File.read(SETTINGS_FILE_PATH), symbolize_names: true)
|
||||
|
||||
# FIXUPS
|
||||
# FOR: v0.9.0
|
||||
@settings.delete(:create_wine_prefixes)
|
||||
@settings[:wine_prefix] ||= ""
|
||||
@settings[:winetricks_command] ||= "winetricks"
|
||||
|
||||
@settings
|
||||
end
|
||||
|
||||
def save_settings
|
||||
|
||||
@@ -10,7 +10,7 @@ class W3DHub
|
||||
flow(width: 1.0, height: 1.0, background_image: "#{GAME_ROOT_PATH}/media/banners/background.png", background_image_color: 0xff_525252, background_image_mode: :fill) do
|
||||
flow(fill: true)
|
||||
|
||||
@card_container = stack(width: 1.0, max_width: MAX_PAGE_WIDTH, height: 1.0, max_height: 720, margin: 128, padding: 16) do
|
||||
@card_container = stack(width: 1.0, max_width: MAX_PAGE_WIDTH, height: 1.0, max_height: 720, margin: 64, v_align: :center, h_align: :center, padding: 16) do
|
||||
background 0xaa_353535
|
||||
end
|
||||
|
||||
@@ -24,9 +24,12 @@ class W3DHub
|
||||
|
||||
def card_welcome
|
||||
stack(width: 1.0, fill: true) do
|
||||
banner "Welcome", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_000000
|
||||
banner "Welcome", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_0074e0
|
||||
title "Welcome to the #{I18n.t(:app_name_simple)}"
|
||||
caption "The #{I18n.t(:app_name_simple)} is a one-stop shop for your W3D gaming needs, providing game downloads, automatic updating, an integrated server browser, and centralized management of in-game options.", width: 1.0, margin_left: 32
|
||||
caption "The #{I18n.t(:app_name_simple)} is a one-stop shop for your W3D gaming needs, providing game downloads, "\
|
||||
"automatic updating, an integrated server browser, and centralized management of in-game options.", width: 1.0, margin_left: 32
|
||||
|
||||
image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 256
|
||||
end
|
||||
|
||||
flow(width: 1.0, height: 46) do
|
||||
@@ -44,14 +47,25 @@ class W3DHub
|
||||
|
||||
def card_getting_started
|
||||
stack(width: 1.0, fill: true) do
|
||||
banner "Getting Started", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_000000
|
||||
title "Import C&C Renegade"
|
||||
caption "You can import your installed copy of Renegade if it wasn't automatically imported from the Games tab. If you need to procure a copy of Renegade, EA's Origin Store has the Command & Conquer The Ultimate Collection available. We cannot provide Renegade for installation.", width: 1.0, margin_left: 32
|
||||
banner "Getting Started", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_0074e0
|
||||
title "Import Command & Conquer: Renegade"
|
||||
caption "You can import your installed copy of Renegade if it wasn't automatically imported from the Games tab.\n"\
|
||||
"If you need to procure a copy of Renegade, Both Steam and the EA App have the Command & Conquer The Ultimate Collection available for purchase. "\
|
||||
"We cannot provide Renegade for installation.", width: 1.0, margin_left: 32
|
||||
|
||||
stack(width: 1.0, height: 2, background: 0x88_ffffff)
|
||||
stack(width: 1.0, height: 2, background: 0xff_0074e0, margin_top: 16, margin_bottom: 16)
|
||||
|
||||
title "Install one of our standalone games"
|
||||
caption "Browse our selection of games from the left panel of the Games tab.\n• Interim Apex - Renegade but with hundreds of vehicles and characters.\n• Red Alert: A Path Beyond - DESCRIPTION\n• Tiberian Sun: Reborn - DESCRIPTION\n\nAnd more... Check out the left panel on the Games tab.", width: 1.0, margin_left: 32
|
||||
stack(width: 1.0, fill: true, margin_left: 32) do
|
||||
tagline "Interim Apex"
|
||||
caption "An expanded boots on the ground conflict set after the advent of Tiberian Dawn and the inter-war period between Tiberian Dawn and Tiberian Sun.", margin_left: 16
|
||||
tagline "Red Alert 2: Apocalypse Rising"
|
||||
caption "A multiplayer first-and-third-person shooter set in the vibrant universe of Command & Conquer: Red Alert 2. ", margin_left: 16
|
||||
tagline "Tiberian Sun: Reborn"
|
||||
caption "A standalone first-person shooter set in the Tiberian Sun universe.", margin_left: 16
|
||||
para ""
|
||||
caption "And more games! See them all on the Games tab."
|
||||
end
|
||||
end
|
||||
|
||||
flow(width: 1.0, height: 46) do
|
||||
@@ -66,25 +80,22 @@ class W3DHub
|
||||
end
|
||||
|
||||
button "Next >" do
|
||||
@card_container.clear { card_communitiy }
|
||||
@card_container.clear { W3DHub.unix? ? card_wine : card_community }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def card_communitiy
|
||||
def card_wine
|
||||
stack(width: 1.0, fill: true) do
|
||||
banner "W3D Hub Community", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_000000
|
||||
title "Forums"
|
||||
caption "Join our forum community", margin_left: 32
|
||||
|
||||
title "Facebook"
|
||||
caption "Like us on Facebook", margin_left: 32
|
||||
|
||||
title "Discord"
|
||||
caption "Join our Discord community server", margin_left: 32
|
||||
|
||||
title "YouTube"
|
||||
caption "Subscribe to our YouTube channel", margin_left: 32
|
||||
banner "Wine - Windows compatibility layer", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_0074e0
|
||||
stack(width: 1.0, fill: true, margin_left: 32) do
|
||||
title "Got Wine?"
|
||||
caption "The launcher requires a windows compatibility tool like wine in order to run the games.", margin_left: 32
|
||||
caption "Install wine and winetricks through your distribution's package manager or use a wine manager like Bottles.", margin_left: 32
|
||||
link "See most up to date instructions on the wiki.", tip: "https://github.com/cyberarm/w3d_hub_linux_launcher/wiki/Getting-Started-With-Wine", margin_top: 16, margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://github.com/cyberarm/w3d_hub_linux_launcher/wiki/Getting-Started-With-Wine")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flow(width: 1.0, height: 46) do
|
||||
@@ -92,6 +103,52 @@ class W3DHub
|
||||
button "< Back" do
|
||||
@card_container.clear { card_getting_started }
|
||||
end
|
||||
|
||||
link "Skip", border_color_bottom: 0xff_777777, margin_left: 16 do
|
||||
pop_state
|
||||
end
|
||||
end
|
||||
|
||||
button "Next >" do
|
||||
@card_container.clear { card_community }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def card_community
|
||||
stack(width: 1.0, fill: true) do
|
||||
banner "W3D Hub Community", width: 1.0, border_thickness_bottom: 4, border_color_bottom: 0xff_0074e0
|
||||
title "W3D Hub"
|
||||
link "Visit website", tip: "https://w3dhub.com", margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://w3dhub.com")
|
||||
end
|
||||
|
||||
title "Forum"
|
||||
link "Join our forum community", tip: "https://w3dhub.com/forum", margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://w3dhub.com/forum")
|
||||
end
|
||||
|
||||
title "Facebook"
|
||||
link "Like us on Facebook", tip: "https://www.facebook.com/w3dhub/", margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://www.facebook.com/w3dhub/")
|
||||
end
|
||||
|
||||
title "Discord"
|
||||
link "Join our Discord community server", tip: "https://discord.gg/jMmmRa2", margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://discord.gg/jMmmRa2")
|
||||
end
|
||||
|
||||
title "YouTube"
|
||||
link "Subscribe to our YouTube channel", tip: "https://www.youtube.com/@w3dhub-official", margin_left: 32, border_color_bottom: 0xff_777777 do
|
||||
W3DHub.url("https://www.youtube.com/@w3dhub-official")
|
||||
end
|
||||
end
|
||||
|
||||
flow(width: 1.0, height: 46) do
|
||||
flow(fill: true, height: 1.0) do
|
||||
button "< Back" do
|
||||
@card_container.clear { W3DHub.unix? ? card_wine : card_getting_started }
|
||||
end
|
||||
end
|
||||
|
||||
button "Done" do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class W3DHub
|
||||
DIR_NAME = "W3DHubAlt".freeze
|
||||
VERSION = "0.9.0".freeze
|
||||
VERSION = "0.9.2".freeze
|
||||
end
|
||||
|
||||
@@ -1,68 +1,75 @@
|
||||
class W3DHub
|
||||
class WebSocketClient
|
||||
def initialize
|
||||
@errored = nil
|
||||
@connection = nil
|
||||
class WebSocketClient
|
||||
def initialize
|
||||
@errored = nil
|
||||
@connection = nil
|
||||
|
||||
@events = {
|
||||
open: nil,
|
||||
message: nil,
|
||||
close: nil,
|
||||
error: nil
|
||||
}
|
||||
end
|
||||
@events = {
|
||||
open: nil,
|
||||
message: nil,
|
||||
close: nil,
|
||||
error: nil
|
||||
}
|
||||
end
|
||||
|
||||
def connect(endpoint, headers: nil, &block)
|
||||
yield(self)
|
||||
def connect(endpoint, headers: nil, &block)
|
||||
yield(self)
|
||||
|
||||
Sync do |task|
|
||||
endpoint = Async::HTTP::Endpoint.parse(endpoint, alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
|
||||
Sync do |task|
|
||||
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
|
||||
)
|
||||
|
||||
Async::WebSocket::Client.connect(endpoint, headers: headers) do |connection|
|
||||
@connection = connection
|
||||
endpoint = Async::HTTP::Endpoint.parse(endpoint, alpn_protocols: Async::HTTP::Protocol::HTTP11.names, ssl_context: ssl_context)
|
||||
|
||||
@events[:open]&.call
|
||||
Async::WebSocket::Client.connect(endpoint, headers: headers) do |connection|
|
||||
@connection = connection
|
||||
|
||||
while message = connection.read
|
||||
@events[:message].call(message)
|
||||
@events[:open]&.call
|
||||
|
||||
while message = connection.read
|
||||
@events[:message].call(message)
|
||||
end
|
||||
# FIXME: Don't rescue for all ta errors?
|
||||
rescue => error
|
||||
@errored = true
|
||||
@events[:error]&.call(error)
|
||||
ensure
|
||||
@events[:close]&.call unless @errored
|
||||
@connection = nil
|
||||
@errored = false
|
||||
end
|
||||
# FIXME: Don't rescue for all ta errors?
|
||||
rescue => error
|
||||
@errored = true
|
||||
@events[:error]&.call(error)
|
||||
ensure
|
||||
@events[:close]&.call unless @errored
|
||||
@connection = nil
|
||||
@errored = false
|
||||
end
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def on(event, &block)
|
||||
raise "Event must be a symbol" unless event.is_a?(Symbol)
|
||||
raise "Unknown event: #{event.inspect}" unless @events.keys.include?(event)
|
||||
raise "No block given for #{event.inspect}" unless block_given?
|
||||
def on(event, &block)
|
||||
raise "Event must be a symbol" unless event.is_a?(Symbol)
|
||||
raise "Unknown event: #{event.inspect}" unless @events.keys.include?(event)
|
||||
raise "No block given for #{event.inspect}" unless block_given?
|
||||
|
||||
@events[event] = block
|
||||
end
|
||||
@events[event] = block
|
||||
end
|
||||
|
||||
def send(data, type: :text)
|
||||
@connection&.write(data)
|
||||
@connection&.flush
|
||||
end
|
||||
def send(data, type: :text)
|
||||
@connection&.write(data)
|
||||
@connection&.flush
|
||||
end
|
||||
|
||||
def close
|
||||
@connection&.close
|
||||
end
|
||||
def close
|
||||
@connection&.close
|
||||
end
|
||||
|
||||
def open?
|
||||
!closed?
|
||||
end
|
||||
def open?
|
||||
!closed?
|
||||
end
|
||||
|
||||
def closed?
|
||||
@connection&.closed?
|
||||
end
|
||||
end
|
||||
def closed?
|
||||
@connection&.closed?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,8 +17,8 @@ class W3DHub
|
||||
end
|
||||
|
||||
# push_state(W3DHub::States::DemoInputDelay)
|
||||
# push_state(W3DHub::States::Welcome)
|
||||
push_state(W3DHub::States::Boot)
|
||||
push_state(W3DHub::States::Welcome) unless File.exist?(SETTINGS_FILE_PATH)
|
||||
# push_state(W3DHub::States::DirectConnectDialog)
|
||||
# push_state(W3DHub::Asterisk::States::IRCProfileForm)
|
||||
end
|
||||
|
||||
@@ -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