Add An MTP Import, new mod statut cache, fixes masives bugs and mores - #64
Open
mokapi48 wants to merge 1 commit into
Open
Add An MTP Import, new mod statut cache, fixes masives bugs and mores#64mokapi48 wants to merge 1 commit into
mokapi48 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Table of Contents
New Features
1. USB MTP Mod Import (PC ↔ Switch)
New files:
src/ModManagerGui/FrameGameBrowser/include/ModsMtpServer.hsrc/ModManagerGui/FrameGameBrowser/src/ModsMtpServer.cppsrc/ModManagerGui/FrameGameBrowser/include/TabImportMod.hsrc/ModManagerGui/FrameGameBrowser/src/TabImportMod.cppsrc/ThirdParty/mtp-server-nx/(entire vendored library, ~30 files)What it does:
A full MTP (Media Transfer Protocol) responder runs on the Switch, exposing the
sdmc:/mods/directory over USB. When the user connects the Switch to a PC via USB, the PC sees the Switch as an MTP storage device and can drag-and-drop mod folders (or.ziparchives) directly intosdmc:/mods/<TitleName>/.Key implementation details:
0x057e(Nintendo), PID0x4000Idle→Starting→Running→Stopping.zipfiles found in the mods root are automatically extracted using minizipappletSetAutoSleepDisabled(true)andappletSetMediaPlaybackState(true)TabImportMod UI:
A new Borealis tab ("Import Mod") is added between the "Game Browser" tab and the separator in
FrameRoot. It contains:Idle/Waiting for USB connection.../MTP connected — browse on your PC/StoppedwillDisappear)Metadata file hiding:
The vendored
SwitchMtpDatabase(custom implementation inmtp-server-nx) filters out SimpleModManager metadata files from the MTP view whenshowDebugMtpFilesisfalse:.smm_title_idmods_status_cache.txtthis_folder_config.txtmod_presets.confThis keeps the MTP view clean for end users.
2. Touchscreen Controls
Modified file:
src/Applications/SimpleModManager/src/SimpleModManager.cpp(~300 lines of new touch handling code)What it does:
Complete touch input support for all GUI interactions, enabling the app to be used without Joy-Cons attached.
Implementation:
hidInitializeTouchScreen()+hidGetTouchScreenStates()TouchState:None→Pressed→Held→ReleasedTabFrame→ active tab →ScrollView→BoxLayout→ListItem/Button, checking bounding boxes at each levelbrls::Application::getTopStackView()Processing loop:
processTouchInput()is called once per frame in the mainrunGui()loop, afterbrls::Application::frame().3. Battery & Clock Status Overlay
New files:
src/ModManagerGui/CoreExtension/include/SystemStatusOverlay.hsrc/ModManagerGui/CoreExtension/src/SystemStatusOverlay.cppWhat it does:
Draws the current battery percentage and clock time in the header bar of the application, using NanoVG directly.
Implementation details:
psmInitialize()→psmGetBatteryChargePercentage()timeGetCurrentTime(TimeType_LocalSystemClock, ...)→timeToCalendarTimeWithMyRule()std::localtime()TabFrame::draw()FrameRoot::draw()andFrameModBrowser::draw()(both overridedraw())SystemStatusOverlay::shutdown()callspsmExit()4. Delete Mods from SD Card (GUI)
Modified files:
src/ModManagerGui/CoreExtension/include/GuiModManager.hsrc/ModManagerGui/CoreExtension/src/GuiModManager.cppsrc/ModManagerGui/FrameModBrowser/include/TabModBrowser.hsrc/ModManagerGui/FrameModBrowser/src/TabModBrowser.cppWhat it does:
Users can now press Y on any mod in the mod browser to delete it entirely — both the mod source folder on the SD card and its installed files in the LayeredFS overlay.
Implementation:
Key::Yfor each modListItemwith action hint "Delete mod"canStartDeleteModFolderThread()prevents launching multiple deletions concurrently (enforces a cooldown via_lastDeleteModFolderFinishedMs_)deleteModFolderFunction()runs in a separate threadsdmc:/mods/<game>/<modname>/removeModInstalledFiles()to delete files that are byte-identical to the mod sourcefinalizeModfilesystemChanges()to re-check remaining mods and update their cachesrebuildUiFromSd()fully clears and repopulates the mod list from the SD card, re-syncing Borealis focus indices5. Orphan Installed-Mod Cleanup
Modified files:
src/ModManagerCore/include/ModManager.hsrc/ModManagerCore/src/ModManager.cppsrc/ModManagerGui/FrameModBrowser/include/FrameModBrowser.hsrc/ModManagerGui/FrameModBrowser/src/FrameModBrowser.cppsrc/ModManagerCore/include/ConfigHandler.h(new config toggle)src/ModManagerCore/src/ConfigHandler.cppWhat it does:
When a mod is deleted from the SD card outside of SMM (or its folder name changes), its installed files in the LayeredFS overlay become "orphans" — they no longer correspond to any mod folder. This feature detects such orphan files and offers to clean them up.
Implementation:
ModManager::refreshOrphanInstalledModList(): Scans all installed files in the game's LayeredFS overlay and compares them against every mod folder. Files that don't match any current mod are collected into_orphanInstalledModList_(vector ofOrphanInstalledModstructs, each with amodNameandapplyCache).claimOrphanInstalledFilesForMod(): When a mod reappears (e.g. re-copied to SD), its files are claimed back from the orphan list.removeOrphanInstalledModCache(): Clears orphan entries from the status cache file.GuiModManager::deleteOrphanInstalledModsFunction(): Background thread that iterates the orphan list and removes the files.FrameModBrowser::promptOrphanInstalledModsCleanup()automatically detects orphans when entering a game's mod browser and shows a dialog: "X orphan installed mod(s) detected. Clean up?"offer-orphan-installed-mod-cleanup(defaulttrue) inconfig.inilets users disable the prompt.6. Automatic Game Folder Discovery
Modified file:
src/ModManagerCore/src/Toolbox.cpp(from ~21 lines to ~300+ lines)What it does:
Instead of requiring users to manually create mod folders for each game, the fork automatically discovers all installed retail games on the system and creates their mod folder structure.
Implementation:
Toolbox::ensureInstalledGameModFolders()does the following on startup:nsListApplicationRecord()to enumerate all installed titleshasApplicationContentMeta()— confirms the title has actual contentisKnownHomebrewTitle()— excludes known homebrew (hbmenu, tinfoil, goldleaf, edizon, etc.)nsApplicationControlData/ \ : * ? " < > |), strips Windows reserved names (CON,PRN,AUX,NUL,COM1-COM9,LPT1-LPT9), trims whitespacesdmc:/mods/<GameName>/if it doesn't exist.smm_title_idmetadata file containing the title ID (for fast re-lookup without API calls)Helper functions added to Toolbox:
ensureModsRootFolder()sdmc:/mods/if missinggetGameFolderTitleId().smm_title_idfirst, falls back tolookForTidInSubFolders()getGameFolderIcon()g_gameIconCache)hasGameFolderIcon()trimFolderName()toUpperAscii()isWindowsReservedName()isLikelyRetailGameTitleId()formatTitleId()sanitizeGameFolderName()hasApplicationContentMeta()writeGameFolderTitleIdMetadata().smm_title_idfilecopyCachedIcon()getApplicationName()/getApplicationAuthor()isKnownHomebrewTitle()7. Per-File Mod Status Cache
Modified files:
src/ModManagerCore/include/ModManager.h(new structs and methods)src/ModManagerCore/src/ModManager.cppWhat it does:
Instead of comparing every mod file against the installed overlay every time (which is slow, especially over SD I/O), the fork maintains a persistent per-file status cache on disk (
mods_status_cache.txtinside each game folder).New data structures:
Extended
ApplyCache:totalFilesintmatchingFilesintdifferentFilesintmissingFilesintfileStatusCachestd::map<std::string, ModFileStatusCache>New methods:
updateModStatusInternal(forceRecheck_, showTerminalProgress_, dumpCache_)refreshModStatus()refreshAllModStatusCache()readGameStatusSummary()formatGameStatusSummary()"X mods | Y active / Z partial / W inactive"string8. Smart Game List Refresh (Signature-Based)
Modified files:
src/ModManagerCore/include/GameBrowser.hsrc/ModManagerCore/src/GameBrowser.cppWhat it does:
The game list is only rebuilt when something actually changed, avoiding expensive re-scans on every frame or tab switch.
Implementation:
buildGameListSignature()computes a hash from:stat())mods_status_cache.txt)refreshGameList(bool force_)compares the current signature against the cached one (_gameListSignature_). If they match andforce_is false, the rebuild is skipped entirely.refreshGameListTag(const std::string& gameFolder)allows updating a single game's status tag in the selector without rebuilding the entire list.9. Advanced Game List Sorting
Modified files:
src/ModManagerCore/include/ConfigHandler.hsrc/ModManagerCore/src/ConfigHandler.cppsrc/ModManagerCore/src/GameBrowser.cppsrc/ModManagerGui/FrameGameBrowser/src/TabGeneralSettings.cppNew sort modes added to
SortGameListenum:GameLaunchedpdmqryQueryPlayStatisticsByApplicationId)ModAddedPlayTimeLaunchCountNew
SortGameListDirectionenum:AscendingDescendingBackward compatibility: Old config values are automatically migrated:
LastPlayed→GameLaunched(Descending)FirstPlayed→GameLaunched(Ascending)LastModAdded→ModAdded(Descending)FirstModAdded→ModAdded(Ascending)UI: The sort dropdown now shows user-friendly display names (e.g. "Last Played" instead of raw enum values), and a new X button toggles between Ascending/Descending.
10. Game Status Summary Tags
What it does:
Each game in the game list now shows a subtitle tag summarizing its mod status (e.g.
"12 mods | 3 active / 2 partial / 7 inactive"), computed from the cache without doing file comparisons.Implementation:
ModManager::readGameStatusSummary()reads the cache file and counts mods by statusModManager::formatGameStatusSummary()produces the display stringTabGames::rebuildLayout()calls this for each game and sets it as theListItemsubtitleTabGames::refreshDisplayedGameStatus()can update a single game's tag11. Caching of Game Icons in Memory
Modified file:
src/ModManagerCore/src/Toolbox.cppWhat it does:
Game icons (loaded from NSP metadata via
nsApplicationControlData) are cached in two static maps:g_gameIconCachestd::vector<unsigned char>(raw JPEG data)g_gameIconAvailabilityCachebool(whether icon is available)This avoids redundant
nsAPI calls when the game list is rebuilt or refreshed.Modified / Improved Components
TabModBrowser — Full Rewrite
File:
src/ModManagerGui/FrameModBrowser/src/TabModBrowser.cpp(148 → 438 lines)Changes:
Key::Yaction on each mod item to trigger deletion with confirmation dialogupdateActionHint(brls::Key::X, "Disable")andupdateActionHint(brls::Key::Y, "Delete mod")rebuildUiFromSd()ListItems, restores focus to_focusModNameAfterDelete_removeDisplayedMod()resyncListItemFocusIndices()BoxLayoutfocus navigation afterremoveView()by resettingparentUserDatachild indices on all remaining childrengetFocusTargetBeforeDelete()draw()rewriteisTriggerRebuildModBrowser()) before callingScrollView::draw(), with guard against rebuilding while a view is disappearingupdateDisplayedModsStatus()applyCacheinstead of live file comparison. Displays"UNCHECKED"for mods without cached data. Handles"PARTIAL"prefix for proper orange coloring"No mods for this game are on your SD card."iMod < _modItemList_.size()guard and null checks on itemsTabGames — Lazy Rebuild & Focus Restoration
File:
src/ModManagerGui/FrameGameBrowser/src/TabGames.cpp(127 → 245 lines)Changes:
rebuildLayout(bool force_)GameBrowser::buildGameListSignature()changed. Filters out games without icons. UsesToolbox::getGameFolderIcon()(cached). Sets game status summary as subtitlewillAppear()draw()resyncListItemFocusIndices()findGameItem()ListItemby game folder namerefreshDisplayedGameStatus()restoreFocusAfterRebuild()FrameModBrowser — Orphan Detection & Status Overlay
File:
src/ModManagerGui/FrameModBrowser/src/FrameModBrowser.cppChanges:
draw()overrideSystemStatusOverlay::draw()to show battery/clock in mod browserpromptOrphanInstalledModsCleanup()offerOrphan-installed-mod-cleanupconfig is enabled)updateModStatus()call on mod browser open to prevent UI freezesresetOrphanCleanupPrompt()GameBrowser — Rewrite with Stats & Smart Refresh
File:
src/ModManagerCore/src/GameBrowser.cpp(180 → 542 lines)Changes:
init()rewriteGameSortEntrystructs with: last launch time, mod folder timestamps, play statistics (total play time, launch count viapdmqryQueryPlayStatisticsByApplicationId), mod status summaries. Filters games without iconssortGameEntries()buildGameListSignature()refreshGameList(bool force_)refreshGameListTag()getFolderIcon()Toolbox::getGameFolderIcon()(cached) instead of directnsAPI calls each timefillPlayStats()queriespdmqryQueryPlayStatisticsByApplicationIdfor play time and launch countresolvePresetForGame()determines active preset for a gamebuildSortTag()generates human-readable sort info for each game entryNew helper functions:
fillModTimestamps,fillPlayStats,sortNeedsPlayStats,parseTitleId,formatTimestamp,formatPlaytime,resolvePresetForGame,buildSortTag,toLowerAscii,hasSortValue,getSortValue.ModManager — Per-File Cache, Orphan Tracking, Status Summaries
File:
src/ModManagerCore/src/ModManager.cppChanges:
updateModStatusInternal()updateModStatus(). Now tracks per-file status inModFileStatusCache, supportsforceRecheck_(bypass cache),showTerminalProgress_(console output), anddumpCache_(write cache to disk)refreshModStatus()refreshAllModStatusCache()refreshOrphanInstalledModList()claimOrphanInstalledFilesForMod()removeOrphanInstalledModCache()readGameStatusSummary()formatGameStatusSummary()applyMod()/applyModList()ModFileStatusCacheand handle orphan cleanup during applyModFileStatusCache,ModStatusSummary(totalMods, activeMods, partialMods, inactiveMods, noFileMods, uncheckedMods),OrphanInstalledModToolbox — Title ID Resolution, Icon Cache, Folder Sanitization
File:
src/ModManagerCore/src/Toolbox.cpp(21 → ~300+ lines)Changes:
ensureModsRootFolder()sdmc:/mods/ensureInstalledGameModFolders().smm_title_idmetadatagetGameFolderTitleId().smm_title_idfile first, then falls back to subfolder searchgetGameFolderIcon()hasGameFolderIcon()ConfigHandler — New Sort Modes, Direction, MTP/Cleanup Toggles
Files:
src/ModManagerCore/include/ConfigHandler.h,src/ModManagerCore/src/ConfigHandler.cppNew enum values:
New config fields:
show-debug-mtp-filesboolfalseoffer-orphan-installed-mod-cleanupbooltruesort-game-list-directionSortGameListDirectionDescendingNew display name helpers:
getSortGameListDisplayName(),getSortGameListDirectionDisplayName(),getSortGameListSettingDisplayName()— return human-readable names for config UI.Backward compatibility: Old
LastPlayed/FirstPlayed/LastModAdded/FirstModAddedvalues are migrated to the newGameLaunched/ModAdded+ direction system.GuiModManager — Background Deletion, Cache Finalization
Files:
src/ModManagerGui/CoreExtension/include/GuiModManager.h,src/ModManagerGui/CoreExtension/src/GuiModManager.cppNew methods:
deleteModFolderFromSd()deleteOrphanInstalledMods()isBackgroundTaskRunning()canStartDeleteModFolderThread()startDeleteModFolderThread()bool(success/fail)startDeleteOrphanInstalledModsThread()boolremoveModInstalledFiles()cleanupInstalledFilesByRelativePaths()deleteOrphanInstalledModsPass()finalizeModfilesystemChanges()finishDeleteModFolderTask()New members:
_triggerRebuildModBrowser_,_deleteModFolderRunning_,_lastDeleteModFolderFinishedMs_New monitors:
ModDeleteFolderMonitor,ModFinalizeMonitor(for background thread completion)SimpleModManager (Main App) — Touch, MTP, Proper Shutdown
Files:
src/Applications/SimpleModManager/include/SimpleModManager.h,src/Applications/SimpleModManager/src/SimpleModManager.cppChanges:
runGui()signatureconst std::string& modsRootFolder_parameterToolbox::ensureModsRootFolder()andToolbox::ensureInstalledGameModFolders()before launching GUIhidInitializeTouchScreen()called on startupprocessTouchInput()called every frameSystemStatusOverlay::shutdown()+ModsMtpServer::shutdownForAppExit()+nsExit()nsInitialize()+ensureInstalledGameModFolders()beforeConsoleHandler::run(), andnsExit()afterSimpleModManagerConsole — ns Initialization
File:
src/Applications/SimpleModManagerConsole/src/SimpleModManagerConsole.cppChanges:
nsInitialize()before console handler initializationToolbox::ensureInstalledGameModFolders()call for console modensExit()after console handler exitsTabGeneralSettings — New UI Controls
File:
src/ModManagerGui/FrameGameBrowser/src/TabGeneralSettings.cppChanges:
show-debug-mtp-filesconfig)offer-orphan-installed-mod-cleanupconfig)README.md — New Sections
File:
README.mdAdded sections:
Build Changes
CMakeLists.txt modifications
src/Applications/SimpleModManager/CMakeLists.txt-lminizip -lz -lbz2to linker flags (ZIP extraction for MTP auto-extract)src/ModManagerGui/CoreExtension/CMakeLists.txtSystemStatusOverlay.cppto sourcessrc/ModManagerGui/FrameGameBrowser/CMakeLists.txtModsMtpServer.cppandTabImportMod.cppto sourcesVendored Dependencies
src/ThirdParty/mtp-server-nx/Entire MTP server library vendored (~30 files). Not present in the upstream repository.
Key components:
MtpServerMtpStorageSwitchMtpDatabaseshowDebugMtpFilesis falseUSBMtpInterfaceMtpObjectInfoHidden files (when
showDebugMtpFiles= false):.smm_title_id,mods_status_cache.txt,this_folder_config.txt,mod_presets.conf.