diff --git a/games/game_yakuza0.py b/games/game_yakuza0.py new file mode 100644 index 0000000..9f3e148 --- /dev/null +++ b/games/game_yakuza0.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza0Game(BasicGame): + + __yakuza_exe_dir = 'media' + + Name = "Yakuza 0 Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza 0" + GameShortName = "yakuza0" + GameSteamId = [638970] + GameBinary = os.path.join(__yakuza_exe_dir, "Yakuza0.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '2dpar', + 'auth_w64_e', + 'battlepar', + 'bootpar', + 'chara', + 'chara_arc', + 'chara_common', + 'cloth', + 'drama_scanner', + 'effect', + 'fighter', + 'flood', + 'flood.par', + 'fontpar', + 'hact', + 'hact.par', + 'light_anim', + 'loading', + 'minigame', + 'motion_w64', + 'movie_w64', + 'reactorpar', + 'scenario', + 'shader', + 'sound', + 'soundcpk', + 'soundpar', + 'stage', + 'staypar', + } diff --git a/games/game_yakuza3remastered.py b/games/game_yakuza3remastered.py new file mode 100644 index 0000000..52df7e8 --- /dev/null +++ b/games/game_yakuza3remastered.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza3RemasteredGame(BasicGame): + + __yakuza_exe_dir = '' + + Name = "Yakuza 3 Remastered Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza 3 Remastered" + GameShortName = "yakuza3remastered" + GameSteamId = [1088710] + GameBinary = os.path.join(__yakuza_exe_dir, "Yakuza3.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '2d', + 'auth', + 'battle', + 'bootpar', + 'chara', + 'chara_arc', + 'chara_common', + 'db.ogre3', + 'effect', + 'effect.par', + 'enemy_dispose', + 'font_hd', + 'font_hd.par', + 'fontpar', + 'hact', + 'light_anim', + 'map_en', + 'map_ja', + 'map_ko', + 'map_zh', + 'minigame', + 'motion', + 'mvuen', + 'mvusm', + 'patch', + 'pausepar', + 'pre_btl_cam', + 'puid.ogre3', + 'reactive_obj', + 'savedata', + 'savedata.par', + 'scenario_en', + 'scenario_ja', + 'scenario_ko', + 'scenario_zh', + 'shader', + 'snda2', + 'staffrollpar', + 'stage', + 'tougijyo', + 'tougijyo.par', + 'wdr_par_en', + 'wdr_par_ja', + 'wdr_par_ko', + 'wdr_par_zh', + } diff --git a/games/game_yakuza4remastered.py b/games/game_yakuza4remastered.py new file mode 100644 index 0000000..b0be17c --- /dev/null +++ b/games/game_yakuza4remastered.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza4RemasteredGame(BasicGame): + + __yakuza_exe_dir = '' + + Name = "Yakuza 4 Remastered Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza 4 Remastered" + GameShortName = "yakuza4remastered" + GameSteamId = [1105500] + GameBinary = os.path.join(__yakuza_exe_dir, "Yakuza4.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '2d', + 'auth', + 'battle', + 'bootpar', + 'chara', + 'chara_arc', + 'chara_common', + 'chasepar', + 'db.soul', + 'effect', + 'effect.par', + 'enemy_dispose', + 'font_hd', + 'font_hd.par', + 'fontpar', + 'hact', + 'ikusei', + 'light_anim', + 'map_en', + 'map_ja', + 'map_ko', + 'map_zh', + 'minigame', + 'motion', + 'mvuen', + 'mvusm', + 'pausepar', + 'pre_btl_cam', + 'puid.soul', + 'reactive_obj', + 'savedata', + 'savedata.par', + 'scenario_en', + 'scenario_ja', + 'scenario_ko', + 'scenario_zh', + 'shader', + 'snda2', + 'staffrollpar', + 'stage', + 'tougijyo', + 'tougijyo.par', + 'wdr_par_en', + 'wdr_par_ja', + 'wdr_par_ko', + 'wdr_par_zh', + } diff --git a/games/game_yakuza5remastered.py b/games/game_yakuza5remastered.py new file mode 100644 index 0000000..5392b2f --- /dev/null +++ b/games/game_yakuza5remastered.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza5RemasteredGame(BasicGame): + + __yakuza_exe_dir = 'main' + + Name = "Yakuza 5 Remastered Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza 5 Remastered" + GameShortName = "yakuza5remastered" + GameSteamId = [1105510] + GameBinary = os.path.join(__yakuza_exe_dir, "Yakuza5.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '2dpar', + 'auth', + 'auth_telop', + 'battlepar', + 'bootpar', + 'chara', + 'chara.par', + 'chara_arc', + 'chara_common', + 'db.devil', + 'effect', + 'effect.par', + 'fighter', + 'fontpar', + 'hact', + 'light_anim', + 'm2ftg', + 'map_par_hd', + 'minigame_en', + 'minigame_ja', + 'minigame_ko', + 'minigame_zh', + 'module', + 'motion', + 'mvstm', + 'pausepar', + 'puid.devil', + 'reactorpar', + 'scenario', + 'scenario_en', + 'scenario_ja', + 'scenario_ko', + 'scenario_zh', + 'shader', + 'soundpar', + 'stage', + 'staypar', + 'strmen', + 'wdr_par_en', + 'wdr_par_ja', + 'wdr_par_ko', + 'wdr_par_zh', + } diff --git a/games/game_yakuza6.py b/games/game_yakuza6.py new file mode 100644 index 0000000..3a2fbc3 --- /dev/null +++ b/games/game_yakuza6.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza6Game(BasicGame): + + __yakuza_exe_dir = '' + + Name = "Yakuza 6 Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza 6: The Song of Life" + GameShortName = "yakuza6" + GameSteamId = [1388590] + GameBinary = os.path.join(__yakuza_exe_dir, "Yakuza6.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '3dlut', + 'asset', + 'asset.par', + 'auth', + 'boot', + 'camera', + 'chara', + 'chara.par', + 'cubemap', + 'cubemap.par', + 'db', + 'drama_scanner', + 'drama_scanner.par', + 'effect', + 'effect.par', + 'entity', + 'entity.par', + 'entity_table', + 'flood', + 'font', + 'font.par', + 'hact', + 'light_anim', + 'lua', + 'lua.par', + 'map', + 'map.par', + 'minigame', + 'motion', + 'motion.par', + 'movie', + 'particle', + 'particle.par', + 'puid', + 'shader', + 'sound', + 'sound.par', + 'stage', + 'stream', + 'talk', + 'talk.par', + 'ui', + 'ui.par', + } diff --git a/games/game_yakuzakiwami.py b/games/game_yakuzakiwami.py new file mode 100644 index 0000000..2d3c2a7 --- /dev/null +++ b/games/game_yakuzakiwami.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class YakuzaKiwamiGame(BasicGame): + + __yakuza_exe_dir = 'media' + + Name = "Yakuza Kiwami Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza Kiwami" + GameShortName = "yakuzakiwami" + GameSteamId = [834530] + GameBinary = os.path.join(__yakuza_exe_dir, "YakuzaKiwami.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '2dpar', + 'auth_w64_e', + 'battlepar', + 'bootpar', + 'chara', + 'chara_arc', + 'chara_common', + 'cloth', + 'drama_scanner', + 'effect', + 'fighter', + 'flood', + 'flood.par', + 'fontpar', + 'hact', + 'hact.par', + 'light_anim', + 'loading', + 'map_par', + 'minigame', + 'module', + 'motion', + 'movie_w64_e', + 'pausepar', + 'reactorpar', + 'scenario', + 'shader', + 'soundcpk', + 'soundpar', + 'stage', + 'staypar', + 'wdr_par_c', + } diff --git a/games/game_yakuzakiwami2.py b/games/game_yakuzakiwami2.py new file mode 100644 index 0000000..be9dec0 --- /dev/null +++ b/games/game_yakuzakiwami2.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class YakuzaKiwami2Game(BasicGame): + + __yakuza_exe_dir = '' + + Name = "Yakuza Kiwami 2 Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza Kiwami 2" + GameShortName = "yakuzakiwami2" + GameSteamId = [927380] + GameBinary = os.path.join(__yakuza_exe_dir, "YakuzaKiwami2.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '3dlut', + 'artisan', + 'asset', + 'asset.par', + 'auth', + 'battle_lexus2', + 'boot', + 'camera', + 'chara', + 'chara.par', + 'cmm', + 'cmm.par', + 'cubemap_lexus2', + 'cubemap_lexus2.par', + 'db', + 'db.par', + 'drama_scanner', + 'drama_scanner.par', + 'effect', + 'effect.par', + 'entity_lexus2', + 'entity_lexus2.par', + 'entity_table', + 'flood', + 'font', + 'hact_lexus2', + 'light_anim_lexus2', + 'light_anim_lexus2.par', + 'lua', + 'lua.par', + 'map', + 'map.par', + 'minigame', + 'motion', + 'motion.par', + 'movie', + 'particle', + 'particle.par', + 'puid', + 'shader', + 'sound', + 'sound.par', + 'stage_lexus2', + 'stream', + 'talk_lexus2', + 'talk_lexus2.par', + 'ui', + 'ui.par', + } diff --git a/games/game_yakuzalikeadragon.py b/games/game_yakuzalikeadragon.py new file mode 100644 index 0000000..69ff398 --- /dev/null +++ b/games/game_yakuzalikeadragon.py @@ -0,0 +1,144 @@ +from __future__ import annotations + +import os + +import mobase +from PyQt5.QtCore import QFileInfo + +from ..basic_game import BasicGame +from .yakuza.yakuza_series import YakuzaGameModDataChecker, yakuza_check_rmm, yakuza_import_mods + + +class Yakuza6Game(BasicGame): + + __yakuza_exe_dir = os.path.join('runtime', 'media') + + Name = "Yakuza: Like a Dragon Support Plugin" + Author = "SutandoTsukai181" + Version = "1.0.0" + + GameName = "Yakuza: Like a Dragon" + GameShortName = "yakuzalikeadragon" + GameSteamId = [1235140] + GameBinary = os.path.join(__yakuza_exe_dir, "YakuzaLikeADragon.exe") + GameDataPath = os.path.join(__yakuza_exe_dir, 'mods', '_externalMods') + + def init(self, organizer: mobase.IOrganizer): + super().init(organizer) + self._featureMap[mobase.ModDataChecker] = YakuzaGameModDataChecker(self.__valid_paths) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_check_rmm(self, win)) + self._organizer.onUserInterfaceInitialized(lambda win: yakuza_import_mods(self, win)) + return True + + def executables(self) -> list[mobase.ExecutableInfo]: + return super().executables() + [mobase.ExecutableInfo( + "Ryu Mod Manager", + QFileInfo(self.gameDirectory().absoluteFilePath( + os.path.join(self.__yakuza_exe_dir, 'RyuModManager.exe'))) + ).withArgument('--cli')] + + def settings(self) -> list[mobase.PluginSetting]: + return super().settings() + [mobase.PluginSetting( + 'import_mods_prompt', + 'Check for mods to import from RMM mods folder on launch', + True + )] + + __valid_paths = { + '3dlut', + 'artisan', + 'asset', + 'asset.par', + 'auth', + 'auth_e', + 'battle', + 'boot', + 'camera', + 'chara', + 'chara.par', + 'cubemap_yazawa', + 'cubemap_yazawa.par', + 'db.yazawa.de', + 'db.yazawa.de.par', + 'db.yazawa.en', + 'db.yazawa.en.par', + 'db.yazawa.es', + 'db.yazawa.es.par', + 'db.yazawa.fr', + 'db.yazawa.fr.par', + 'db.yazawa.it', + 'db.yazawa.it.par', + 'db.yazawa.ja', + 'db.yazawa.ja.par', + 'db.yazawa.ko', + 'db.yazawa.ko.par', + 'db.yazawa.pt', + 'db.yazawa.pt.par', + 'db.yazawa.ru', + 'db.yazawa.ru.par', + 'db.yazawa.zh', + 'db.yazawa.zh.par', + 'db.yazawa.zhs', + 'db.yazawa.zhs.par', + 'effect', + 'effect.par', + 'entity_table', + 'entity_yazawa', + 'entity_yazawa.par', + 'flood', + 'font.yazawa', + 'font.yazawa.par', + 'grass', + 'hact_yazawa', + 'light_anim_yazawa', + 'light_anim_yazawa.par', + 'lua', + 'lua.par', + 'map', + 'map.par', + 'minigame', + 'motion', + 'motion.par', + 'mvsfd', + 'navimesh', + 'particle', + 'particle.par', + 'patch', + 'ps5', + 'puid.yazawa', + 'reflection', + 'shader', + 'sound', + 'sound.par', + 'sound_en', + 'sound_en.par', + 'stage', + 'stream', + 'stream_en', + 'system', + 'talk_yazawa', + 'talk_yazawa.par', + 'ui.yazawa.de', + 'ui.yazawa.de.par', + 'ui.yazawa.en', + 'ui.yazawa.en.par', + 'ui.yazawa.es', + 'ui.yazawa.es.par', + 'ui.yazawa.fr', + 'ui.yazawa.fr.par', + 'ui.yazawa.it', + 'ui.yazawa.it.par', + 'ui.yazawa.ja', + 'ui.yazawa.ja.par', + 'ui.yazawa.ko', + 'ui.yazawa.ko.par', + 'ui.yazawa.pt', + 'ui.yazawa.pt.par', + 'ui.yazawa.ru', + 'ui.yazawa.ru.par', + 'ui.yazawa.zh', + 'ui.yazawa.zh.par', + 'ui.yazawa.zhs', + 'ui.yazawa.zhs.par', + 'version', + } diff --git a/games/yakuza/__init__.py b/games/yakuza/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/games/yakuza/yakuza_series.py b/games/yakuza/yakuza_series.py new file mode 100644 index 0000000..4520487 --- /dev/null +++ b/games/yakuza/yakuza_series.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import os +import shutil +import time +import webbrowser +from typing import Optional + +import mobase +from PyQt5.QtCore import qWarning +from PyQt5.QtWidgets import QMessageBox + +from ...basic_game import BasicGame + + +def yakuza_check_rmm(plugin: BasicGame, win): + if not plugin.isActive(): + return + + rmm_path = os.path.join(plugin._gamePath, os.path.dirname(plugin.GameBinary), 'RyuModManager.exe') + + if not os.path.exists(rmm_path): + reply = QMessageBox.critical( + win, + 'Ryu Mod Manager Missing', + 'Ryu Mod Manager was not found in the game\'s directory. Mods will not work without it.\nOpen Ryu Mod Manager download page?', + QMessageBox.Yes | QMessageBox.Ignore, + QMessageBox.Yes + ) + + if reply == QMessageBox.Yes: + webbrowser.open('https://github.com/SutandoTsukai181/RyuModManager/releases/latest') + + +def yakuza_import_mods(plugin: BasicGame, win): + if not plugin.isActive() or (plugin._organizer.pluginSetting(plugin.name(), 'import_mods_prompt') is False): + return + + game_mods_path = os.path.join(plugin._gamePath, os.path.dirname(plugin.GameDataPath)) + + success_count = 0 + fail_count = 0 + + # Get the installed mods + modfolders = [x for x in os.listdir(game_mods_path) if os.path.isdir( + os.path.join(game_mods_path, x)) and x not in ('Parless', '_externalMods')] + + # Ask if we should migrate mods + reply = QMessageBox.question( + win, + 'Import Mods', + 'Do you want to import your Ryu Mod Manager mods to Mod Organizer?\nDoing so will move all the mods and delete them from the original directory.', + QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, + QMessageBox.Cancel + ) + + if reply == QMessageBox.Cancel: + # Just stop, and show the same prompt next time + return + + # If the reply is either Yes or No, then we don't want to show this prompt next time + plugin._organizer.setPluginSetting(plugin.name(), 'import_mods_prompt', False) + + if reply == QMessageBox.No: + QMessageBox.information( + win, + 'Import Mods', + 'Mods will NOT be imported. If you want to see this prompt again, you can change the option in the plugin settings.', + QMessageBox.Ok, + QMessageBox.Ok + ) + + return + + # Read mod list file and sort the mods accordingly + enabled_mods = None + modlist = os.path.join(os.path.dirname(game_mods_path), 'ModList.txt') + if os.path.exists(modlist) and os.path.isfile(modlist): + with open(modlist) as file: + # List of mods to enable after importing is done + enabled_mods = [(x[1:], x[0] == '<') for x in file.readline().rstrip('\n').split('|') if x.startswith('<') or x.startswith('>')] + ordered_mods, _ = zip(*enabled_mods) + enabled_mods = dict([x for x in enabled_mods if x[1]]) + + # Sort the folders according to the order in the list + modfolders.sort(key=(lambda k: (val := dict(zip(ordered_mods, range(len(ordered_mods)))).get(k)) + or (val if val is not None else len(ordered_mods)))) + + # -1 for the overwrite folder + org_mod_count = len(plugin._organizer.modList().allMods()) - 1 + + # Import mods + for mod_name in modfolders: + mod_path = os.path.join(game_mods_path, mod_name) + + try: + mod = plugin._organizer.createMod(mobase.GuessedString(mod_name)) + shutil.copytree(mod_path, mod.absolutePath(), dirs_exist_ok=True) + + # Should not call IOrganizer.modDataChanged() because all it does is start a refresh + # and it won't finish before the script is done anyway + + success_count += 1 + except Exception: + fail_count += 1 + qWarning(f'Could not properly import mod: {mod_name}') + + try: + shutil.rmtree(mod_path, ignore_errors=True) + except Exception: + qWarning(f'Could not remove mod after importing from path: {mod_path}') + + # 3 seconds seems appropriate + plugin._organizer.refresh(False) + time.sleep(3) + + for i, mod_name in enumerate(modfolders): + # Set each mod according to its ordered priority + p = org_mod_count + i + plugin._organizer.modList().setPriority(mod_name, p) + plugin._organizer.modList().setActive(mod_name, enabled_mods.get(mod_name, False)) + + # Manual refresh is needed for some reason + # Maybe something about the refresh not being able to finish until after the script is done? + # no matter how much time you sleep, it won't finish + if reply == QMessageBox.Yes: + QMessageBox.information( + win, + 'Success', + f'{success_count} mod(s) have been imported.\n' + + (f'{fail_count} mod(s) could not be imported.\n' if fail_count > 0 else '') + + '\nPlease refresh the mod list manually (Right click -> "Refresh")', + QMessageBox.Ok, + QMessageBox.Ok + ) + + +class YakuzaGameModDataChecker(mobase.ModDataChecker): + + def __init__(self, valid_paths: set): + super().__init__() + self._validPaths = valid_paths + + _validPaths: set + + def walk_tree(self, filetree: mobase.IFileTree) -> mobase.ModDataChecker.CheckReturn: + name = filetree.name().casefold() + + if name in self._validPaths: + return mobase.ModDataChecker.FIXABLE + + if filetree.isDir(): + for entry in filetree: + if self.walk_tree(entry) != mobase.ModDataChecker.INVALID: + return mobase.ModDataChecker.FIXABLE + + return mobase.ModDataChecker.INVALID + + def dataLooksValid(self, filetree: mobase.IFileTree) -> mobase.ModDataChecker.CheckReturn: + # Check for mods that were already installed + for entry in filetree: + if entry.name().casefold() in self._validPaths: + return mobase.ModDataChecker.VALID + + return self.walk_tree(filetree) + + def fix(self, filetree: mobase.IFileTree) -> Optional[mobase.IFileTree]: + name = filetree.name().casefold() + + if name in self._validPaths and filetree.parent() is not None: + return filetree.parent() + + # Keep looking for a valid path + if filetree.isDir(): + for entry in filetree: + result = self.fix(entry) + if result is not None: + return result + + return None