def extract_by_innoextract(installer, temp_dir): err_msg = "" if shutil.which("innoextract"): cmd = ["innoextract", installer, "-d", temp_dir, "--gog"] stdout, stderr, exitcode = _exe_cmd(cmd) if exitcode not in [0]: err_msg = _("Innoextract extraction failed.") else: # In the case the game is installed in "temp_dir/app" like Zeus + Poseidon (Acropolis) inno_app_dir = os.path.join(temp_dir, "app") if os.path.isdir(inno_app_dir): _mv(inno_app_dir, temp_dir) # In the case the game is installed in "temp_dir/game" like Dragon Age™: Origins - Ultimate Edition inno_game_dir = os.path.join(temp_dir, "game") if os.path.isdir(inno_game_dir): _mv(inno_game_dir, temp_dir) innoextract_unneeded_dirs = [ "__redist", "tmp", "commonappdata", "app", "DirectXpackage", "dotNet35" ] innoextract_unneeded_dirs += [ "MSVC2005", "MSVC2005_x64", "support", "__unpacker", "userdocs", "game" ] for unneeded_dir in innoextract_unneeded_dirs: unneeded_dir_full_path = os.path.join(temp_dir, unneeded_dir) if os.path.isdir(unneeded_dir_full_path): shutil.rmtree(unneeded_dir_full_path) else: err_msg = _("Innoextract not installed.") return err_msg
def __download(self, download_info, finish_func, cancel_to_state): download_success = True GLib.idle_add(self.update_to_state, self.state.QUEUED) Config.set("current_download", self.game.id) # Start the download for all files self.download_list = [] number_of_files = len(download_info['files']) total_file_size = 0 executable_path = None download_files = [] for key, file_info in enumerate(download_info['files']): try: download_url = self.api.get_real_download_link( file_info["downlink"]) except ValueError as e: print(e) GLib.idle_add(self.parent.parent.show_error, _("Download error"), _(str(e))) download_success = False break total_file_size += self.api.get_file_size(file_info["downlink"]) try: # Extract the filename from the download url (filename is between %2F and &token) filename = urllib.parse.unquote( re.search('%2F(((?!%2F).)*)&t', download_url).group(1)) except AttributeError: filename = "{}-{}.bin".format(self.game.get_stripped_name(), key) download_path = os.path.join(self.download_dir, filename) if key == 0: # If key = 0, denote the file as the executable's path executable_path = download_path md5sum = self.api.get_download_file_md5(file_info["downlink"]) if md5sum: self.game.md5sum[os.path.basename(download_path)] = md5sum download = Download( url=download_url, save_location=download_path, finish_func=finish_func if download_path == executable_path else None, progress_func=self.set_progress, cancel_func=lambda: self.__cancel(to_state=cancel_to_state), number=number_of_files - key, out_of_amount=number_of_files, game=self.game) download_files.insert(0, download) self.download_list.extend(download_files) if check_diskspace(total_file_size, Config.get("install_dir")): DownloadManager.download(download_files) ds_msg_title = "" ds_msg_text = "" else: ds_msg_title = "Download error" ds_msg_text = "Not enough disk space to install game." download_success = False if ds_msg_title: GLib.idle_add(self.parent.parent.show_error, _(ds_msg_title), _(ds_msg_text)) return download_success
def verify_installer_integrity(game, installer): error_message = "" if not os.path.exists(installer): error_message = _("{} failed to download.").format(installer) if not error_message: for installer_file_name in os.listdir(os.path.dirname(installer)): hash_md5 = hashlib.md5() with open( os.path.join(os.path.dirname(installer), installer_file_name), "rb") as installer_file: for chunk in iter(lambda: installer_file.read(4096), b""): hash_md5.update(chunk) calculated_checksum = hash_md5.hexdigest() if installer_file_name in game.md5sum: if game.md5sum[installer_file_name] == calculated_checksum: print("{} integrity is preserved. MD5 is: {}".format( installer_file_name, calculated_checksum)) else: error_message = _( "{} was corrupted. Please download it again.").format( installer_file_name) break else: print("Warning. No info about correct {} MD5 checksum".format( installer_file_name)) return error_message
def __init__(self, parent): Gtk.Dialog.__init__(self, title=_("Preferences"), parent=parent, modal=True) self.parent = parent self.__set_language_list() self.button_file_chooser.set_filename(Config.get("install_dir")) self.switch_keep_installers.set_active(Config.get("keep_installers")) self.switch_stay_logged_in.set_active(Config.get("stay_logged_in")) self.switch_show_fps.set_active(Config.get("show_fps")) self.switch_show_windows_games.set_active( Config.get("show_windows_games")) # Set tooltip for keep installers label installer_dir = os.path.join(self.button_file_chooser.get_filename(), "installer") self.label_keep_installers.set_tooltip_text( _("Keep installers after downloading a game.\nInstallers are stored in: {}" ).format(installer_dir)) # Only allow showing Windows games if wine is available if not shutil.which("wine"): self.switch_show_windows_games.set_sensitive(False) self.switch_show_windows_games.set_tooltip_text( _("Install Wine to enable this feature"))
def on_menu_button_store(self, widget): try: webbrowser.open(self.gogBaseUrl + self.game.url) except webbrowser.Error: self.parent.parent.show_error( _("Couldn't open store page"), _("Please check your internet connection"))
def save_pressed(self, button): self.__save_language_choice() Config.set("keep_installers", self.switch_keep_installers.get_active()) Config.set("stay_logged_in", self.switch_stay_logged_in.get_active()) if self.switch_show_windows_games.get_active() != Config.get( "show_windows_games"): if self.switch_show_windows_games.get_active( ) and not shutil.which("wine"): self.parent.show_error( _("Wine wasn't found. Showing Windows games cannot be enabled." )) Config.set("show_windows_games", False) else: Config.set("show_windows_games", self.switch_show_windows_games.get_active()) self.parent.reset_library() # Only change the install_dir is it was actually changed if self.button_file_chooser.get_filename() != Config.get( "install_dir"): if self.__save_install_dir_choice(): DownloadManager.cancel_all_downloads() self.parent.reset_library() else: self.parent.show_error( _("{} isn't a usable path").format( self.button_file_chooser.get_filename())) self.destroy()
def ok_pressed(self, button): if self.game.is_installed(): self.game.set_info( "check_for_updates", self.switch_properties_check_for_updates.get_active()) self.game.set_info("show_fps", self.switch_properties_show_fps.get_active()) if self.switch_properties_use_gamemode.get_active( ) and not shutil.which("gamemoderun"): self.parent.parent.parent.show_error( _("GameMode wasn't found. Using GameMode cannot be enabled." )) self.game.set_info("use_gamemode", False) else: self.game.set_info( "use_gamemode", self.switch_properties_use_gamemode.get_active()) if self.switch_properties_use_mangohud.get_active( ) and not shutil.which("mangohud"): self.parent.parent.parent.show_error( _("MangoHud wasn't found. Using MangoHud cannot be enabled." )) self.game.set_info("use_mangohud", False) else: self.game.set_info( "use_mangohud", self.switch_properties_use_mangohud.get_active()) self.game.set_info("variable", str(self.entry_properties_variable.get_text())) self.game.set_info("command", str(self.entry_properties_command.get_text())) self.game.set_info("hide_game", self.switch_properties_hide_game.get_active()) self.parent.parent.filter_library() self.destroy()
def __init__(self, parent): Gtk.Dialog.__init__(self, title=_("Preferences"), parent=parent, modal=True) self.parent = parent self.__set_locale_list() self.__set_language_list() self.__set_view_list() self.button_file_chooser.set_filename(Config.get("install_dir")) self.switch_keep_installers.set_active(Config.get("keep_installers")) self.switch_stay_logged_in.set_active(Config.get("stay_logged_in")) self.switch_use_dark_theme.set_active(Config.get("use_dark_theme")) self.switch_show_hidden_games.set_active( Config.get("show_hidden_games")) self.switch_show_windows_games.set_active( Config.get("show_windows_games")) self.switch_create_applications_file.set_active( Config.get("create_applications_file")) # Set tooltip for keep installers label installer_dir = os.path.join(self.button_file_chooser.get_filename(), "installer") self.label_keep_installers.set_tooltip_text( _("Keep installers after downloading a game.\nInstallers are stored in: {}" ).format(installer_dir))
def extract_installer(game, installer, temp_dir): # Extract the installer error_message = "" if game.platform == "linux": command = ["unzip", "-qq", installer, "-d", temp_dir] else: # Set the prefix for Windows games prefix_dir = os.path.join(game.install_dir, "prefix") if not os.path.exists(prefix_dir): os.makedirs(prefix_dir, mode=0o755) # It's possible to set install dir as argument before installation command = [ "env", "WINEPREFIX={}".format(prefix_dir), "wine", installer, "/dir={}".format(temp_dir) ] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.wait() stdout, stderr = process.communicate() stdout = stdout.decode("utf-8") stderr = stderr.decode("utf-8") if (process.returncode not in [0, 1]) or \ (process.returncode in [1] and "(attempting to process anyway)" not in stderr): error_message = _("The installation of {} failed. Please try again." ).format(installer) elif len(os.listdir(temp_dir)) == 0: error_message = _("{} could not be unzipped.".format(installer)) return error_message
def load_description(self): description = "" lang = Config.get("lang") if self.gamesdb_info["summary"]: desc_lang = "*" for summary_key in self.gamesdb_info["summary"].keys(): if lang in summary_key: desc_lang = summary_key description_len = 470 if len(self.gamesdb_info["summary"][desc_lang]) > description_len: description = "{}...".format( self.gamesdb_info["summary"][desc_lang][:description_len]) else: description = self.gamesdb_info["summary"][desc_lang] if "*" in self.gamesdb_info["genre"]: genre = self.gamesdb_info["genre"]["*"] else: genre = _("unknown") for genre_key, genre_value in self.gamesdb_info["genre"].items(): if lang in genre_key: genre = genre_value description = "{}: {}\n{}".format(_("Genre"), genre, description) if self.game.is_installed(): description = "{}: {}\n{}".format(_("Version"), self.game.get_info("version"), description) GLib.idle_add(self.label_game_description.set_text, description)
def on_menu_button_forum(self, widget): try: webbrowser.open(self.api.get_info(self.game)['links']['forum'], new=2) except webbrowser.Error: self.parent.parent.show_error( _("Couldn't open forum page"), _("Please check your internet connection"))
def on_menu_button_gog_database(self, widget): try: webbrowser.open("https://www.gogdb.org/product/{}".format( self.game.id)) except webbrowser.Error: self.parent.parent.show_error( _("Couldn't open forum page"), _("Please check your internet connection"))
def on_menu_button_support(self, widget): try: webbrowser.open(self.api.get_info(self.game)['links']['support'], new=2) except: self.parent.parent.show_eror( _("Couldn't open support page"), _("Please check your internet connection"))
def on_menu_button_pcgamingwiki(self, widget): try: webbrowser.open( "https://pcgamingwiki.com/api/gog.php?page={}".format( self.game.id)) except webbrowser.Error: self.parent.parent.show_error( _("Couldn't open forum page"), _("Please check your internet connection"))
def extract_linux(installer, temp_dir): err_msg = "" command = ["unzip", "-qq", installer, "-d", temp_dir] stdout, stderr, exitcode = _exe_cmd(command) if (exitcode not in [0]) and \ (exitcode not in [1] and "(attempting to process anyway)" not in stderr): err_msg = _("The installation of {} failed. Please try again.").format(installer) elif len(os.listdir(temp_dir)) == 0: err_msg = _("{} could not be unzipped.".format(installer)) return err_msg
def install_game(game, installer, parent_window=None) -> None: if not os.path.exists(installer): GLib.idle_add(__show_installation_error, game, _("{} failed to download.").format(installer), parent_window) raise FileNotFoundError("The installer {} does not exist".format(installer)) if not __verify_installer_integrity(installer): GLib.idle_add(__show_installation_error, game, _("{} was corrupted. Please download it again.").format(installer), parent_window) os.remove(installer) raise FileNotFoundError("The installer {} was corrupted".format(installer)) # Make a temporary empty directory for extracting the installer temp_dir = os.path.join(CACHE_DIR, "extract/{}".format(game.id)) if os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) os.makedirs(temp_dir) # Extract the installer try: with zipfile.ZipFile(installer) as archive: for member in archive.namelist(): file = archive.getinfo(member) archive.extract(file, temp_dir) # Set permissions attr = file.external_attr >> 16 os.chmod(os.path.join(temp_dir, member), attr) except zipfile.BadZipFile as e: GLib.idle_add(__show_installation_error, game, _("{} could not be unzipped.").format(installer), parent_window) raise e # Make sure the install directory exists library_dir = Config.get("install_dir") if not os.path.exists(library_dir): os.makedirs(library_dir) # Copy the game files into the correct directory shutil.move(os.path.join(temp_dir, "data/noarch"), game.install_dir) shutil.copyfile( os.path.join(THUMBNAIL_DIR, "{}.jpg".format(game.id)), os.path.join(game.install_dir, "thumbnail.jpg"), ) # Remove the temporary directory shutil.rmtree(temp_dir, ignore_errors=True) if Config.get("keep_installers"): keep_dir = os.path.join(Config.get("install_dir"), "installer") if not os.path.exists(keep_dir): os.makedirs(keep_dir) try: os.rename(installer, os.path.join(keep_dir, "{}.sh".format(game.name))) except Exception as ex: print("Encountered error while copying {} to {}. Got error: {}".format(installer, keep_dir, ex)) else: os.remove(installer)
def start_game(game, parent_window=None) -> subprocess: error_message = "" process = None __set_fps_display() # Change the directory to the install dir working_dir = os.getcwd() os.chdir(game.install_dir) try: process = subprocess.Popen(__get_execute_command(game), stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: error_message = _("No executable was found in {}").format( game.install_dir) # restore the working directory os.chdir(working_dir) # Check if the application has started and see if it is still runnning after a short timeout if process: try: process.wait(timeout=float(3)) except subprocess.TimeoutExpired: return process elif not error_message: error_message = _("Couldn't start subprocess") # Set the error message to what's been received in std error if not yet set if not error_message: stdout, stderror = process.communicate() error_message = stderror.decode("utf-8") stdout_message = stdout.decode("utf-8") if not error_message: if stdout: error_message = stdout_message else: error_message = _("No error message was returned") # Show the error as both a dialog and in the terminal error_text = _("Failed to start {}:").format(game.name) print(error_text) print(error_message) dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR, parent=parent_window.parent, modal=True, buttons=Gtk.ButtonsType.CLOSE, text=error_text) dialog.format_secondary_text(error_message) dialog.run() dialog.destroy()
def get_download_info(self): try: download_info = self.api.get_download_info(self.game) result = True except NoDownloadLinkFound as e: print(e) if Config.get("current_download") == self.game.id: Config.unset("current_download") GLib.idle_add(self.parent.parent.show_error, _("Download error"), _("There was an error when trying to fetch the download link!\n{}".format(e))) download_info = False result = False return result, download_info
def on_menu_button_support(self, widget): try: webbrowser.open(self.api.get_info(self.game)['links']['support'], new=2) except: dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR, parent=self.parent.parent, modal=True, buttons=Gtk.ButtonsType.OK, text=_("Couldn't open support page")) dialog.format_secondary_text( _("Please check your internet connection")) dialog.run() dialog.destroy()
def __add_games_from_api(self): retrieved_games, err_msg = self.api.get_library() if not err_msg: self.offline = False else: self.offline = True GLib.idle_add(self.parent.show_error, _("Failed to retrieve library"), _(err_msg)) for game in retrieved_games: if game not in self.games: self.games.append(game) elif self.games[self.games.index(game)].id == 0: self.games[self.games.index(game)].id = game.id self.games[self.games.index(game)].image_url = game.image_url self.games[self.games.index(game)].url = game.url
def __add_games_from_api(self): try: retrieved_games = self.api.get_library() self.offline = False except: retrieved_games = [] self.offline = True GLib.idle_add(self.parent.show_error, _("Failed to retrieve library"), _("Couldn't connect to GOG servers")) for game in retrieved_games: if game not in self.games: self.games.append(game) elif self.games[self.games.index(game)].id == 0: self.games[self.games.index(game)].id = game.id
def __init__(self, parent, game, api): Gtk.Dialog.__init__(self, title=_("Properties of {}").format(game.name), parent=parent.parent.parent, modal=True) self.parent = parent self.game = game self.api = api self.gamesdb_info = self.api.get_gamesdb_info(self.game) # Disable/Enable buttons self.button_sensitive(game) # Retrieve variable & command each time properties is open self.entry_properties_variable.set_text(self.game.get_info("variable")) self.entry_properties_command.set_text(self.game.get_info("command")) # Keep switch FPS disabled/enabled self.switch_properties_show_fps.set_active( self.game.get_info("show_fps")) # Keep switch game shown/hidden self.switch_properties_hide_game.set_active( self.game.get_info("hide_game")) # Center properties window self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
def __state_queued(self): self.button.set_label(_("in queue…")) self.button.set_sensitive(False) self.image.set_sensitive(False) self.menu_button.hide() self.button_cancel.show() self.__create_progress_bar()
def save_pressed(self, button): self.__save_language_choice() Config.set("keep_installers", self.switch_keep_installers.get_active()) Config.set("stay_logged_in", self.switch_stay_logged_in.get_active()) Config.set("show_fps", self.switch_show_fps.get_active()) if self.switch_show_windows_games.get_active() != Config.get( "show_windows_games"): Config.set("show_windows_games", self.switch_show_windows_games.get_active()) self.parent.reset_library() # Only change the install_dir is it was actually changed if self.button_file_chooser.get_filename() != Config.get( "install_dir"): if self.__save_install_dir_choice(): DownloadManager.cancel_all_downloads() self.parent.reset_library() else: dialog = Gtk.MessageDialog( parent=self, modal=True, destroy_with_parent=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text=_("{} isn't a usable path").format( self.button_file_chooser.get_filename())) dialog.run() dialog.destroy() self.destroy()
def __install(self, save_location, update=False, dlc_title=""): if update: processing_state = self.state.UPDATING failed_state = self.state.INSTALLED else: processing_state = self.state.INSTALLING failed_state = self.state.DOWNLOADABLE success_state = self.state.INSTALLED GLib.idle_add(self.update_to_state, processing_state) err_msg = install_game(self.game, save_location) if not err_msg: GLib.idle_add(self.update_to_state, success_state) install_success = True if dlc_title: self.game.set_dlc_info( "version", self.api.get_version(self.game, dlc_name=dlc_title), dlc_title) else: self.game.set_info("version", self.api.get_version(self.game)) else: GLib.idle_add(self.parent.parent.show_error, _("Failed to install {}").format(self.game.name), err_msg) GLib.idle_add(self.update_to_state, failed_state) install_success = False return install_success
def __download_file(self) -> None: if not os.path.exists(self.download_dir): os.makedirs(self.download_dir) if not os.path.exists(self.keep_path): download_info = self.api.get_download_info(self.game) file_url = download_info["downlink"] data = requests.get(file_url, stream=True) handler = open(self.download_path, "wb") total_size = int(data.headers.get('content-length')) downloaded_size = 0 chunk_count = 0 for chunk in data.iter_content(chunk_size=4096): if chunk: chunk_count += 1 handler.write(chunk) downloaded_size += len(chunk) # Only update the progress bar every 2 megabytes if chunk_count == 4000: percentage = downloaded_size / total_size GLib.idle_add(self.progress_bar.set_fraction, percentage) chunk_count = 0 handler.close() GLib.idle_add(self.progress_bar.destroy) GLib.idle_add(self.button.set_label, _("installing..")) self.__install_game() self.busy = False GLib.idle_add(self.load_state) GLib.idle_add(self.button.set_sensitive, True) GLib.idle_add(self.parent.filter_library)
def __add_games_from_api(self): try: retrieved_games = self.api.get_library() self.offline = False except: self.offline = True GLib.idle_add(self.parent.show_error, _("Failed to retrieve library"), _("Couldn't connect to GOG servers")) return installed_game_names = [] for game in self.games: installed_game_names.append(game.name.lower()) for game in retrieved_games: if game.name.lower() not in installed_game_names: self.games.append(game)
def __add_games_from_api(self): try: retrieved_games = self.api.get_library() self.offline = False except: self.offline = True GLib.idle_add(self.parent.show_error, _("Failed to retrieve library"), _("Couldn't connect to GOG servers")) return for game in retrieved_games: if game in self.games: # Make sure the game id is set if the game is installed for installed_game in self.games: if game == installed_game: self.games.remove(installed_game) break self.games.append(game)
def __install(self, update=False, dlc_title=""): keep_executable_path = self.get_keep_executable_path() if keep_executable_path: installer = keep_executable_path else: installer = self.download_path if update: processing_state = self.state.UPDATING failed_state = self.state.INSTALLED success_state = self.state.INSTALLED else: processing_state = self.state.INSTALLING failed_state = self.state.DOWNLOADABLE success_state = self.state.INSTALLED GLib.idle_add(self.update_to_state, processing_state) err_msg = install_game(self.game, installer) if not err_msg: GLib.idle_add(self.update_to_state, success_state) install_success = True self.game.set_status("version", self.api.get_version(self.game, dlc_name=dlc_title), dlc_title=dlc_title) else: GLib.idle_add(self.parent.parent.show_error, _("Failed to install {}").format(self.game.name), err_msg) GLib.idle_add(self.update_to_state, failed_state) install_success = False return install_success
def verify_disk_space(game, installer): err_msg = "" if game.platform == "linux": required_space = get_game_size_from_unzip(installer) if not check_diskspace(required_space, game.install_dir): err_msg = _("Not enough space to extract game. Required: {} Available: {}" ).format(required_space, get_available_disk_space(game.install_dir)) return err_msg