From bf5bc33f23c9a5903237551bfcfc857927f3ad3f Mon Sep 17 00:00:00 2001 From: Dteyn Date: Mon, 9 Oct 2023 12:22:56 -0600 Subject: [PATCH 1/2] Added an optional "Top Games" sorting feature to prioritize a custom list of games at the top of the display. This enhancement provides users with the flexibility to highlight their favorite games, making them easily accessible at the top of the list. - `tadpoleConfig.py` changes: - Config setting `_static_topGamesEnabled` with default to False - Methods `getTopGamesEnabled` and `setTopGamesEnabled` to read and set the new config option - `SettingsDialog.py` changes: - Section "Sorting Options" with a checkbox to enable/disable the "Top Games" sorting feature. - Method `topGamesToggled` which calls `tpConf.setTopGamesEnabled` in tadpole_config - `tadpole_functions.py` changes: - Function `read_top_games` which reads the TopGames.txt file and returns it as a dictionary, with a list of games for each system - Systems are defined by a trailing colon, for example FC: or GBA: - `tadpole.py` changes: - Imported `read_top_games` from `tadpole_functions.py` - top_games_list initialized as an empty list - Function `RunFrogTool` modified to get the `top_games_list` and pass it to `frogtool.process_sys` if Top Games feature is enabled - Function `loadROMsToTable` modified to display top games if Top Games feature is enabled - `frogtool.py` changes: - Function `process_sys` takes an additional parameter: top_games which defaults to an empty list, which is passed to `write_index_file` - Function `write_index_file` will re-sort the games with top games if feature is enabled, before metadata pointers are generated - Added error handling to gracefully handle cases where the `TopGames.txt` file is missing or improperly formatted. - Conducted thorough testing to ensure the new feature functions as intended without disrupting existing functionality. --- dialogs/SettingsDialog.py | 17 +++++++++++++++-- frogtool.py | 34 ++++++++++++++++++++++++++-------- tadpole.py | 19 +++++++++++++++++-- tadpoleConfig.py | 13 +++++++++++-- tadpole_functions.py | 26 ++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/dialogs/SettingsDialog.py b/dialogs/SettingsDialog.py index 8f72d06..bbb1754 100644 --- a/dialogs/SettingsDialog.py +++ b/dialogs/SettingsDialog.py @@ -9,7 +9,7 @@ import frogtool from dialogs.DownloadProgressDialog import DownloadProgressDialog -# Subclass Qidget to create a Settings window +# Subclass Qidget to create a Settings window class SettingsDialog(QDialog): """ This window should be called without a parent widget so that it is created in its own window. @@ -51,6 +51,17 @@ def __init__(self, tpConf): self.layout_main.addWidget(QLabel(" ")) # spacer + # Sorting options options + self.top_games_enabled = self.tpConf.getTopGamesEnabled() # Get the initial state from configuration + self.layout_main.addWidget(QLabel("Sorting options")) + self.top_games_sorting_checkbox = QCheckBox("Enable Top Games List (TopGames.txt)", self) + self.top_games_sorting_checkbox.setToolTip("Adds the games specified in TopGames.txt to the top of the game listing") + self.top_games_sorting_checkbox.clicked.connect(self.topGamesToggled) + self.layout_main.addWidget(self.top_games_sorting_checkbox) + self.top_games_sorting_checkbox.setChecked(self.top_games_enabled) + + self.layout_main.addWidget(QLabel(" ")) # spacer + #File options options self.layout_main.addWidget(QLabel("File Options")) UserSavedDirectory = tpConf.getLocalUserDirectory() @@ -122,4 +133,6 @@ def userSelectedDirectoryResetSettingsButton(self): def thumbnailViewClicked(self): self.tpConf.setViewThumbnailsInTable(self.sender().isChecked()) - + + def topGamesToggled(self): + self.tpConf.setTopGamesEnabled(self.top_games_sorting_checkbox.isChecked()) diff --git a/frogtool.py b/frogtool.py index 52eb82e..38bcf73 100644 --- a/frogtool.py +++ b/frogtool.py @@ -94,7 +94,7 @@ def getROMList(roms_path): filenames = list(map(file_entry_to_name, files)) return filenames -def process_sys(drive, system, test_mode): +def process_sys(drive, system, test_mode, top_games=None): print(f"Processing {system}") roms_path = os.path.join(drive,system) @@ -129,9 +129,9 @@ def process_sys(drive, system, test_mode): name_map_cn = dict(zip(filenames, stripped_names)) name_map_pinyin = dict(zip(filenames, stripped_names)) - write_index_file(name_map_files, sort_without_file_ext, index_path_files, test_mode) - write_index_file(name_map_cn, sort_normal, index_path_cn, test_mode) - write_index_file(name_map_pinyin, sort_normal, index_path_pinyin, test_mode) + write_index_file(name_map_files, sort_without_file_ext, index_path_files, test_mode, top_games) + write_index_file(name_map_cn, sort_normal, index_path_cn, test_mode, top_games) + write_index_file(name_map_pinyin, sort_normal, index_path_pinyin, test_mode, top_games) print("Done\n") return f"Finished updating {system} with {no_files} ROMs" @@ -278,7 +278,7 @@ def check_and_back_up_file(file_path): raise StopExecution -def write_index_file(name_map, sort_func, index_path, test_mode): +def write_index_file(name_map, sort_func, index_path, test_mode, top_games=None): # entries must maintain a consistent order between all indexes, but what that order actually is doesn't matter # so use alphabetised filenames for this sorted_filenames = sorted(name_map.keys()) @@ -296,6 +296,27 @@ def write_index_file(name_map, sort_func, index_path, test_mode): # the rest are pointers to the display names in the desired display order # so sort display names according to the display order, and build a list of pointers in that order sorted_display_names = sort_func(name_map.values()) + + # unless Top Games feature is enabled; if so separate the Top Games and place at the top of the list + if top_games and sorted_display_names: + + # Check if sorted_display_names have file extensions, if not strip the extensions from top_games + has_extension = any(sorted_display_names[0].endswith("." + ext) for ext in zxx_ext.values()) + if not has_extension: + for ext in zxx_ext.values(): + top_games = [game[:-len(ext) - 1] if game.endswith("." + ext) else game for game in top_games] + + top_sorted = [game for game in top_games if game in sorted_display_names] + remainder_games_sorted = [game for game in sorted_display_names if game not in top_sorted] + + # Log error for games in top list but not found in the main list + for game in top_games: + if game not in sorted_display_names: + print(f"WARNING: Game '{game}' is in the Top Games List but not found in the main list.") + + # Combine top list and remainder list + sorted_display_names = top_sorted + remainder_games_sorted + sorted_pointers = map(lambda name: pointers_by_name[name], sorted_display_names) for current_pointer in sorted_pointers: metadata_bytes += int_to_4_bytes_reverse(current_pointer) @@ -324,6 +345,3 @@ def write_index_file(name_map, sort_func, index_path, test_mode): def check_sys_valid(system): return system and (system in systems.keys() or system == "ALL") - - - diff --git a/tadpole.py b/tadpole.py index e9b2ae1..731b8db 100644 --- a/tadpole.py +++ b/tadpole.py @@ -29,6 +29,7 @@ # Tadpole imports import frogtool import tadpole_functions +from tadpole_functions import read_top_games from tadpoleConfig import TadpoleConfig # Dialog imports from dialogs.SettingsDialog import SettingsDialog @@ -64,6 +65,7 @@ def RunFrogTool(drive, console): if drive == 'N/A': logging.warning("You are trying to run froggy with no drive.") return + top_games_list = [] print(f"Running frogtool with drive ({drive}) and console ({console})") logging.info(f"Running frogtool with drive ({drive}) and console ({console})") try: @@ -77,7 +79,10 @@ def RunFrogTool(drive, console): rebuildingmsgBox.showProgress(progress, True) rebuildingmsgBox.show() for console in frogtool.systems.keys(): - result = frogtool.process_sys(drive, console, False) + if tpConf.getTopGamesEnabled(): + print("Top Games feature enabled - sorting game list with Top Games List at the top") + top_games_list = read_top_games(console) + result = frogtool.process_sys(drive, console, False, top_games_list) #Update Progress progress += 10 rebuildingmsgBox.showProgress(progress, True) @@ -85,7 +90,10 @@ def RunFrogTool(drive, console): rebuildingmsgBox.close() QMessageBox.about(window, "Result", "Rebuilt all ROMS for all systems") else: - result = frogtool.process_sys(drive, console, False) + if tpConf.getTopGamesEnabled(): + print("Top Games feature enabled - sorting game list with Top Games List at the top") + top_games_list = read_top_games(console) + result = frogtool.process_sys(drive, console, False, top_games_list) print("Result " + result) #Always reload the table now that the folders are all cleaned up window.loadROMsToTable() @@ -1198,6 +1206,13 @@ def loadROMsToTable(self): start_time = time.perf_counter() #sort the list aphabetically before we go through it files = sorted(files) + if tpConf.getTopGamesEnabled(): #sort with top games at the top if Top Games feature is enabled + print("Top Games feature enabled - sorting game list with Top Games List at the top") + top_games_list = read_top_games(system) + if top_games_list: + top_sorted = [game for game in top_games_list if game in files] + remainder_games_sorted = [game for game in files if game not in top_sorted] + files = top_sorted + remainder_games_sorted for i,game in enumerate(files): objGame = sf2000ROM(os.path.join(roms_path, game)) if objGame.ROMlocation == '': diff --git a/tadpoleConfig.py b/tadpoleConfig.py index b20286a..51fa45f 100644 --- a/tadpoleConfig.py +++ b/tadpoleConfig.py @@ -17,8 +17,9 @@ class TadpoleConfig(): _static_thumbnails_overwrite_DEFAULT = "False" _static_thumbnails_download = "download" _static_thumbnails_download_DEFAULT = "0" + _static_topGamesEnabled = "top_games_enabled" + _static_topGamesEnabled_DEFAULT = "False" - def __init__(self): super().__init__() print(f"establishing tadpole config") @@ -107,4 +108,12 @@ def setThumbnailOverwrite(self, enabled: bool): def getThumbnailOverwrite(self): view = self.getVariable(self._static_thumbnails,self._static_thumbnails_overwrite,self._static_thumbnails_overwrite_DEFAULT) - return view == "True" \ No newline at end of file + return view == "True" + + def getTopGamesEnabled(self): + return self.config[self._static_general].get(self._static_topGamesEnabled, self._static_topGamesEnabled_DEFAULT) == "True" + + def setTopGamesEnabled(self, value): + self.config[self._static_general][self._static_topGamesEnabled] = "True" if value else "False" + with open(self._static_TadpoleConfigFile, 'w') as configfile: + self.config.write(configfile) diff --git a/tadpole_functions.py b/tadpole_functions.py index 3f2e937..210e9de 100644 --- a/tadpole_functions.py +++ b/tadpole_functions.py @@ -1169,6 +1169,32 @@ def addThumbnail(rom_path, drive, system, new_thumbnail, ovewrite): #QMessageBox.about(window, "Change ROM Cover", "An error occurred.") return False +def read_top_games(system): + try: + current_system_name = None + games = [] + with open("TopGames.txt", 'r') as file: + for line in file: + line = line.strip() + if line.endswith(":"): # Line ending with colon indicates a system name + if current_system_name == system: # Check if the previous system matches the provided system + return games + current_system_name = line[:-1] # Remove the colon to get the system name + games = [] # Clear the games list + elif line: # Non-empty line indicates a game name + games.append(line) + # Check the last system's games if it matches the provided system + if current_system_name == system: + return games + return [] # Return an empty list if the system was not found + except FileNotFoundError: + print(f"Error: File 'TopGames.txt' not found. Top Game Sorting will not be enabled.") + return [] + except Exception as e: + print(f"Error while reading the TopGames.txt file: {e}") + return [] + + #Thanks to Dteyn for putting the python together from here: https://github.com/Dteyn/SF2000_Battery_Level_Patcher/blob/master/main.py #Thanks to OpenAI for writing the class and converting logging to prints class BatteryPatcher: From 8ea330cad8aad7f32250b6eab9d9545caaf3a3b9 Mon Sep 17 00:00:00 2001 From: Dteyn Date: Mon, 9 Oct 2023 12:32:25 -0600 Subject: [PATCH 2/2] TopGames.txt for the stock firmware. Includes a list of the Top 25 games for each system. --- TopGames.txt | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 TopGames.txt diff --git a/TopGames.txt b/TopGames.txt new file mode 100644 index 0000000..6c06ec7 --- /dev/null +++ b/TopGames.txt @@ -0,0 +1,188 @@ +ARCADE: +Aero Fighters 2.zfb +Alien vs. Predator.zfb +Baseball Stars 2.zfb +Blazing Star.zfb +Garou -Mark of the Wolves.zfb +Marvel Super Heroes.zfb +Marvel Vs. Capcom- Clash of Super Heroes.zfb +Matrimelee.zfb +Metal Slug 3.zfb +Neo Bomberman.zfb +Neo Turf Masters.zfb +Progear.zfb +Real Bout Fatal Fury Special.zfb +Samurai Shodown IV -Amakusa's Revenge.zfb +Saturday Night Slam Masters.zfb +Shock Troopers.zfb +Street Fighter Alpha 3.zfb +Street Fighter II- The World Warrior.zfb +Super Puzzle Fighter II Turbo.zfb +The King of Fighters '97.zfb +The King of Fighters '98 -The Slugfest.zfb +The Punisher.zfb +U.N. Squadron.zfb +Vampire Savior- The Lord of Vampire.zfb +Zupapa!.zfb + +GB: +Balloon Kid.zgb +Batman.zgb +Battletoads in Ragnarok's World.zgb +Bomberman GB.zgb +Castlevania II - Belmont's Revenge.zgb +Donkey Kong.zgb +Donkey Kong Land.zgb +Dr. Mario.zgb +Final Fantasy Adventure.zgb +Megaman V.zgb +Kid Icarus - Of Myths and Monsters.zgb +Kirby's Dream Land.zgb +Lemmings.zgb +Metroid II - Return of Samus.zgb +Mole Mania.zgb +Operation C.zgb +Pokemon - Red Version.zgb +Pokemon - Blue Version.zgb +R-Type.zgb +Super Mario Land.zgb +Super Mario Land 2 - 6 Golden Coins.zgb +Teenage Mutant Ninja Turtles - Fall of the Foot Clan.zgb +Tetris.zgb +The Legend of Zelda - Link's Awakening.zgb +Wario Land - Super Mario Land 3.zgb + +GBC: +Conker's Pocket Tales.zgb +Donkey Kong Country.zgb +Dragon Warrior III.zgb +Harvest Moon 2 GBC.zgb +Mario Golf.zgb +Mario Tennis.zgb +MegaMan Xtreme.zgb +Metal Gear Solid.zgb +Pocket Bomberman.zgb +Pokemon - Crystal Version.zgb +Pokemon - Gold Version.zgb +Pokemon - Silver Version.zgb +Rayman.zgb +Resident Evil Gaiden.zgb +R-Type DX.zgb +Shantae.zgb +Super Mario Bros. Deluxe.zgb +Survival Kids.zgb +Tetris DX.zgb +The Legend of Zelda - Link's Awakening DX.zgb +The Legend of Zelda - Oracle of Ages.zgb +The Legend of Zelda - Oracle of Seasons.zgb +Tomb Raider.zgb +Warioland II.zgb +Warioland 3.zgb + +GBA: +Advance Wars.zgb +Astro Boy - Omega Factor.zgb +Castlevania - Aria of Sorrow.zgb +Castlevania - Circle of the Moon.zgb +Castlevania - Harmony of Dissonance.zgb +F-Zero - Maximum Velocity.zgb +Fire Emblem - The Sacred Stones.zgb +Golden Sun.zgb +Kirby - Nightmare in Dream Land.zgb +Mario & Luigi - Superstar Saga.zgb +Mario Kart - Super Circuit.zgb +Megaman Zero.zgb +Metroid - Zero Mission.zgb +Metroid Fusion.zgb +Pokemon - Emerald Version.zgb +Pokemon - Ruby Version.zgb +Pokemon - Sapphire Version.zgb +Sonic Advance 2.zgb +Sonic Advance 3.zgb +Sonic Advance.zgb +Super Mario Advance 2 - Super Mario World.zgb +Super Mario Advance 4 - Super Mario Bros. 3.zgb +Super Mario Advance.zgb +Sword of Mana.zgb +Zone of the Enders - The Fist of Mars.zgb + +FC: +Adventure Island 2.zfc +Battletoads 1.zfc +Bubble Bobble 1.zfc +Castlevania.zfc +Contra 1.zfc +Double Dragon 1.zfc +Dr Mario.zfc +Excitebike.zfc +Final Fantasy.zfc +Gauntlet 2.zfc +Ghostn Goblins.zfc +Gradius 1.zfc +Kirbys adventure.zfc +Kung Fu.zfc +Mega Man 2.zfc +Metroid.zfc +Ninja Gaiden 1.zfc +Punch Out.zfc +StarTropics.zfc +Super Mario Bros 1.zfc +Super Mario Bros 2.zfc +Super Mario Bros 3.zfc +Tecmo Bowl.zfc +The Legend of Zelda 2.zfc +The Legend of Zelda.zfc + +SFC: +Actraiser.zsf +Breath Of Fire II.zsf +Chrono Trigger.zsf +Contra III - The Alien Wars.zsf +Donkey Kong Country.zsf +Donkey Kong Country 2 - Diddy's Kong Quest.zsf +Earthbound.zsf +Earthworm Jim.zsf +Final Fantasy II.zsf +Final Fantasy III.zsf +F-Zero.zsf +Kirby Super Star.zsf +The Legend Of Zelda - A Link To The Past.zsf +Mega Man X.zsf +Secret Of Mana.zsf +Simcity.zsf +Super Castlevania Iv.zsf +Super Ghouls'n Ghosts.zsf +Super Mario All-Stars.zsf +Super Mario Kart.zsf +Super Mario World.zsf +Super Metroid.zsf +Super Punch-Out!!.zsf +Super Street Fighter II.zsf +Terranigma.zsf + +MD: +Altered Beast.zmd +Beyond Oasis.zmd +Castlevania - Bloodlines.zmd +Comix Zone.zmd +Earthworm Jim.zmd +Ecco the Dolphin.zmd +Golden Axe.zmd +Gunstar Heroes.zmd +Mortal Kombat II.zmd +NBA Jam.zmd +Phantasy Star IV.zmd +Ristar.zmd +Rocket Knight Adventures.zmd +Shining Force II.zmd +Shinobi III - Return of the Ninja Master.zmd +Sonic & Knuckles.zmd +Sonic The Hedgehog 1.zmd +Sonic The Hedgehog 2.zmd +Sonic The Hedgehog 3.zmd +Streets of Rage 2.zmd +Streets of Rage 3.zmd +Strider.zmd +Teenage Mutant Hero Turtles - The Hyperstone Heist.zmd +ToeJam & Earl.zmd +Vectorman.zmd