From ec81959947a6a97b3f190f80cbb8ae16f1448d14 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Fri, 19 Nov 2021 11:09:14 -0600 Subject: [PATCH] Interim Apex can now be installed and launched (on Windows) --- lib/application_manager.rb | 29 +++++++- lib/application_manager/task.rb | 79 +++++++++++++++++++--- lib/application_manager/tasks/installer.rb | 4 +- lib/cache.rb | 10 ++- lib/common.rb | 20 ++++++ lib/pages/games.rb | 37 +++++----- lib/settings.rb | 42 ++++-------- lib/states/message_dialog.rb | 16 +++-- w3dhub.rb | 2 +- 9 files changed, 173 insertions(+), 66 deletions(-) diff --git a/lib/application_manager.rb b/lib/application_manager.rb index 80287b5..29a4422 100644 --- a/lib/application_manager.rb +++ b/lib/application_manager.rb @@ -1,5 +1,7 @@ class W3DHub class ApplicationManager + include CyberarmEngine::Common + def initialize @tasks = [] # :installer, :importer, :repairer, :uninstaller end @@ -75,8 +77,33 @@ class W3DHub end end + def run(app_id, channel, *args) + if (app_data = installed?(app_id, channel)) + Process.spawn(app_data[:install_path], *args) + end + end + + def installed!(task) + # install_dir + # installed_version + # installPath # game executable + # wine_prefix # optional + + install_directory = Cache.install_path(task.application, task.channel) + application_data = { + install_directory: install_directory, + installed_version: task.channel.current_version, + install_path: "#{install_directory}/game.exe", + wine_prefix: task.wine_prefix + } + + window.settings[:games] ||= {} + window.settings[:games,][:"#{task.app_id}_#{task.release_channel}"] = application_data + window.settings.save_settings + end + def installed?(app_id, channel) - false + window.settings[:games, :"#{app_id}_#{channel}"] end def installing?(app_id, channel) diff --git a/lib/application_manager/task.rb b/lib/application_manager/task.rb index 62144bd..f3bfd00 100644 --- a/lib/application_manager/task.rb +++ b/lib/application_manager/task.rb @@ -5,7 +5,7 @@ class W3DHub attr_reader :app_id, :release_channel, :application, :channel, :total_bytes_to_download, :bytes_downloaded, :packages_to_download, - :manifests + :manifests, :packages, :files, :wine_prefix def initialize(app_id, release_channel) @app_id = app_id @@ -14,13 +14,17 @@ class W3DHub @task_state = :not_started # :not_started, :running, :paused, :halted, :complete, :failed @application = window.applications.games.find { |g| g.id == app_id } - @channel = @application.channels.find { |c| c.name == release_channel } + @channel = @application.channels.find { |c| c.id == release_channel } @packages_to_download = [] @total_bytes_to_download = -1 @bytes_downloaded = -1 @manifests = [] + @files = {} + @packages = [] + + @wine_prefix = nil setup end @@ -88,7 +92,19 @@ class W3DHub def fail!(reason = "") @task_state = :failed - @task_failure_reason = "Failed: #{reason}" + @task_failure_reason = reason.to_s + end + + # Quick checks before network and computational work starts + def fail_fast + # tar present? + tar_present = system("tar --help") + fail!("FAIL FAST: `tar --help` command failed, tar is not installed. Will be unable to unpack packages.") unless tar_present + + if W3DHub.unix? + wine_present = system("which #{window.settings[:wine_command]}") + fail!("FAIL FAST: `which wine` command failed, wine is not installed. Will be unable to create prefixes or launch games.") unless wine_prefix + end end def run_on_main_thread(block) @@ -154,6 +170,8 @@ class W3DHub puts "#{manifest.game}-#{manifest.type}: #{manifest.version} (#{manifest.base_version})" manifest.files.each do |file| + @files["#{file.name}:#{manifest.version}"] = file + next if file.removed? # No package data if file.patch? @@ -193,14 +211,13 @@ class W3DHub package_details = Api.package_details(hashes) if package_details + @packages = [package_details].flatten @packages_to_download = [] update_application_taskbar("Downloading #{@application.name}...", "Verifying local packages...", 0.0) package_details.each do |pkg| - unless verify_package(pkg) - @packages_to_download << pkg - end + @packages_to_download << pkg unless verify_package(pkg) end @total_bytes_to_download = @packages_to_download.sum { |pkg| pkg.size - pkg.custom_partially_valid_at_bytes } @@ -238,18 +255,62 @@ class W3DHub end def unpack_packages(packages) + path = Cache.install_path(@application, @channel) + puts "Unpacking packages in '#{path}'..." + Cache.create_directories(path) + + packages.each do |package| + puts " #{package.name}:#{package.version}" + package_path = Cache.package_path(package.category, package.subcategory, "#{package.name}.zip", package.version) + + puts " Running tar command: #{"tar -xf #{package_path} -C #{path}"}" + status = system("tar -xf #{package_path} -C #{path}") + if status + else + puts "COMMAND FAILED!" + end + end end def create_wine_prefix + if W3DHub.unix? + # TODO: create a wine prefix if configured + end end def install_dependencies(packages) end def mark_application_installed + window.application_manager.installed!(self) + puts "#{@app_id} has been installed." end + def verify_files(_) + # TODO: Check extracted game files or just re-extract everything? + + # Zip.on_exists_proc = true # Overwrite existing files + # zip_file = Zip::InputStream.new(File.open(package_path)) + # while (entry = zip_file.get_next_entry) + + # extract_path = "#{path}/#{entry.name}" + # manifest_file = @files["#{entry.name}:#{package.version}"] + + # if manifest_file.removed? + # puts " !skipped: #{extract_path}" + # else + # target_file_exits = File.exist?(extract_path) + # next if target_file_exits # && Digest::SHA256.new.hexdigest(File.read(extract_path)) == manifest_file.checksum + + # puts " #{path}/#{extract_path}" + # Cache.create_directories(extract_path) unless target_file_exits + + # entry.extract(extract_path) + # end + # end + end + ############# # Functions # ############# @@ -287,7 +348,7 @@ class W3DHub digest = Digest::SHA256.new path = Cache.package_path(package.category, package.subcategory, package.name, package.version) - return false unless File.exists?(path) + return false unless File.exist?(path) file_size = File.size(path) puts " File size: #{file_size}" @@ -300,7 +361,7 @@ class W3DHub read_length = chunk_size read_length = file_size - chunk_start if chunk_start + chunk_size > file_size - break if file_size - chunk_start < 0 + break if (file_size - chunk_start).negative? f.seek(chunk_start) @@ -327,4 +388,4 @@ class W3DHub end end end -end \ No newline at end of file +end diff --git a/lib/application_manager/tasks/installer.rb b/lib/application_manager/tasks/installer.rb index 8af379e..ffbd05f 100644 --- a/lib/application_manager/tasks/installer.rb +++ b/lib/application_manager/tasks/installer.rb @@ -6,6 +6,9 @@ class W3DHub end def execute_task + fail_fast + return false if failed? + update_application_taskbar("Downloading #{@application.name}...", "Fetching manifests...", 0.0) manifests = fetch_manifests return false if failed? @@ -33,7 +36,6 @@ class W3DHub sleep 1 update_application_taskbar("Installing #{@application.name}...", "Installing dependencies...", 0.0) - install_dependencies(packages) return false if failed? sleep 1 diff --git a/lib/cache.rb b/lib/cache.rb index 5877b23..7bb6966 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -27,8 +27,8 @@ class W3DHub end end - def self.create_directories(path) - target_directory = File.dirname(path) + def self.create_directories(path, is_directory = false) + target_directory = is_directory ? path : File.dirname(path) FileUtils.mkdir_p(target_directory) unless Dir.exist?(target_directory) end @@ -39,6 +39,12 @@ class W3DHub "#{package_cache_dir}/#{category}/#{subcategory}/#{version}/#{name}.package" end + def self.install_path(application, channel) + app_install_dir = $window.settings[:app_install_dir] + + "#{app_install_dir}/#{application.category}/#{application.id}/#{channel.id}" + end + # Download a W3D Hub package def self.fetch_package(package, block) path = package_path(package.category, package.subcategory, package.name, package.version) diff --git a/lib/common.rb b/lib/common.rb index 7e3bb93..d3d8ee8 100644 --- a/lib/common.rb +++ b/lib/common.rb @@ -15,4 +15,24 @@ class W3DHub def self.format_size_number(i) format("%0.2f", i) end + + def self.windows? + RbConfig::CONFIG["host_os"] =~ /(mingw|mswin|windows)/i + end + + def self.mac? + RbConfig::CONFIG["host_os"] =~ /(darwin|mac os)/i + end + + def self.linux? + RbConfig::CONFIG["host_os"] =~ /(linux|bsd|aix|solaris)/i + end + + def self.unix? + linux? || mac? + end + + def self.home_directory + File.expand_path("~") + end end diff --git a/lib/pages/games.rb b/lib/pages/games.rb index a08f2b7..35c1442 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -43,7 +43,7 @@ class W3DHub self if hit?(x, y) end - game_button.subscribe(:clicked_left_mouse_button) do |e| + game_button.subscribe(:clicked_left_mouse_button) do populate_game_page(game, game.channels.first) populate_games_list end @@ -85,16 +85,16 @@ class W3DHub # end # end - if window.application_manager.installed?(game.id, channel.name) + if window.application_manager.installed?(game.id, channel.id) Hash.new.tap { |hash| - hash["Game Settings"] = { icon: "gear", block: proc { window.application_manager.settings(game.id, channel.name) } } + hash["Game Settings"] = { icon: "gear", block: proc { window.application_manager.settings(game.id, channel.id) } } if game.id != "ren" - hash["Repair Installation"] = { icon: "wrench", block: proc { window.application_manager.repair(game.id, channel.name) } } - hash["Uninstall"] = { icon: "trashCan", block: proc { window.application_manager.uninstall(game.id, channel.name) } } + hash["Repair Installation"] = { icon: "wrench", block: proc { window.application_manager.repair(game.id, channel.id) } } + hash["Uninstall"] = { icon: "trashCan", block: proc { window.application_manager.uninstall(game.id, channel.id) } } end - hash["Install Folder"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.name, :installation) } } - hash["User Data Folder"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.name, :user_data) } } - hash["View Screenshots"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.name, :screenshots) } } + hash["Install Folder"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.id, :installation) } } + hash["User Data Folder"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.id, :user_data) } } + hash["View Screenshots"] = { icon: nil, block: proc { window.application_manager.show_folder(game.id, channel.id, :screenshots) } } }.each do |key, hash| flow(width: 1.0, height: 22, margin_bottom: 8) do image "#{GAME_ROOT_PATH}/media/ui_icons/#{hash[:icon]}.png", width: 0.11 if hash[:icon] @@ -126,25 +126,24 @@ class W3DHub flow(width: 1.0, height: 0.08) do # background 0xff_551100 - # TODO: Determine if game is installed or not and show apporpiante options ["Play Now" and "Single Player", "Install" and "Import"] - # game.play_items.each do |item| - # button "#{item.label}", margin_left: 24 do - # item.block&.call(game) - # end - # end if window.application_manager.installed?(game.id, channel.id) - button "Play Now", margin_left: 24 - button "Single Player", margin_left: 24 + button "Play Now", margin_left: 24 do + window.application_manager.run(game.id, channel.id) + end + + button "Single Player", margin_left: 24 do + window.application_manager.run(game.id, channel.id) + end else unless game.id == "ren" - button "Install", margin_left: 24, enabled: !window.application_manager.task?(:installer, game.id, channel.name) do |button| + button "Install", margin_left: 24, enabled: !window.application_manager.task?(:installer, game.id, channel.id) do |button| button.enabled = false - window.application_manager.install(game.id, channel.name) + window.application_manager.install(game.id, channel.id) end end button "Import", margin_left: 24, enabled: false do - window.application_manager.import(game.id, channel.name, "?") + window.application_manager.import(game.id, channel.id, "?") end end end diff --git a/lib/settings.rb b/lib/settings.rb index f616111..783ce2a 100644 --- a/lib/settings.rb +++ b/lib/settings.rb @@ -5,6 +5,8 @@ class W3DHub language: Gosu.user_languages.first.split("_").first, app_install_dir: default_app_install_dir, package_cache_dir: default_package_cache_dir, + wine_command: "wine", + create_wine_prefixes: true, allow_diagnostic_reports: false, server_list_username: nil, account: {}, @@ -14,45 +16,29 @@ class W3DHub end def self.default_app_install_dir - if windows? - "#{home_directory}/#{W3DHub::DIR_NAME}" - elsif linux? - "#{home_directory}/.local/share/#{W3DHub::DIR_NAME}" - elsif mac? - "#{home_directory}/.local/share/#{W3DHub::DIR_NAME}" + if W3DHub.windows? + "#{W3DHub.home_directory}/#{W3DHub::DIR_NAME}" + elsif W3DHub.linux? + "#{W3DHub.home_directory}/.local/share/#{W3DHub::DIR_NAME}" + elsif W3DHub.mac? + "#{W3DHub.home_directory}/.local/share/#{W3DHub::DIR_NAME}" else raise "Unknown platform: #{RbConfig::CONFIG["host_os"]}" end end def self.default_package_cache_dir - if windows? - "#{home_directory}/#{W3DHub::DIR_NAME}/Launcher/package-cache" - elsif linux? - "#{home_directory}/.local/share/#{W3DHub::DIR_NAME}/package-cache" - elsif mac? - "#{home_directory}/.local/share/#{W3DHub::DIR_NAME}/package-cache" + if W3DHub.windows? + "#{W3DHub.home_directory}/#{W3DHub::DIR_NAME}/Launcher/package-cache" + elsif W3DHub.linux? + "#{W3DHub.home_directory}/.local/share/#{W3DHub::DIR_NAME}/package-cache" + elsif W3DHub.mac? + "#{W3DHub.home_directory}/.local/share/#{W3DHub::DIR_NAME}/package-cache" else raise "Unknown platform: #{RbConfig::CONFIG["host_os"]}" end end - def self.windows? - RbConfig::CONFIG["host_os"] =~ /(mingw|mswin|windows)/i - end - - def self.mac? - RbConfig::CONFIG["host_os"] =~ /(darwin|mac os)/i - end - - def self.linux? - RbConfig::CONFIG["host_os"] =~ /(linux|bsd|aix|solaris)/i - end - - def self.home_directory - File.expand_path("~") - end - def initialize unless File.exist?(SETTINGS_FILE_PATH) @settings = Settings.defaults diff --git a/lib/states/message_dialog.rb b/lib/states/message_dialog.rb index 67078e8..d4ae1df 100644 --- a/lib/states/message_dialog.rb +++ b/lib/states/message_dialog.rb @@ -8,17 +8,23 @@ class W3DHub background 0xee_444444 - stack(width: 1.0, height: 1.0, margin: 128, padding: 8, background: 0xee_222222) do - flow(width: 1.0, height: 0.06) do + stack(width: 1.0, height: 1.0, margin: 128, background: 0xee_222222) do + flow(width: 1.0, height: 0.1, padding: 8) do + background 0x88_000000 + image "#{GAME_ROOT_PATH}/media/ui_icons/warning.png", width: 0.04, align: :center, color: 0xff_ff8800 tagline "#{@options[:title]}", width: 0.9, text_align: :center end - para @options[:message], width: 1.0, height: 0.7, padding: 8 + stack(width: 1.0, height: 0.78, padding: 16) do + para @options[:message], width: 1.0 + end - button "Okay", width: 1.0, margin_top: 64 do - pop_state + stack(width: 1.0, height: 0.1, padding: 8) do + button "Okay", width: 1.0 do + pop_state + end end end end diff --git a/w3dhub.rb b/w3dhub.rb index 5b9ed79..3b95630 100644 --- a/w3dhub.rb +++ b/w3dhub.rb @@ -9,9 +9,9 @@ end require "fileutils" require "digest" require "rexml" -require "zlib" require "launchy" +require "zip" class W3DHub GAME_ROOT_PATH = File.expand_path(".", __dir__)