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 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: