
Ein Disassembler und experimenteller Decompiler für TLOU2 DC Scripts.
dconstruct ist ein Reverse-Engineering-Werkzeug für die DC-Script-Dateien, die in The Last of Us Part II verwendet werden. Es verfügt über einen Disassembler und einen Decompiler.
Es gibt .asm-Dateien aus, die die disassemblierten Strukturen und den Bytecode enthalten, sowie .dcpl-Dateien (DC Pseudo Language), die C-ähnlichen Pseudocode enthalten.
Sie können auch Änderungen an Dateien über die Befehlszeile vornehmen, einschließlich des Ersetzens ganzer Strukturen mit geringem Aufwand. Dies macht das Erstellen von Mods, die einfach ein paar Werte in den .bin-Dateien ändern, extrem einfach.
.bin-Datei im Spiel dauert nur wenige Sekunden-e-Flag, Erstellen neuer Dateien, die Sie für Mods verwenden könnenZunächst wird empfohlen, das entpackte dconstruct-Verzeichnis an einen sicheren Ort zu verschieben, z.B. C:\Program Files.
Um dconstruct so benutzerfreundlich wie möglich zu gestalten, wird empfohlen, das .\bin-Verzeichnis innerhalb des dconstruct-Ordners zu Ihrem PATH hinzuzufügen. Weitere Informationen finden Sie hier oder befolgen Sie diese kurzen Schritte:
.\bin-Verzeichnis ein. Es sollte in etwa so aussehen:
\bin endet und NICHT mit \dconstruct.dconstruct --about ein. Sie sollten eine Ausgabe des Programms sehen und keine Fehlermeldung erhalten.Führen Sie einen Befehl wie diesen in der Befehlszeile aus, um Ihre erste disassemblierte Datei zu generieren:```shell dconstruct my_bin_file.bin
Dies gibt dann eine Datei mit dem Namen `my_bin_file.bin.asm` im selben Verzeichnis wie Ihre Eingabedatei aus. Sie können diese Datei dann mit einem Text-/Code-Editor öffnen. Ich würde die Verwendung von etwas wie VSCode empfehlen, das erweiterte Suchfunktionen bietet und gut mit großen Dateien umgehen kann. Der standardmäßige Windows-Editor wird nicht empfohlen.
Um eine Datei zu dekompilieren, fügen Sie das `--decompile`-Flag beim Ausführen des Befehls hinzu.
# Command line arguments
- `-i` - Eingabedatei oder -ordner. Kann weggelassen werden, wenn der Eingabepfad als erstes Argument übergeben wird.
- `-o` - Ausgabepfad. Wenn Ihr Eingabepfad ein Ordner ist, darf dies keine Datei sein. Wenn keine Ausgabe angegeben wird, wird die .txt-Datei neben der Eingabedatei abgelegt. Wenn die Eingabe ein Ordner ist und keine Ausgabe angegeben wird, erstellt das Programm ein "output"-Verzeichnis im aktuellen Arbeitsverzeichnis und legt alle Dateien darin ab.
- Die sidbase wird aus `sidbase.bin` geladen, das sich neben der ausführbaren Datei befindet.
- `--no_decompile` - gibt keinen dekompilierten Pseudocode in eine .dcpl-Datei aus. Die Datei wird neben der .asm-Datei abgelegt. Standardmäßig ist dies deaktiviert.
- `--no_optimize` - optimiert und bereinigt den dcpl-Code nicht. Beinhaltet das Inlinen von Funktionsaufrufen, das Entfernen ungenutzter Variablen, das Umwandeln kompatibler for-Schleifen in foreach-Schleifen und das Umwandeln einiger if-else-Ketten in match-Ausdrücke.
- `--pascal_case` - wandelt die Funktionsnamen des Spiels in Pascal-Schreibweise in der dcpl-Ausgabe um, z.B. get-boolean -> GetBoolean.
- `--graphs` - gibt .svg-Dateien mit Kontrollflussgraphen für alle dekompilierten Funktionen aus. Jede .bin-Datei erhält einen eigenen Ordner mit all ihren Graphen. Dies **verlangsamt** die Dekompilierungsgeschwindigkeit erheblich, daher wird es nicht empfohlen, wenn Sie eine große Anzahl von Dateien gleichzeitig dekompilieren.
- `--emit_once` - verhindert, dass dieselbe Struktur zweimal in der Disassemblierung ausgegeben wird. Wenn eine Struktur mehrfach vorkommt, wird nur die erste Instanz vollständig ausgegeben, und alle weiteren Vorkommen werden durch ein `ALREADY_EMITTED`-Tag ersetzt. Dies kann die Dateigröße erheblich reduzieren.
- `-e` - eine Bearbeitung vornehmen. Weitere Informationen im folgenden Abschnitt.
- `--edit_file` - eine Bearbeitungsdatei bereitstellen. Eine Bearbeitungsdatei enthält eine Bearbeitung pro Zeile. Es wird dieselbe Syntax wie beim -e-Flag verwendet.
# Was ist ein Disassembler?
Ein [Disassembler](https://en.wikipedia.org/wiki/Disassembler) ist ein Werkzeug, das binäre Anweisungen (auch bekannt als [Bytecode](https://en.wikipedia.org/wiki/Bytecode) oder [Maschinencode](https://en.wikipedia.org/wiki/Machine_code)) liest und jede in eine für Menschen lesbare Version übersetzt, die als [Mnemonic](https://en.wikipedia.org/wiki/Assembly_language#Mnemonics) bezeichnet wird. Disassembler versuchen im Allgemeinen nicht, viel über die Bedeutung dieser Anweisungen zu interpretieren, sondern wandeln sie lediglich 1-1 in ihre lesbaren Versionen um. Zum Beispiel die Anweisungen:```arm
15 00 00 00
4A 01 01 00
43 31 01 00
1C 00 00 01
werden in die folgenden menschenlesbaren Versionen zerlegt:```arm LookupPointer r0, 0 LoadStaticU64Imm r1, 1 Move r49, r1 CallFf r0, r0, 1
Alle Zahlen im Bytecode sind in [hexadezimaler](https://en.wikipedia.org/wiki/hexadecimal) Schreibweise angegeben. Die erste Spalte in jeder Zeile stellt den `Opcode` dar, also den Befehlstyp, der ausgeführt wird. Die nächste Spalte ist das Zielregister, in dem das Ergebnis der Operation gespeichert wird. Die letzten beiden Spalten sind die Operanden 1 und 2, bei denen es sich entweder um Register oder Literalzahlen handelt, auf denen die Operation ausgeführt wird. Nicht alle Befehle nutzen alle 4 Bytes – zum Beispiel benötigt der erste `LookupPointer`-Befehl nur einen Operanden.
Der Disassembler dconstruct fügt außerdem einige zusätzliche Informationen hinzu, um das Lesen der Befehle etwas zu erleichtern. Er fügt auch Labels ein (z. B. `L_0`), um Verzweigungen im Code besser nachvollziehen zu können.```arm
15 00 00 00 LookupPointer r0, 0 r0 = ST[0] -> <is-player-abby?>
4A 01 01 00 LoadStaticU64Imm r1, 1 r1 = ST[1] -> <player>
43 31 01 00 Move r49, r1 r49 = player
1C 00 00 01 CallFf r0, r0, 1 r0 = is-player-abby?(player)
2F 0D 00 00 BranchIfNot r0, 0xD IF NOT r0 => L_0
Dies ist nützlich, wenn Sie den rohen Inhalt der Datei betrachten möchten, ohne dass das Programm zu viele Vermutungen anstellt. Aber es kann für große Codeblöcke schwer lesbar sein, da es keinerlei Struktur gibt. Hier kommt ein Decompiler ins Spiel.
Ein Decompiler ist das Gegenteil eines Compilers. Ein Compiler ist ein Programm, das von Menschen geschriebenen Code (wie C, Java, C++, ...) entgegennimmt und Maschinenanweisungen erzeugt. Im Fall von TLOU2 und vielen anderen ND-Spielen heißt die verwendete Skriptsprache "DC", was im Grunde nur eine Version der Programmiersprache Racket ist, und die "Maschine" ist einfach das Spiel selbst, das die Anweisungen ausführt, während das Spiel läuft. Im Wesentlichen schreiben Programmierer DC und verwenden einen Compiler, um diesen Code in die .bin-Dateien zu verwandeln, die mit dem Spiel ausgeliefert werden.
Ein Decompiler nimmt den oben disassemblierten Code und erzeugt das, was als Pseudo-Code bekannt ist. Pseudo-Code ist ein Versuch, den ursprünglichen Quellcode zu rekonstruieren, der zur Erzeugung der rohen Anweisungen verwendet wurde. Dies soll das Verständnis des Codes erheblich erleichtern, jedoch ist der Prozess der Erzeugung von Pseudo-Code recht aufwändig, da es viele verschiedene Versionen von Quellcode gibt, die den endgültigen Bytecode erzeugen können, sowie Optimierungen, die während der Kompilierung stattfinden.
Derzeit ist die Ausgabe des dconstruct-Decompilers nicht syntaktisch ähnlich zum ursprünglichen DC. DC (d.h. Racket) ist eine funktionale Programmiersprache mit einer einzigartigen Syntax, die für Programmierer, die nicht daran gewöhnt sind, verdammt unleserlich ist. Aus diesem Grund habe ich mich entschieden, den Pseudo-Code vorerst eher wie C aussehen zu lassen, was für die meisten Menschen leichter zu lesen sein sollte. Die Erstellung weiterer Syntaxen, einschließlich Racket sowie einer Python-Version, ist jedoch geplant.
43 00 31 00 15 01 00 00 43 02 00 00 43 31 02 00 1B 01 01 01 43 02 00 00 40 03 01 00 24 02 02 03 2F 0B 02 00 40 02 02 00 2D 0C 00 00 40 02 03 00 43 03 02 00 15 04 04 00 43 05 01 00 43 31 05 00 1C 04 04 01 07 03 03 04 43 01 03 00 00 01 01 00
Daraus ist es praktisch unmöglich zu erkennen, was der Code tut.
## Disassemblierter Code mit Labels & Symboltabelle```arm
sqrt-sign = script-lambda [0x9A8D8] {
[1 args]
0000 0x09A928 43 00 31 00 Move r0, r49 r0 = arg_0
0001 0x09A930 15 01 00 00 LookupPointer r1, 0 r1 = ST[0] -> <absf>
0002 0x09A938 43 02 00 00 Move r2, r0 r2 = arg_0
0003 0x09A940 43 31 02 00 Move r49, r2 r49 = arg_0
0004 0x09A948 1B 01 01 01 Call r1, r1, 1 r1 = absf(arg_0)
0005 0x09A950 43 02 00 00 Move r2, r0 r2 = arg_0
0006 0x09A958 40 03 01 00 LoadStaticFloatImm r3, 1 r3 = ST[1] -> <0.000000>
0007 0x09A960 24 02 02 03 FGreaterThanEqual r2, r2, r3 r2 = r2 >= r3
0008 0x09A968 2F 0B 02 00 BranchIfNot r2, 0xB IF NOT r2 => L_0
0009 0x09A970 40 02 02 00 LoadStaticFloatImm r2, 2 r2 = ST[2] -> <1.000000>
000A 0x09A978 2D 0C 00 00 Branch 0xC GOTO => L_1
L_0:
000B 0x09A980 40 02 03 00 LoadStaticFloatImm r2, 3 r2 = ST[3] -> <-1.000000>
L_1:
000C 0x09A988 43 03 02 00 Move r3, r2 r3 = -1.000000
000D 0x09A990 15 04 04 00 LookupPointer r4, 4 r4 = ST[4] -> <sqrt>
000E 0x09A998 43 05 01 00 Move r5, r1 r5 = RET_absf
000F 0x09A9A0 43 31 05 00 Move r49, r5 r49 = RET_absf
0010 0x09A9A8 1C 04 04 01 CallFf r4, r4, 1 r4 = sqrt(RET_absf)
0011 0x09A9B0 07 03 03 04 FMul r3, r3, r4 -1.000000 = -1.000000 * RET_sqrt
0012 0x09A9B8 43 01 03 00 Move r1, r3 r1 = -1.000000
0013 0x09A9C0 00 01 01 00 Return r1 Return
SYMBOL TABLE:
0000 0x09A9C8 function: absf
0001 0x09A9D0 float: 0.000000
0002 0x09A9D8 float: 1.000000
0003 0x09A9E0 float: -1.000000
0004 0x09A9E8 function: sqrt
}
Der Code ist jetzt wesentlich lesbarer, aber selbst der einzelne Zweig ist schwer zu lesen, wenn man nicht an Assembler gewöhnt ist.
Ohne hier zu sehr ins Detail zu gehen: Ein Kontrollflussgraph (CFG) zerlegt den Assembler-Code entlang der verschiedenen Sprungbefehle in „Knoten“. Dies ist entscheidend, wenn wir den Code analysieren, um herauszufinden, wo der „Fluss“ des Programms in verschiedene Pfade abweichen kann, was uns möglicherweise dazu zwingt, Variablen, if-Anweisungen, for-Schleifen usw. auszugeben. Diese Graphen müssen im Hintergrund erzeugt werden, aber Sie können sie mit dem Programmflag --graphs als Bilder ausdrucken.
u64? sqrt-sign(f32 arg_0) { f32 var_1; if (arg_0 >= 0.00) { var_1 = 1.00; } else { var_1 = -1.00; } return var_1 * sqrt(absf(arg_0)); }
Der Zweck der Funktion ist nun sehr klar: Wir nehmen den absoluten Wert des Arguments, ziehen die Quadratwurzel dieses Wertes und multiplizieren ihn mit dem ursprünglichen Vorzeichen des Arguments. Also zum Beispiel: `sqrt-sign(-9) = -3`.
## Optimierungsdurchläufe
dconstruct wendet automatisch Optimierungsdurchläufe auf den Pseudocode an. Hier sind einige Beispiele:
### Inlining von Funktionsaufrufen
#### Vorher```c
u64? set-arrow-explosive-handle-rootvars() {
u64? var_0 = get-uint64(fx-handle, self);
u64? var_1 = get-float(kill, self);
set-effect-float(var_0, killradius, var_1);
u64? var_2 = get-uint64(fx-handle, self);
u64? var_3 = get-float(strong, self);
set-effect-float(var_2, strongradius, var_3);
u64? var_4 = get-uint64(fx-handle, self);
u64? var_5 = get-float(weak, self);
u64? var_6 = set-effect-float(var_4, weakradius, var_5);
return var_6;
}
u64? set-arrow-explosive-handle-rootvars() { set-effect-float(get-uint64(fx-handle, self), killradius, get-float(kill, self)); set-effect-float(get-uint64(fx-handle, self), strongradius, get-float(strong, self)); return set-effect-float(get-uint64(fx-handle, self), weakradius, get-float(weak, self)); }
### Foreach loops
### Vorher```c#
u64? bmm-deactivate-all(u64? arg_0) {
u64? var_0 = darray-count(arg_0);
begin-foreach();
for (u64 i = 0; i < var_0; i++) {
u64? var_1 = darray-at(arg_0, i);
u16 var_2;
if (var_1 && *(u16*)(var_1 + 12) == 7) {
var_2 = *(u64*)var_1;
} else if (var_1 && *(u16*)(var_1 + 12) == 5) {
var_2 = *(u64*)var_1;
} else if (var_1 && *(u16*)(var_1 + 12) == 4) {
var_2 = *(u64*)var_1;
} else {
var_2 = 0;
}
net-send-event-all(deactivate, var_2);
}
u64? var_3 = end-foreach();
return var_3;
}
u64? bmm-deactivate-all(u64? arg_0) { foreach (u64? var_1 : arg_0) { u16 var_2; if (var_1 && (u16)(var_1 + 12) == 7) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 5) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 4) { var_2 = (u64)var_1; } else { var_2 = 0; } net-send-event-all(deactivate, var_2); } }
### Match expressions
### Vorher```scala
string #C57EE0A64537AE8F(u16 arg_0) {
string var_0;
if (arg_0 == 0) {
var_0 = "Militia";
} else if (arg_0 == 1) {
var_0 = "Scars";
} else if (arg_0 == 2) {
var_0 = "Rattlers";
} else if (arg_0 == 3) {
var_0 = "Infected";
} else if (arg_0 == 4) {
var_0 = "Max Num Factions";
} else {
var_0 = "Invalid";
}
return var_0;
}
string #C57EE0A64537AE8F(u16 arg_0) { return match (arg_0) { 0 -> "Militia" 1 -> "Scars" 2 -> "Rattlers" 3 -> "Infected" 4 -> "Max Num Factions" else -> "Invalid" }; }
## Beispiel einer zerlegten Struktur```c
*ellie-weapons* = symbol-array [0x00190] {
[0] int: 6
[1] int: 0
[2] array [0x198] {size: 6} {
[0] anonymous struct [0x780] {
[0] sid: pistol-beretta
}
[1] anonymous struct [0x788] {
[0] sid: pistol-revolver-taurus
}
[2] anonymous struct [0x790] {
[0] sid: rifle-remington-bolt
}
[3] anonymous struct [0x798] {
[0] sid: bow-ellie
}
[4] anonymous struct [0x7a0] {
[0] sid: shotgun-remington-pump
}
[5] anonymous struct [0x7a8] {
[0] sid: rifle-mpx5
}
}
}
Bearbeiten von DC-Dateien mit dem -e Flag
Sie können das -e Flag verwenden, um Änderungen an DC-Dateien vorzunehmen. Diese Änderungen werden in einer neuen Kopie der Originaldatei gespeichert, wobei das Original unberührt bleibt. Es können mehrere -e Flags gleichzeitig angegeben werden, um mehrere Änderungen auf einmal vorzunehmen.
Alternativ können Sie dem Programm den Pfad zu einer Bearbeitungsdatei angeben. Eine Bearbeitungsdatei enthält eine Änderung pro Zeile. Sie verwendet die gleiche Syntax wie das -e Flag, sollte aber etwas einfacher zu verwenden sein, wenn Sie mehrere Änderungen auf einmal vornehmen möchten.
Die Bearbeitung erfolgt vor dem Disassemblieren und Dekompilieren, sodass die Änderung in den generierten Dateien sichtbar wird.
Jede Änderung folgt dieser Syntax:```xml
[]= ``` - ``: Die Speicheradresse der Struktur, die Sie bearbeiten möchten (in hexadezimal, beginnend mit `0x`. Am einfachsten kopieren Sie sie aus einer disassemblierten Version der Datei, die Sie bearbeiten möchten). - ``: Der Index der Membervariablen innerhalb der Struktur. Entspricht der Zahl, die Sie links neben dem Member sehen können. - ``: Der neue Wert, der diesem Member zugewiesen werden soll. Muss die gleiche Größe haben. (ints und floats haben die Größe 4, sids/structs haben die Größe 8). Das Programm wird NICHT prüfen, ob die Strukturen vom gleichen Typ sind.Angenommen, Sie haben eine Struktur wie diese:```c++ [4] firearm-gameplay-def [0x11C28] { [0] float 0.7 // might represent the rate of fire, so i want to lower it for my mod ... }
Um die erste Membervariable (Index 0) durch den Float-Wert 0.5 zu ersetzen, würde der Bearbeitungsbefehl lauten:
`-e 0x11C28[0]=0.5`
Die Struktur, die wir bearbeiten wollen, befindet sich bei `0x11C28`, und wir möchten die erste Membervariable (die 0 links vom Float). Dann setzen wir den neuen Wert nach dem `=`, in diesem Fall 0.5. Wenn die Bearbeitung erfolgreich war, zeigt das Programm eine Meldung an, die angibt, dass der Wert von `0.7->0.5` geändert wurde.
Für eine Bearbeitungsdatei lassen Sie einfach das `-e` weg und fügen einen Befehl pro Zeile ein:
### edit_file.txt
0x11C28[0]=0.5
0x11C28[1]=0.2
...
## Arten von Membervariablen
Strukturen können verschiedene Arten von Membervariablen haben:
- `float` - Geben Sie Dezimalwerte mit einem Punkt an (z.B. 0.5).
- `int` - Geben Sie ganzzahlige Werte ohne Punkt an (z.B. 42).
- `sid` (Zeichenkettenkennung) - (weitere Informationen unten)
- `string` - wird derzeit nicht zum Ersetzen unterstützt
- `structure` - durch Ersetzen eines Zeigers (weitere Informationen unten)
### Ersetzen von sid durch Namenssuche:
`-e 0xABC[5]=ellie`
Dies sucht nach dem Wert "ellie" in der aktuellen sidbase. Wenn er nicht existiert, wird eine Warnung ausgegeben und keine Bearbeitung angewendet. Wenn der Wert gefunden wird, ersetzt der tatsächliche Hashwert (eine große Zahl) den aktuellen Wert an der Membervariable.
### Ersetzen von sid durch direkte manuelle Hash-Überschreibung:
`-e 0xABC[5]=#XXXXXXXXXXXXXXXX`
Das # kennzeichnet einen rohen Hashwert, der direkt ohne Suche angewendet wird.
### Ersetzen von Memberstrukturen
Wenn eine Struktur eine andere Struktur als Member enthält, können Sie die gesamte Memberstruktur ersetzen, indem Sie ihr die Adresse einer anderen Struktur zuweisen.
Angenommen, Sie haben Folgendes:```c++
[4] weapon-gameplay-def [0x0C523] {
...
[7] firearm-gameplay-def [0x11C28] {
...
}
}
Also weapon-gameplay-def enthält eine firearm-gameplay-def.
Um die firearm-gameplay-def innerhalb der weapon-gameplay-def durch eine andere firearm-gameplay-def zu ersetzen, die sich an Adresse 0x0ABC befindet, lautet die Bearbeitung:
-e 0x11C28[7]=0x0ABC
VS Code unterstützt das Erstellen benutzerdefinierter Erweiterungen, um Syntaxhervorhebung für benutzerdefinierte Sprachen hinzuzufügen. dconstruct wird mit einer .vsix-Datei ausgeliefert, die diese Unterstützung für die Dateierweiterung .dcpl hinzufügt. Da es sich um eine nur für einmalige Zwecke verwendete Sprache handelt, die nicht wirklich für die alltägliche Programmierung gedacht ist, lade ich die Erweiterung nicht im Marketplace hoch, sondern liefere sie als rohe .vsix-Datei aus. Um diese Erweiterung in Ihrer VS Code-Installation zu installieren, führen Sie den folgenden Befehl aus:```shell code --install-extension <path/to/extension/dcpl-lint-0.0.1.vsix>
oder öffnen Sie die Befehlspalette mit CTRL+SHIFT+P und geben Sie "Install extension via VSIX" ein und wählen Sie die .vsix-Datei aus.
Danach sollte Ihr .dcpl-Code etwa so aussehen:

# Bekannte Probleme
Der Dekompilierer ist derzeit noch nicht zu 100 % vollständig und befindet sich daher in einem "experimentellen" Zustand. Wenn Sie während der Dekompilierung Warnungen erhalten, machen Sie sich keine Sorgen, da diese Funktionen derzeit nicht unterstützt werden, aber hoffentlich in Zukunft verfügbar sein werden. Daneben gibt es derzeit weitere bekannte Probleme:
- Einzelausdrucksfunktionen mit starkem Short-Circuiting (insbesondere solche innerhalb von Strukturen) sind noch nicht implementiert. Ich habe mit der Arbeit am Algorithmus dafür begonnen, weiß aber nicht, wie lange es dauern wird, ihn fertigzustellen, obwohl dies ganz oben auf der Prioritätenliste steht.
- Bestimmte Typen sind falsch, insbesondere Argumenttypen.
- Leere if-Blöcke in if/else-Anweisungen können zu seltsamen Einrückungen führen. Auch das ist nicht immer meine Schuld, da es bestimmte Zweige gibt, die tatsächlich keine echte Arbeit leisten, was schwer zu erkennen ist.
Daneben gibt es einige Probleme, die wahrscheinlich nicht behoben werden:
- Zurückgeben von Müll, wenn nicht bekannt ist, ob eine Funktion void ist oder nicht.
- Unmengen von redundantem Code.
# Geplante Funktionen
- Ein vollständiges Ausgabeformat für Racket und Python
# Besonderer Dank
- **icemesh** – für die Bereitstellung der zugrundeliegenden [Strukturen für die DC-Dateien](https://github.com/icemesh/dc/tree/main/t2) und [seinen Disassembler](https://github.com/icemesh/t2-dc-disasm), die weitgehend als Inspiration dienten.
- **Specilizer** – für sein DC-Tool, ebenfalls eine Inspiration für dieses Programm.
- **uxh** – für Skriptkenntnisse.
- **bigdragon** & **Wedge** für Beta-Tests
- Die gesamte Modding-Discord-Community – für Freundlichkeit und Hilfsbereitschaft.
## Unterstützung
Alle meine Tools und Mods werden immer zu 100% kostenlos sein, aber Programme wie dieses erfordern viel Arbeit.
Wenn Sie mich unterstützen möchten, können Sie mein Ko-fi besuchen:
[](https://ko-fi.com/deepquantum)
## Lizenz
Die Dateien, die Sie mit diesem Mod erstellen, gehören vollständig Ihnen und Sie können damit machen, was Sie möchten. Eine Anerkennung wäre willkommen, ist aber nicht zwingend erforderlich.
Das Programm selbst ist unter der [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/) lizenziert.
Das bedeutet, dass Sie das Programm mit anderen teilen dürfen, wenn Sie eine Anerkennung geben, aber Sie dürfen es derzeit weder modifizieren noch monetarisieren.