-
Notifications
You must be signed in to change notification settings - Fork 0
ARCHITEKTUR
Letzte Aktualisierung: 2026-03-21 Zweck: Diese Datei beschreibt wie Anvil aufgebaut ist. Sie ist die verbindliche Referenz für alle Entwickler (CC, Claude, Agents). Bevor Code geändert wird, muss diese Datei gelesen werden.
Anvil Organizer ist ein nativer Linux Mod Manager, inspiriert von MO2 (Mod Organizer 2). Er verwaltet Mods für mehrere Spiele (Cyberpunk 2077, Fallout 4, Witcher 3, RDR2, BG3).
Grundprinzip wie MO2: Mods werden NIEMALS direkt ins Game-Verzeichnis kopiert. Sie liegen in einem eigenen Verzeichnis (.mods/) und werden beim Spielstart per Symlink ins Game-Verzeichnis verlinkt. Nach dem Spielende werden die Symlinks entfernt. Das Game-Verzeichnis bleibt sauber.
| Aspekt | MO2 (Windows) | Anvil (Linux) |
|---|---|---|
| Deploy-Mechanismus | USVFS (Virtual File System, DLL-Injection) | Physische Symlinks + JSON-Manifest |
| Dateien im Game-Dir | Nie (nur virtuell) | Symlinks (zeigen auf .mods/) |
| Frameworks (CET, F4SE) | User installiert manuell | Anvil kopiert direkt ins Game-Dir |
| Nach Game-Ende | VFS wird automatisch beendet | Symlinks bleiben bis Purge |
WICHTIG: Anvil kann kein USVFS nutzen (Linux). Stattdessen werden physische Symlinks erstellt. Das Ergebnis ist äquivalent — das Game sieht die Mod-Dateien im richtigen Verzeichnis.
~/.anvil-organizer/instances/<Spielname>/
├── .anvil.ini # Instanz-Konfiguration
├── .mods/ # ALLE Mods liegen hier
│ ├── ModName1/ # Jede Mod = ein Ordner
│ │ ├── archive/pc/mod/ # Mod-Dateien in korrekter Struktur
│ │ │ └── mod_datei.archive
│ │ └── meta.ini # Mod-Metadaten
│ ├── ModName2/
│ │ ├── r6/scripts/ # Script-Mods
│ │ │ └── script.reds
│ │ └── meta.ini
│ ├── naked_separator/ # Separator (Trenner)
│ │ └── meta.ini
│ └── ...
├── .profiles/ # Profile
│ ├── modlist.txt # GLOBALE Mod-Reihenfolge (für alle Profile)
│ ├── Default/
│ │ └── active_mods.json # Welche Mods aktiv sind (pro Profil)
│ ├── Vanilla/
│ │ └── active_mods.json
│ └── ...
├── .downloads/ # Heruntergeladene Archive
├── .overwrite/ # (Placeholder, noch nicht funktional)
├── .deploy_manifest.json # Tracking aller erstellten Symlinks
├── .modindex.json # Zwischengespeicherte Dateilisten je Mod
└── categories.json # Kategorien
~/.local/share/Steam/steamapps/common/Cyberpunk 2077/
├── archive/pc/mod/ # Hier landen Symlinks für .archive-Dateien
│ └── mod_datei.archive → ../../.mods/ModName/archive/pc/mod/mod_datei.archive
├── r6/scripts/ # Hier landen Symlinks für Scripts
│ └── script.reds → ../../.mods/ModName/r6/scripts/script.reds
├── bin/x64/plugins/ # Frameworks (ECHTE KOPIEN, nicht Symlinks)
│ └── cyber_engine_tweaks.asi # CET — direkt kopiert
├── red4ext/ # RED4ext — direkt kopiert
└── ... # Original-Spieldateien
Jede Mod in .mods/ MUSS die gleiche Ordnerstruktur haben wie sie im Game-Verzeichnis landen soll.
Beispiel für eine Cyberpunk-Mod:
.mods/Blur Begone/
├── archive/pc/mod/ # ← DIESE Struktur ist PFLICHT
│ └── blur_begone.archive
└── meta.ini
Der Deployer erstellt einen Symlink:
Game-Dir/archive/pc/mod/blur_begone.archive → .mods/Blur Begone/archive/pc/mod/blur_begone.archive
FALSCH wäre:
.mods/Blur Begone/
├── blur_begone.archive # ← FALSCH! Datei liegt direkt im Root
└── meta.ini
Das würde einen Symlink direkt im Game-Root erstellen — das zerstört das Game-Verzeichnis.
Die _flatten_single_subfolder()-Funktion darf NIEMALS die Mod-Ordnerstruktur verändern wenn sie Game-relevante Unterordner enthält (archive/, bin/, r6/, red4ext/, mods/, engine/).
-
silent_purge()— Entfernt alle alten Symlinks (liest Manifest) -
silent_deploy()— Erstellt neue Symlinks für alle aktiven Mods - Spiel wird gestartet
Für jede aktive Mod (reversed, höchste Priorität zuletzt):
Für jede Datei in .mods/ModName/:
Berechne relativen Pfad (z.B. archive/pc/mod/datei.archive)
Ziel = Game-Dir + relativer Pfad
Wenn Ziel eine echte Datei ist → ÜBERSPRINGE (Safety Rule, AUSSER Frameworks)
Wenn Ziel ein Symlink ist → Entferne (niedrigere Priorität)
Prüfe ob Pfad in GameCopyDeployPaths liegt → JA: Kopiere (shim_copy) / NEIN: Symlink
Erstelle Symlink oder Kopie: Ziel → Quelle in .mods/
Hinweis: Es gibt keinen Whitelist-Filter für Ordnernamen. Alle Dateien aus aktivierten Mods werden deployed (wie in v0.1.0). Metadata-Dateien (meta.ini, codes.txt) werden übersprungen.
Manche Programme (z.B. CET's Lua VM) können Linux-Symlinks nicht durch Wine's S:-Drive folgen. Für diese Pfade werden Dateien als echte Kopien statt Symlinks deployed.
- Typ im Manifest:
shim_copy(wird beim Purge entfernt) - Konfiguration pro Game-Plugin:
GameCopyDeployPaths = ["bin/x64/plugins/cyber_engine_tweaks"] - Gleiche Purge-Logik wie der F4SE Proton Shim
- modlist.txt: Erste Mod = höchste Priorität (oben in GUI)
- Deployer reversed die Liste intern → "last wins"
- Bei Dateikonflikten gewinnt die Mod mit der höchsten Priorität
- Liest
.deploy_manifest.json - Entfernt alle Symlinks die auf
.mods/zeigen - Entfernt
shim_copy-Dateien (CET-Lua-Mods, Proton-Shims) - Entfernt leere Verzeichnisse die beim Deploy erstellt wurden
- Framework-Kopien (type: "copy") werden NICHT entfernt
Frameworks (CET, RED4ext, ArchiveXL, etc.) werden ANDERS behandelt als normale Mods:
- Sie werden direkt ins Game-Verzeichnis KOPIERT (nicht per Symlink)
- Sie werden beim Purge NICHT entfernt
- Sie gehören NICHT in
.mods/ - Sie gehören NICHT in die modlist.txt
- Sie werden im Downloads-Tab NICHT als "installiert" markiert wenn ein .mods/-Ordner existiert
- Sie werden im Frameworks-Bereich angezeigt (unten in der Mod-Liste)
- Die Erkennung ob ein Archiv ein Framework ist geschieht über
is_framework_mod()mit Datei-Patterns - Frameworks dürfen echte Dateien im Game-Verzeichnis überschreiben (Safety-Check wird bypassed)
- Der
root/-Prefix (MO2 RootBuilder-Pattern) wird automatisch gestrippt - Frameworks werden IMMER deployed, unabhängig von
active_mods.json(überGameDirectInstallMods) - Bei Neuinstallation eines Frameworks kommt ein Update-Dialog zur Bestätigung
WICHTIG: Die Framework-Erkennung muss VOR dem Flatten des Archivs stattfinden, weil Flatten die Patterns zerstören kann.
WICHTIG: Framework-Reinstall (_fw_reinstall) nutzt den konfigurierten Downloads-Pfad und durchsucht Unterordner (nicht nur .downloads/).
Ein Preset (z.B. ACU bei Cyberpunk) ist eine Einstellungsdatei, die ein
Framework einliest — keine Mod im ueblichen Sinn. Anvil baut daraus trotzdem
eine gewoehnliche Mod in .mods/, mit dem Zielpfad schon im Ordner:
.mods/ACU-Preset - Grace (female)/
bin/x64/plugins/cyber_engine_tweaks/mods/
AppearanceChangeUnlocker/character-presets/female/Grace.preset
Gruende: Der gewoehnliche Deploy bringt sie ohne Sonderweg an die richtige Stelle, und ein Update des Frameworks wirft sie nicht weg — was passierte, schriebe man sie in dessen Ordner hinein.
- Presets stehen in der modlist.txt und in active_mods.json wie jede
andere Mod. Ohne Eintrag in der modlist.txt werden sie nie ausgerollt
(
mod_deployer.deploy()liest die Liste). -
Nur die Anzeige ist anders: sie werden aus der Mod-Liste herausgefiltert
und in einem eigenen Bereich darunter gezeigt (Gegenstueck zum
Frameworks-Bereich). Zustaendig ist
MainWindow._split_presets(). - Die Erkennung entscheidet der Inhalt, nicht der Name und nicht der
Trenner:
character_presets.is_preset_mod()verlangt, dass alle Dateien der Mod die Preset-Endung tragen UND unter dem Zielpfad der Preset-Art liegen. Eine Mod mit zusaetzlichem.archivegreift ins Spiel ein und bleibt eine gewoehnliche Mod. - Welche Preset-Arten ein Spiel kennt, sagt
plugin.get_preset_kinds(). Leer heisst: der Bereich existiert dort gar nicht. - Die Erkennung liest die zwischengespeicherte Dateiliste aus
.modindex.json(siehe 10.6). Ist der Index leer oder veraltet, erscheint das Preset wieder in der Mod-Liste — ein bewusster, unschaedlicher Rueckfall. - Von Anvil selbst angelegte Dateien (
meta.ini,codes.txt,fomod_choices.json) und Punktdateien zaehlen bei der Erkennung nicht mit. Die Liste muss zumod_deployer._SKIP_FILESpassen; ein Test haelt beide Mengen gleich. - Beim Umsortieren haengt ein Preset an seinem vorherigen sichtbaren
Vorgaenger (
merge_hidden_order()), nicht am Trenner. Zieht man ausgerechnet die Mod ueber ihm woanders hin, wandert es mit. Fuers Ausrollen folgenlos — Presets kollidieren mit nichts. - Im Bereich gibt es nur Ein/Aus, Ordner oeffnen und Loeschen. Umbenennen, Eigenschaften, Kategorien und das Verschieben in der Reihenfolge sind fuer Presets nicht erreichbar. Bewusste Einschraenkung, kein Versehen.
# Managed by Anvil Organizer v2
+wichtige Reihenfolge_separator
+Preem Fixes - Cloth
+Blur Begone
+Body_separator
+VTK Vanilla HD - Head
- Erste Zeile = höchste Priorität (oben in GUI, gewinnt Konflikte)
- Trenner stehen VOR ihren Mods (gleiche Reihenfolge wie GUI)
-
+Prefix = Mod ist in der Liste (Aktiv-Status kommt aus active_mods.json) - Frameworks stehen NICHT in der modlist.txt
["Blur Begone", "Preem Fixes - Cloth", "VTK Vanilla HD - Head"]- Set von Mod-Ordnernamen die aktiv sind
- Muss bei JEDEM Rename und Delete in ALLEN Profilen aktualisiert werden
- Ordner in
.mods/mit Suffix_separator(z.B.naked_separator/) - Werden vom Deployer ÜBERSPRUNGEN (keine Symlinks)
- Können nicht aktiviert/deaktiviert werden
- Dienen nur zur visuellen Gruppierung in der GUI
- Kompatibel mit MO2-Format
- NIEMALS Mod-Dateien direkt ins Game-Verzeichnis kopieren (nur Symlinks, außer Frameworks und GameCopyDeployPaths)
-
NIEMALS die Ordnerstruktur in
.mods/verändern (Flatten nur für den obersten Wrapper-Ordner des Archivs, NICHT für Game-Verzeichnisse wie archive/, bin/, r6/) -
NIEMALS Frameworks in
.mods/oder modlist.txt ablegen - NIEMALS modlist.txt ändern ohne auch active_mods.json zu aktualisieren (bei Rename/Delete)
- NIEMALS Legacy-API (per-Profile modlist.txt) verwenden — immer globale API
- NIEMALS den Deploy-Mechanismus (Symlinks, Kopien, Manifest, Purge, Frameworks) ändern ohne ausdrückliche Zustimmung von Marc. Das schließt ein: mod_deployer.py, GameCopyDeployPaths, GameDirectInstallMods, install_framework(), die Purge-Logik, und alles was bestimmt wie Dateien ins Game-Verzeichnis gelangen.
- NIEMALS die Mod-Ordnerstruktur durch Flatten zerstören
-
NIEMALS einen Eintrag, der nur aus der ANZEIGE genommen wird (gesperrte Frameworks, Presets), beim Umsortieren seine Position in der modlist.txt verlieren lassen — siehe
merge_hidden_order() -
NIEMALS Code ändern ohne vorher MO2-Referenz zu lesen (
/home/mob/Projekte/Fremd-Mod Manager/mo2-referenz/src/)
- Physisches Deploy statt VFS — Linux hat kein USVFS. Symlinks sind die Alternative.
- Getrennte Dateien — modlist.txt (Reihenfolge) + active_mods.json (Aktiv-Status). Ermöglicht globale Reihenfolge mit profil-spezifischem Aktiv-Status.
- modlist.txt nicht invertiert — Einfacher zu lesen. Deployer reversed intern.
- Framework Direct-Install — MO2 verwaltet keine Frameworks. Anvil kopiert sie ins Game-Dir. 4b. Eigene Bereiche statt einer Liste — MO2 zeigt jede Mod in einer einzigen Liste und filtert hoechstens. Anvil zieht ganze Mod-Klassen (Frameworks, Charakter-Presets) in eigene Bereiche unter der Liste.
- Persistent Deploy — Symlinks bleiben nach Game-Ende bestehen (kein VFS das beendet wird).
-
Zwischengespeicherte Dateilisten — MO2 liest den Mod-Baum bei jedem Refresh neu
(
DirectoryRefresher::refresh()). Anvil merkt sich die Dateiliste je Mod in.modindex.json, sonst dauert der Start bei mehreren hundert Mods zu lange. Ob eine Mod neu eingelesen werden muss, entscheidet ein Abdruck ueber alle Ordner der Mod (Pfad + Aenderungszeit, blake2b). Die Zeit des Wurzelordners allein genuegt nicht — eine Datei, die in einem Unterordner umbenannt wird, aendert sie nicht. Nicht erkannt wird ein an Ort und Stelle ueberschriebener Dateiinhalt; die Dateiliste stimmt dann trotzdem, nur die Groessenangabe nicht. Der Deployer prueft zusaetzlich vor jeder Mod (ModIndex.refresh()) und meldet ueberDeployResult.missing_sources, wenn eine Mod nicht vollstaendig ins Spiel gelangt ist. Das blockiert den Spielstart bewusst nicht.
Der MO2-Quellcode liegt in /home/mob/Projekte/Fremd-Mod Manager/mo2-referenz/src/. Wichtige Dateien:
| MO2-Datei | Anvil-Äquivalent | Beschreibung |
|---|---|---|
modlist.cpp |
mod_list_io.py |
modlist.txt lesen/schreiben |
modinforegular.cpp |
mod_entry.py |
Mod-Metadaten, Rename |
modlistviewactions.cpp |
mainwindow.py |
Kontextmenü-Aktionen |
usvfsconnector.cpp |
mod_deployer.py |
Deploy-Mechanismus |
installationmanager.cpp |
mod_installer.py |
Mod-Installation |
profile.cpp |
Profil-Logik in mainwindow.py
|
Profil-Verwaltung |
REGEL: Bevor Code an Mod-Verwaltung, Deploy, oder modlist.txt geändert wird, MUSS die entsprechende MO2-Datei gelesen werden.
- Anvil Organizer
- Deployment Methods & OverlayFS
- Installation
- Storage Management
- Ghost Recon Breakpoint
- Create Game Plugin
- Migration from Windows
- Proton Tools