A cross-platform desktop tool (Windows, Linux, macOS) for SimCity 4 modders. It scans your
Plugins folder and flags files in your 075-my-plugins and 895-my-overrides folders that
duplicate content already present elsewhere in Plugins, so you can safely move them out of
the way.
Built with Avalonia UI (.NET 10).
The tool was developed with Claude's support and under human supervision
Prebuilt, self-contained binaries for Windows, Linux, and macOS (Apple Silicon) are published automatically on the Releases page — no .NET runtime install required. See Setup if you'd rather build from source.
This app runs natively on Windows, Linux, and macOS via Avalonia UI. Two things made this a clean port:
- The DBPF/TGI parsing (
Services/DbpfParsingService.cs,Services/DbpfFileSystemService.cs) is plain, portable C# reading raw file bytes directly - no external native or Windows-only library dependency at all. Models/andServices/(all scanning, comparison, moving, and sc4pac catalog logic) never referenced the UI framework, so none of it needed to change for this port - only the UI layer (MainWindow, dialogs, file/folder pickers) did.
A few platform-specific notes:
- Folder/file pickers use Avalonia's
IStorageProvider, which uses each OS's native picker - including the XDG desktop portal on Linux. - Dark title bar: the Windows-only
DwmSetWindowAttributecall that colors the native title bar is guarded byOperatingSystem.IsWindows()and does nothing on Linux/macOS, where the window chrome already follows the app's dark theme through Avalonia itself. - Opening a mod's page (
Open Pagebutton) usesProcess.Start(..., UseShellExecute = true), which resolves to the OS's default URL handler on every platform (xdg-openon Linux,openon macOS, the default browser association on Windows).
- Recursively scans a Plugins folder for
.dat,.sc4lot,.sc4model, and.sc4descfiles - Detects duplicate content by comparing TGI (Type-Group-Instance) identifiers
- Automatically excludes non-content bookkeeping TGI entries (e.g. the DBPF directory record) that would otherwise cause false positives
- Extra file-name check for
895-my-overrides, since that folder is meant to hold intentional overrides that share a TGI with the original file by design - Low memory footprint, suitable for machines with as little as 4–8 GB of RAM
- Live, color-coded log and progress bars for both the scan and the comparison step
- Moves (never deletes) duplicates to a folder of your choice, preserving the original subfolder structure
- Cancel support for long-running scans
- Check sc4pac Catalog: scans
075-my-plugins/895-my-overrides, matches their TGIs against the community-run SC4 Prop Texture Catalog, and lists any sc4pac package that already provides that content — so you can install it properly instead of keeping a loose manual copy - Cross-checks results against sc4pac's own
sc4pac-plugins.json/sc4pac-plugins-lock.json(auto-detected from the default profile location), so packages you've already installed — explicitly or as a dependency — and the false positives that come from two different mods sharing the same TGI, are filtered out automatically - Export sc4pac JSON: saves every package found by the catalog check to a
{ "explicit": [...] }file, the same format sc4pac itself uses, ready to hand off to sc4pac
- Windows 10/11, Linux, or macOS
- .NET 10 SDK to build, or the .NET 10 Desktop Runtime to run a published build — no platform-specific workload needed
- Internet access the first time you use Check sc4pac Catalog (to download
Catalog.db, ~22 MB); the scan/move features work fully offline
Prebuilt binaries are available from the Releases page — most users won't need to build from source at all. To build it yourself instead:
-
Clone this repository.
-
Build and run:
dotnet build dotnet run
To publish a self-contained build for a specific OS:
dotnet publish -c Release -r win-x64 --self-contained
dotnet publish -c Release -r linux-x64 --self-contained
dotnet publish -c Release -r osx-x64 --self-contained # osx-arm64 for Apple Silicon-
Browse... – select your SimCity 4
Pluginsfolder (the one containing075-my-pluginsand895-my-overrides). -
sc4pac-plugins.json – auto-detected on startup from sc4pac's default profile location (
%AppData%\io.github.memo33\sc4pac\config\profiles\...); use Browse... next to it if sc4pac isn't installed there, uses a different profile, or wasn't detected. This is optional, but strongly recommended before running Check sc4pac Catalog (step 5) — see Filtering out already-installed packages. -
Scan TGIs – scans the whole folder tree and identifies duplicates. Files are color-coded by origin, and detected duplicates are highlighted.
-
Move Duplicates – choose a destination folder; all detected duplicates are moved there (with their relative subfolder structure preserved), freeing them from
Pluginswithout permanently deleting anything. -
Check sc4pac Catalog – scans only
075-my-plugins/895-my-overridesand checks their TGIs against the SC4 Prop Texture Catalog (downloaded automatically on first use, then cached locally). Any matching sc4pac package is listed with:- Copy ID – copies the sc4pac package identifier (
group:package-name) - Copy sc4pac add – copies the ready-to-run
sc4pac add group:package-namecommand - Open Page – opens the mod's actual download/info page in your browser, when known
sc4pac itself doesn't expose a documented way to trigger an install from an external link, so installing is a two-step, always-accurate process: copy the ID (or the command) here, then paste it into sc4pac's own Find Packages search (or run the copied command in a terminal).
Export sc4pac JSON (button above the results list) saves every package found so far to a
{ "explicit": ["group:package-id", ...] }file — the same format sc4pac itself uses for its explicit-packages list — so you can hand it straight to sc4pac and install everything found in one go, instead of adding packages one by one. - Copy ID – copies the sc4pac package identifier (
A file inside 075-my-plugins is flagged as a duplicate if it shares at least one TGI with a
file in the main Plugins folder.
A file inside 895-my-overrides is flagged as a duplicate only if both are true:
- it shares at least one TGI with a file in
Plugins, and - a file with the same name already exists in
Plugins.
This extra check exists because 895-my-overrides is meant to hold intentional overrides,
which by design share a TGI with the file they replace — a TGI match alone there is expected,
not a mistake.
Duplicates are only ever compared against the main Plugins folder; files within
075-my-plugins and 895-my-overrides are never compared against each other.
Catalog.db is a SQLite database built and maintained by the
SC4 Prop Texture Catalog project. It's
downloaded directly from GitHub on first use and cached at
%LocalAppData%\SC4ModMigrationAssistant\Catalog.db (delete that file to force a fresh
download). It maps individual TGIs to the file they came from, and that file to the sc4pac
package(s) that ship it.
For every distinct TGI found in 075-my-plugins / 895-my-overrides, the app looks up whether
that exact TGI (or, for entries where the catalog only records Group+Instance, that Group and
Instance) belongs to a known package, then groups the results by package so you see one entry
per mod rather than one per file.
There's no publicly documented way to make an external link launch an install directly inside sc4pac, so results are given as a package ID / CLI command you paste into sc4pac (see Usage) plus, when available, a link to the mod's real page — not a fabricated "click to install" link that might not actually work.
Two things can make the catalog check suggest a package you don't actually need:
- You already installed it via sc4pac — either explicitly, or as an automatic dependency
of something else (e.g.
nam:bridges, pulled in as a dependency of NAM). The local file in075/895still exists (maybe left over from before you switched to sc4pac), so its TGI still matches the catalog, even though sc4pac is already managing that content. - A TGI is shared by more than one catalog package. Some resources (shared dependencies, assets reused across packs) are indexed under more than one package in the catalog, so a single local file can resolve to several candidates - only one of which may actually be relevant.
To avoid both, the app reads sc4pac's own profile files (see sc4pac's documentation, "Details" section) - both are looked for in the same folder, whichever one is selected/detected:
sc4pac-plugins.json's"explicit"array — packages you added yourself.sc4pac-plugins-lock.json's"installed"array — every package actually installed, dependencies included. This is the one that matters most:sc4pac-plugins.jsonalone would have missednam:bridgesin the example above, since it only tracks explicit additions.
Both lists are combined, and for every TGI, all of its candidate packages are skipped if any of them is in that combined list. This handles case 1 directly (now covering dependency-only packages too), and also resolves most instances of case 2: if the TGI's "correct" package is already installed, the other candidate(s) that merely happen to share the same TGI are false positives anyway and get filtered out along with it.
sc4pac-plugins.json is auto-detected on startup from sc4pac's default location
(%AppData%\io.github.memo33\sc4pac\config\profiles\...\sc4pac-plugins.json, picking the most
recently modified one if you have several profiles); sc4pac-plugins-lock.json is then looked
for automatically right next to it. Use Browse... in the UI to point at either file manually
if auto-detection doesn't find them (e.g. a non-default profile location) - whichever one you
pick, the app still looks for its sibling in the same folder. If the lock file can't be found,
the catalog check still runs, but the log will warn that dependency-only packages may still be
suggested.
A few constants in Services/DbpfScanService.cs can be adjusted:
| Constant | Purpose | Default |
|---|---|---|
DbpfExtensions |
File extensions treated as DBPF files | .dat, .sc4lot, .sc4model, .sc4desc |
Overrides075FolderName / Overrides895FolderName |
Override folder names to look for | 075-my-plugins, 895-my-overrides |
ExcludedTgis |
TGIs excluded from comparison as non-content entries | DBPF Directory record, default LD entry |
ProgressReportInterval |
How often the scan progress bar updates | every 100 files |
The catalog download URL and local cache path are in
Services/CatalogDatabaseService.cs (CatalogDownloadUrl, CacheFilePath).
.github/workflows/
ci.yml Build check on every pull request (Windows/Linux/macOS)
release.yml Packages and publishes a GitHub Release on push to master or a v*.*.* tag
SC4ModMigrationAssistant.csproj
LICENSE.md
Program.cs Avalonia entry point (BuildAvaloniaApp / Main)
App.axaml / App.axaml.cs FluentTheme (Dark) + Synthwave accent/brushes + control styles
MainWindow.axaml / MainWindow.axaml.cs
LogEntryView.cs
Dialogs/
MessageDialog.axaml / .axaml.cs Custom OK / Yes-No modal (Avalonia has no built-in MessageBox)
Models/
SourceCategory.cs
TgiKey.cs
ScannedFile.cs
CatalogScanFile.cs
Sc4pacMatch.cs
Sc4pacExplicitPackages.cs
LogColor.cs
Services/
DbpfFileSystemService.cs
DbpfParsingService.cs
DbpfScanService.cs
DuplicateMoverService.cs
CatalogDatabaseService.cs
Sc4pacLookupService.cs
Sc4pacPluginsFileService.cs
Issues and pull requests are welcome.
This project is licensed under the MIT License
This tool moves files out of your Plugins folder based on automated heuristics. Back up your Plugins folder before running it, and review the log before moving files if you're unsure.

