18.11.2025, 11:57
|
Reverse Engineering der NLT II
|
|
18.11.2025, 11:59
(18.11.2025, 11:57)llm schrieb:(18.11.2025, 11:47)NewProggie schrieb: CMake hatte ich vor ein paar Wochen schon hinzugefügt (in meinem Fork). Wurde aber nicht upstream gemerged bisher. CI mit Auto-Builds und Linter Checks hab ich ebenfalls vor ein paar Wochen schon hinzugefügt :-)
18.11.2025, 19:18
(18.11.2025, 10:47)HenneNWH schrieb: Danke, Danke! Schade. Aber ich sehe das; es wäre einiges an Aufwand, die binär äquivalente DOS-Version weiter nachzuziehen und zu pflegen. (18.11.2025, 10:47)HenneNWH schrieb: Kleine Info zu den Charakterbildern und dem Import nach Schweif: In Schick liegt das Heldenbild (32x32 Pixel/Bits) eben noch im Spielstand. Ab Schweif gibt es lediglich einen Index in eine vordefinierte Auswahl an Bildern. Diese vordefinierten Bilder ließen sich übrigens auch relativ simpel (automatisiert per Skript) im Spielarchiv verändern -- ggf. sogar hin zu dem, was man im Schick-Spielstand eigens kreiert hatte. Nur sind die Paletten von Schick und Schweif leider nicht kompatibel; es gibt zwar ab Schweif mehr Farben, aber die alten gibt es nicht mehr 1:1. (Schweif und Riva sind identisch.) Ich habe gerade mal testweise die Charakterporträts von Schick (von denen es nicht mehr alle in Schweif gibt) automatisiert in die Palette von Schweif überführt (nearest-color / ΔE*76), und es sieht, bis auf eine subjektiv leichte Verdunkelung hier und da, eigentlich bei allen Porträts ganz passabel aus. Vielleicht baue ich die Idee bei Gelegenheit zu einem kleinen Modding-Tool aus, um die alten (oder eigenen) Charakterporträts nach Schweif / Riva zu übertragen.
18.11.2025, 20:34
(18.11.2025, 19:18)cmfrydos schrieb: In Schick liegt das Heldenbild (32x32 Pixel/Bits) eben noch im Spielstand. Ab Schweif gibt es lediglich einen Index in eine vordefinierte Auswahl an Bildern. Kannst du das mal zeigen? (Screenshots) (18.11.2025, 20:34)NewProggie schrieb: Kannst du das mal zeigen? (Screenshots) Hatte sogar schon was vorbereitet, aber dann aus urheberrechtlichen Gründen zurückgerudert. Hier eine Variante davon, in der quasi nur 2/3 * 2/3 der Charakterporträts gezeigt werden (um hier keine originalen Spieldateien zu teilen). Achtung, dadurch wirken die Porträts etwas entstellt, da Pixel fehlen. Sollte einem dennoch eine Idee der Farbverfälschung geben. Links jeweils das Original (skaliert) aus Schick, rechts, wie es mit der Palette aus Stern/Riva aussehen würde: Bzw. hier das ermittelte Mapping zwischen den Paletten (links Schick, rechts Sternenschweif). Man sieht, dass vor allem dunklere Brauntöne problematisch sind:
19.11.2025, 14:22
(Dieser Beitrag wurde zuletzt bearbeitet: 19.11.2025, 22:26 von siebenstreich.)
Hier noch mein aktueller Kenntnisstand zum Gegenstandszähler (hero.num_filled_inv_slots). Die Sache scheint ähnlich gelagert zu sein wie bei den von Henne recht treffend benannten Placebo-Zaubern: Der Zähler wird fleißig aktualisiert, wenn auch nicht immer ganz korrekt. Aber ausgelesen wird der Zähler nur an sehr wenigen Stellen, und die Wirkung hält sich arg in Grenzen:
Eine gewisse Gefahr geht allerdings davon aus, dass der Zähler manchmal falsch aktualisiert wird, womit er potentiell jeden denkbaren Wert annehmen kann. U.u. wird also ein Verkaufsbildschirm zu Unrecht vorzeitig beendet. Oder ein Held bekommt einen neuen Gegenstand nicht, obwohl er durchaus Platz dafür gehabt hätte. Fazit: Eigentlich braucht man den Zähler num_filled_inv_slots nicht, aber er macht potentiell Ärger. Die beste Lösung wäre wohl, das Ding in der "cleanen" Neuprogrammierung einfach abzuklemmen (und für den Schweif-Import den Wert am Ende einmal korrekt rauszuzählen).
19.11.2025, 17:21
(19.11.2025, 17:21)HenneNWH schrieb: Das kompiliert mit GCC und Clang MSVC cl.exe mag den code auch noch und erzeugt das selbe Bild unter Windows 11 btw deine swap32 geht ein wenig kompakter ![]() Code: inline uint32_t swap32(uint32_t v) {
Das ist richtig. Meine Funktion hat den Vorteil, dass sie mathematisch beweisbar ist.
Hier mal ein kleiner Brute-Force-Test um sich ein Urteil über die Qualität der Compileroptimierung zu bilden. Code: #include <stdio.h>Meine Version (-O0, -O1, -O2) GCC: 94s, 10s, 10s Clang: 113s, 0s, 0s Deine Version (-O0, -O1, -O2) GCC: 29s, 0s, 0s Clang: 28s, 9s, 0s Die Werte vom MSVC würden mich hier mal interessieren. P.S.: Wenn es weniger als 1s dauer Optimiert der Compiler die Schleife in der main()-Funktion weg.
19.11.2025, 20:30
(Dieser Beitrag wurde zuletzt bearbeitet: 20.11.2025, 10:45 von siebenstreich.)
(19.11.2025, 19:40)HenneNWH schrieb: Das ist richtig. Meine Funktion hat den Vorteil, dass sie mathematisch beweisbar ist. Jetzt muss ich auch mal den Klugscheißer anschalten. ![]() Man beweist Aussagen, nicht Funktionen. Vermutlich meinst du die zugehörige Korrektheitsaussage, also dass die angegebene Funktion wirklich immer das tut, was wir von ihr erwarten (sie spiegelt die Positionen der vier Bytes). Dies ist aber für beide Fälle beweisbar, schon allein, weil es nur endlich viele mögliche Eingabe-Bitmuster gibt, die man schlimmstenfalls alle durchprobieren kann. Und in beiden Fällen geht es natürlich auch direkt.
19.11.2025, 21:46
(19.11.2025, 20:30)siebenstreich schrieb:(19.11.2025, 19:40)HenneNWH schrieb: Das ist richtig. Meine Funktion hat den Vorteil, dass sie mathematisch beweisbar ist. Und ganz praktisch gesprochen, macht so ein Quatsch in einem Rollenspiel auch keinen Sinn oder Unterschied. HPC ist ein wichtiges Werkzeug, aber man muss auch den Kontext sehen (können).
In erster Linie muss es zuverlässig funktionieren.
Die Originalimplementierung dieser Funktion hat mit GCC/Clang falsche Werte geliefert. Auch der BCC hat mit eingeschaltener Optimierung nicht korrekten Code erzeugt. Korrektheitsbeweise sind sehr praktisch. Was genau möchtest du mir/ uns damit mitteilen? (19.11.2025, 22:09)HenneNWH schrieb: Was genau möchtest du mir/ uns damit mitteilen? ohne die Info von dir mit "Die Originalimplementierung dieser Funktion hat mit GCC/Clang falsche Werte geliefert." war einfach nicht klar warum dich diese kleine Routine so interessiert - jetzt sind NewProggie und ich im Bilde ![]() d.h. du hast das für 32/64bit gefixt aber weils im BCC eh ohne Optimierung gebaut wurde hatte der Fehler in der DOS Exe nie Auswirkungen? zu deinem Benchmark Code: Meine Version (-O0, -O1, -O2)die Zahlen kommen mir komisch vor - speziell gcc -02 und clang -01 - der clang (21.1.5) und der gcc (15.2.1) schmeissen bei mir mit -O2 schon den kompletten Code raus und machen nur return 0; siehe: https://gcc.godbolt.org/z/a6GssaaT1 und für clang(ab -O1)/gcc(ab -O2) ist der code unserer beiden bei Funktionen 100% identisch (der MSVC 2022 reduziert meine Funktion auch auf bswap bei /O2) - nur der Benchmark-Code ist anders optimiert https://gcc.godbolt.org/z/8rqvY9bjh Code: swap32_llm(unsigned int):damit "wissen" der gcc und clang das dein if nie anschlagen wird, also kann man alles weg optimieren bei sowas musst du meist Sondervariable mit einfügen - damit der Optimizer die Finger still hält - UND den Assembler-Code anschauen eher sowas wie Code: int main() {aber da beiden Funktionen identisch sind nach der Code-Generierung macht hier Benchmarking nicht so viel Sinn weil man dann nur den Benchmark-Code benchmarkt und du solltest deinen gcc/clang mal updaten ![]() der aktuelle MSVC 2022 schafft es nur meine Routine in den einfach swap zu wandeln - deine Routine bleibt etwas umfangreicher https://gcc.godbolt.org/z/bW8vxc3Wz aber VS2022 schafft es auch nicht mit dem einfachen swap (also dem wissen darum) auf return 0 zu optimieren was man aber auf keinen Fall als gcc/clang machen immer den bessere Code interpretieren sollte - die Final-Optimierung bei solchen Dead-Ends ist beim gcc/clang einfach besser - aber haben nicht immer Auswirkung auf echte Software manchmal rasten auch die Optimizer vom gcc/clang aus (hab da schon genug Bug Reports eingestellt oder Diskussionen) gehabt wo der sich ergebende Code auch nicht mehr so fein war Entscheident sind die richtigen Werkzeuge(VTune unter Windows/Linux, gcc.godbolt.org sind die Welt-Favoriten)/Strategien zum Benchmarking, aktuelle Kompiler und keine ausgeprägte "so war das mal" Denke Es ändert sich in den letzten 10 Jahren erstaunlich viel, erstaunlich schnell als das Erfahrungswerte nicht einer deutlichen Auffrischung benötigen - das gilt für jeden Entwickler ![]() aber es zeigt auf jeden Fall wie gut die Optimizer arbeiten können
HPC? High Performance Code? Hilbert-Proof-Calculus?
Naja, ich bin auch überzeugt, dass llm's swap32 leicht beweisbar korrekt ist: Jeder Term der bitweisen Veroderung beeinflusst genau 8 Bits, und es sind jeweils die richtigen für den Big↔Little-Endian-Swap. Durch die Kompaktheit lässt sich die Funktion auch gut lesen. Aber eigentlich will man hier ja doch, dass der Compiler am Ende wieder ein bswap im Assembler erzeugt, oder? __builtin_bswap32 (GCC/Clang) bzw. _byteswap_ulong (MSVC) dürften daher die schnellste und, im Gegensatz zu Inline-Asm, plattformunabhängigere Variante sein. Der alte Code hatte gegen Strict-Aliasing (und Alignment) verstoßen, vermutlich daher undefiniertes Verhalten (bei höheren Optimierungsstufen): Code: int16_t a[2];Edit: Ups, da war ich zu langsam, interessante Analyse llm!. Zitat:vermutlich daher undefiniertes Verhalten (bei höheren Optimierungsstufen) wäre interessant ob der "-fsanitize=undefined" das aufzeigt - ich denke ein Coverity hätte das auch bemängelt update: "-fsanitize=undefined" zeigt da nix mit -O0 oder höher, hab einfach den Borland-Code - also die 3-4 Routinen kopiert - das Ergebnis ändert sich ständig aber sanitizer schlägt nicht an - kann aber auch an mir liegen der neue TypeSanitizer "-fsanitize=type" findet was, ist aber auch noch sehr experimentell https://clang.llvm.org/docs/TypeSanitizer.html Code: clang++ -fsanitize=type -fno-omit-frame-pointer -g -O0 test.cpp -o testbekommt man für diesen Code (TypeSanitizer findings als Kommentar) folgende Ausgaben Code: #include <stdint.h>Ausgabe Code: ==13160==ERROR: TypeSanitizer: type-aliasing-violation on address 0x7ffe1e8763b4 (pc 0x5b2bbc0665f8 bp 0x7ffe1e876280 sp 0x7ffe1e876210 tid 13160)d.h. der TypeSanitizer kommt jetzt auch auf meine Liste also: -fsanitize=address, =thread, =undefined und =type ![]() leider ist der =memory so kompliziert zu nutzen wegen den 3rd-parties (aber Schick ist da ja angenehm bescheiden) - da bleibt aber noch der valgrind - auch wenn er 100x langsamer ist ![]() die Sanitizer werden ständig verbessert oder es kommen neue hinzu - wenn man sicher gehen will sollte man die Kompiler immer schön aktuell halten - SUSE Tumbleweed/Fedora oder Arch-Linux sind da immer recht fix mit Update auf die neuen Releases (20.11.2025, 05:59)llm schrieb: ohne die Info von dir mit "Die Originalimplementierung dieser Funktion hat mit GCC/Clang falsche Werte geliefert." war einfach nicht klar warum dich diese kleine Routine so interessiert Ziel des Tests war herauszufinden wie gut die Compiler optimieren. Wenn die Schleife aus der main()-Funktion wegoptimiert wird, passt es. Die Funktion swap_u32() ist mir im Charaktergenerator schon aufgefallen. Damals hatte ich diese Idee für den Test. Gestern gab's dann auch einen echten Grund das zu fixen. Performance steht da nicht im Vordergrund, dafür wird diese Funktion zu selten aufgerufen. Hab am Wochenende auf Debian 13 aktualisiert: GCC 14.2.0, Clang 19.1.7 läuft hervorragend. (20.11.2025, 06:19)cmfrydos schrieb: Aber eigentlich will man hier ja doch, dass der Compiler am Ende wieder ein bswap im Assembler erzeugt, oder? Nicht ganz. Mir geht es bei BrightEyes darum möglichst nah am Original zu bleiben und portablen und korrekten Code zu erzeugen. Die builtins halte ich bei performancekritischer Software für das Mittel der Wahl, aber es zieht im Nachgang für mehrere Compiler Präprozessor-ifdef-Orgien nach sich. Das muss nicht sein. (20.11.2025, 06:26)llm schrieb: wäre interessant ob der "-fsanitize=undefined" das aufzeigt - ich denke ein Coverity hätte das auch bemängelt Nur zur Info: Der Address-Sanitizer funktioniert bei mir mit GCC auf dem Raspi2 nicht. Da fehlen scheinbar hardwareseitig atomic_load/store und compare_exchange Instruktionen.
20.11.2025, 14:46
Ich hab eben rudimentären CMake Support für Schick hinzugefügt und Henne hat das auch schon upstream gemerged.
Daneben bin ich einmal die seg-Dateien durch und hätte folgenden Vorschlag für die Umbenennung der Dateien: Code: # Core Systems (seg001-seg011)Was meint ihr? (20.11.2025, 14:46)NewProggie schrieb: Ich hab eben rudimentären CMake Support für Schick hinzugefügt Super! warum nutzt du die alte Schreibweise in CMake mit den Include/Lib Pfaden also warum nicht die neue(auch schon Jahre alte) Schreibweise wie in ngen? bei OpenMP machst du neu, bei SDL alter Style? Code: target_link_libraries(ngen PUBLIC SDL2::SDL2main)und bitte auch die Header rein machen - sonst fehlen die in MSVC, QtCreator oder CLion - Bauen geht aber CMake stellt keinen Projektbezug her
20.11.2025, 16:22
(20.11.2025, 14:46)NewProggie schrieb: Ich hab eben rudimentären CMake Support für Schick hinzugefügt und Henne hat das auch schon upstream gemerged. Danke für die Arbeit. Das Umbenennen ist aktuell noch nicht vorgesehen, aber für die Zukunft geplant. Siebenstreich und ich haben uns an die historische Struktur, die sich aus dem Rekonstruktionsprozess ergeben hat, gewöhnt. Die bleibt vorerst so, da wir damit klarkommen. Ist ja auch mein Projekt. ![]() Prio haben aktuell, unter Beibehaltung der Binäräquivalenz des DOS-Binaries, Umbenennungen, die SDL2-Portierung und Fehlerbehebungen für 64-Bit Systeme. Alles andere muss warten. |
Benutzer, die gerade dieses Thema anschauen: 1 Gast/Gäste




