Crystals-DSA-Foren

Normale Version: Reverse Engineering der NLT
Sie sehen gerade eine vereinfachte Darstellung unserer Inhalte. Normale Ansicht mit richtiger Formatierung.
So jetzt gibt es das erste Release in 2016:

Es wurde wieder ein kleiner Meilenstein erreicht:
Es sind jetzt über 80% der Funktionen nachgebaut. :jippie:

Weiterhin konnte eine etwas haarige Aufgabe, die ich schon lange vor mir hergeschoben haben,
jetzt von mir gelöst werden.
Es handelt sich um das Nachbauen von Schatztruhen (und Leichen :shock:).

Im Moment macht nur die gesicherte Schatztruhe mit dem Runenknochen von Gorah davon Gebrauch.
(Bitte um Tests!)
Weitere werden natürlich folgen :D.

Bei den Reiseevents handelt es sich größtenteils um Lager-, Jagd- und Kräuterplätze.
Die interessanten Begegnungen werde ich hier im Changelog natürlich nennen.
Außerdem gibt es Reisebegegnungen, die keine Funktionalität haben und
Reisebegegnungen, die im Spiel nicht Aktiviert wurden. :)

Ersetzte Funktionen (Segmente sind komplett identisch)
  • seg088: dungeon thorwal 2 / 2 (Schatztruhenlogik, tauchen + Rätselschloss in der Ruine)
  • seg109: Reiseevents 1 / 10
  • seg111: Reiseevents 3 / 10 (Gorah[64], Reiter[65], Kampf mit Steppenhunden[66])

Ersetzte Funktionen
  • seg113: Reiseevents 5 / 10 (Tatzelwurm[80], Kampf mit Orks und Oger[84])


TODO-Liste:
  • Reiseevents (viele kleine Funktionen)
  • Reisemodus
  • Dungeons (ein paar kleine und wenige sehr lange Funktionen)
  • Check: Skripte zum Vergleichen der Binärdateien (erfordert eigenen Borland C++ 3.1 Compiler)

Statistik:
  • Es sind 991 von 1236 Funktionen nachgebaut (80,18%).
  • Davon sind 964 identisch mit dem Originalcode.
  • Nach Byte-Metrik sind schon 78,84% (korrigiert) fertig.

Viele Spaß beim Testen,
HenneNWH
Neue Reiseevents? Irrwitz. :cool:
Neue Version vom 18.01.2016!

P.S.: Hier mal die Übersicht, die Henne erstellt hat: http://bright-eyes.obiwahn.de/Bright-Eyes/details.html
So, ich habe mal wieder angefangen Schick zu spielen, natürlich mit Bright-Eyes. Eigentlich vor allem deswegen. Mal gucken, wann ich über den ersten Bug stolper! :-D Bisher läuft in der Zwingfeste noch alles gut.
Da wünsche ich dir Vergnügen und Erfolg (wenn es denn einen Bug gibt :-D ).

In der Zwingfeste habe ich bisher nur einige Spezialfunktionen (tauchen, Schatztruhen) nachgebaut.
Es fehlt nur noch der Dungeon-Handler (~4kiBi) Binärcode, dann wäre das Testen in der Zwingfeste sinnvoller.
Den könnte ich als nächstes in Angriff nehmen, wenn ich noch 10 Reiseevents geschafft habe.
Das könnte an diesem Wochenende passiert sein.
Hallo allerseits,

ich beobachte dieses Reverse-Engineering-Projekt schon länger und bin schwer beeindruckt, wie weit diese Mammut-Aufgabe bereits vorangeschritten ist. Respekt!

Den auf Github zugänglichen Quellcode von Version 3.02 (de) habe ich als Grundlage genommen, um mir mal ein paar Assembler-Kenntnisse anzueignen (C versteh ich bereits einigermaßen). Das ist ja wirklich gut machbar mit den ebenfalls verfügbaren IDA-Dateien.

Nachdem ich nun so ein bisschen drin bin, dachte ich, ich kann vielleicht auch ein bisschen mithelfen, und fand auch direkt klare Handlungsanweisungen von HenneNWH in einem früheren Beitrag (http://www.crystals-dsa-foren.de/showthr...#pid142632). Aber es ist ja ein Wahnsinn, sich da so weit einzuarbeiten, dass man wirklich mal eine lokale Variable "versteht"!

Assembler in C übersetzen ist schon mühsam genug, aber dann noch verstehen, was der Code wirklich tut ...!? Ich bin schon ziemlich beeindruckt davon, dass es doch so viele Funktionen und globale Variablen sind, die bereits "entschlüsselt" werden konnten. Ich dachte, ich fange mal mit dem Kampfsystem an, und versuche zu verstehen, wie das funktioniert - konkret wollte ich den Sinn von FIG_LIST_HEAD und der dort gespeicherten Werte verstehen. Da scheinen ja recht zentrale Daten des aktuellen Kampfes gespeichert zu sein. Gibt es irgendeine Stelle, an der ihr sowas dokumentieren würdet?

Ich muss echt sagen, dass ich nach Stunden des Code-Durchstöberns immer noch nicht viel verstehe. Was zur Hölle macht denn beispielsweise die Funktion `FIG_set_12_13` (oder `FIG_reset_12_13`) in seg006?! Da geht's offensichtlich um den an Stelle 0x12 gespeicherten Wert und der kann die Werte 0,1,2,3 annehmen, aber was bedeutet das? In der Funktion `FIG_set_0e` hat jemand kommentiert, es handle sich um eine "presence flag". Was auch immer das ist: Habt ihr das herausgefunden, indem ihr entsprechende DEBUG-logs hinzugefügt habt und das Spiel so lange gespielt habt, bis ihr entsprechenden Output generieren konntet? Oder wie würdet ihr bei sowas generell vorgehen?
(26.01.2016, 10:02)HenneNWH schrieb: [ -> ]Das könnte an diesem Wochenende passiert sein.

Ist etwas passiert? ;):cool:
@gaor:
Schön, dass du mitmachen willst. Und jemand, der sich an Assembler heranwagt, können wir immer gut gebrauchen.
Allerdings kann ich dir dazu nicht viel sagen. Bin eher der Hexer... mit ASM hab ich bisher wenig am Hut gehabt.
Wenn ich raten soll, dann würde ich sagen, Henne hat das meiste durch Code Review herausgefunden. Allerdings ist die Methode, einfach printf's einzufügen, auch erfolgversprechend. Dokumentieren würde ich das im Quelltext, und den dann hier zum Einbau hochladen.
Hallo Gaor, :wave:

(01.02.2016, 00:30)gaor schrieb: [ -> ]Nachdem ich nun so ein bisschen drin bin, dachte ich, ich kann vielleicht auch ein bisschen mithelfen, und fand auch direkt klare Handlungsanweisungen von HenneNWH in einem früheren Beitrag (http://www.crystals-dsa-foren.de/showthr...#pid142632). Aber es ist ja ein Wahnsinn, sich da so weit einzuarbeiten, dass man wirklich mal eine lokale Variable "versteht"!

Assembler in C übersetzen ist schon mühsam genug, aber dann noch verstehen, was der Code wirklich tut ...!? Ich bin schon ziemlich beeindruckt davon, dass es doch so viele Funktionen und globale Variablen sind, die bereits "entschlüsselt" werden konnten. Ich dachte, ich fange mal mit dem Kampfsystem an, und versuche zu verstehen, wie das funktioniert - konkret wollte ich den Sinn von FIG_LIST_HEAD und der dort gespeicherten Werte verstehen. Da scheinen ja recht zentrale Daten des aktuellen Kampfes gespeichert zu sein. Gibt es irgendeine Stelle, an der ihr sowas dokumentieren würdet?

Ich muss echt sagen, dass ich nach Stunden des Code-Durchstöberns immer noch nicht viel verstehe. Was zur Hölle macht denn beispielsweise die Funktion `FIG_set_12_13` (oder `FIG_reset_12_13`) in seg006?! Da geht's offensichtlich um den an Stelle 0x12 gespeicherten Wert und der kann die Werte 0,1,2,3 annehmen, aber was bedeutet das? In der Funktion `FIG_set_0e` hat jemand kommentiert, es handle sich um eine "presence flag". Was auch immer das ist: Habt ihr das herausgefunden, indem ihr entsprechende DEBUG-logs hinzugefügt habt und das Spiel so lange gespielt habt, bis ihr entsprechenden Output generieren konntet? Oder wie würdet ihr bei sowas generell vorgehen?

es freut mich, dass Du mitmachen möchtest. Die Lernkurve in diesem Projekt ist sehr steil,
mit der Zeit erschließt sich der Sinn, vor allem wenn man das Spiel schonmal gespielt hat
und eine grobe Vorstellung davon hat, wie es funktionieren könnte.
Das Kampfsystem ist aber schon eine harte Nummer, da Kampflogik und Grafikausgabe
nicht klar getrennt sind.

Mein Wissen zu FIG_LIST_HEAD:
Es handelt sich um das erste Element einer doppelt verketteten Liste.
Welche Informationen in der Liste gespeichert sind weiß ich nicht vollständig,
aber ich vermute es könnte sich um Beschreibungen der Darstellung handeln.
Die Charakterbögen der Gegner sind auf jeden Fall woanders (ENEMY_SHEETS) gespeichert.

Die Funktion FIG_set_0e(fight_id, val) z.B. such in dieser Liste nach einem Element,
in welchem die fight_id (ptr + 0x10) mit der übergebenen fight_id übereinstimmt.
Wenn das Erste gefunden wurde, wird die Stelle (ptr + 0x0e) mit dem übergebenen Wert val überschrieben
und die Stelle (ptr + 0x12) auf 1 gesetzt.

EDIT: Die Funktion FIG_draw_figures() in der gleichen Datei iteriert über alle Listenelemente und
zeichnet sie nur auf dem Bildschirm, wenn (ptr + 0x12) gleich 1 ist.
Entweder es bedeutet es, dass die Figur auf dem Spielfeld ist (presence flag) oder
die Figur hat sich bewegt und muss neu gezeichnet werden (update/refresh flag).

Ein Anfang wäre die vollständige Datenstruktur dieser Listenelemente im Wiki aufzustellen und
die Datentypen und Bedeutungen Schritt für Schritt nachzutragen.

Ein Anfang:

Die Größe eines Listenelements ist 35/0x33 Byte:

Adresse Typ Bedeutung
0x00 s16 ???
0x02 s8 Sprite Nummer
0x03 s8 X-Koordinate (auf Schachbrett)
0x04 s8 Y-Koordinate (auf Schachbrett)
0x05 s8 ???
0x06 s8 ???
0x07 s8 ???
0x08 s8 ???
0x09 s8 ???
0x0a s8 ???
0x0b s8 ???
0x0c s8 ???
0x0d s8 ???
0x0e s8 ???
0x0f s8 ???
0x10 s8 Kampf ID {-1 = ungültig}
0x11 s8 ???
0x12 s8 ??? presence flag
0x13 s8 ???
0x14 s8 ??? object_id
0x15 s8 ???
0x16 s8 ???
0x17 ptr* Grafiksprite
0x1b ptr* nächstes Listenelement
0x1f ptr* vorheriges Listenelement

Wenn etwas noch nicht klar ist kann es später nachgetragen werden.
Die Datentypen kannst Du daran erkennen, mit welcher Funktion darauf zugegriffen wird:
host_readbs() => b [Byte] s [signed]
host_readb() => b [Byte] [unsigned]
host_readws() => w [word/short] s [signed]
host_readds() => d [doubleword/long] s [signed]

Wenn es machbar ist, sind auch mögliche Werte der einzelnen Einträge interessant.

Jetzt kann gesucht werden, wann diese Funktionen aufgerufen werden.
Oder man verändert die Funktionen gezielt und beobachtet was passiert.
printf() ist auf jeden Fall sehr hilfreich.

(01.02.2016, 08:29)Obi-Wahn schrieb: [ -> ]
(26.01.2016, 10:02)HenneNWH schrieb: [ -> ]Das könnte an diesem Wochenende passiert sein.

Ist etwas passiert? ;):cool:

Ja, ich habe direkt mit dem Dungeon-Handler für die Zwingfeste angefangen.
Den ersten Level habe ich schon fertig bekommen, es fehlen also noch vier Level. :grin:

Würdest Du Gaor einen Wiki-Account anlegen?


(01.02.2016, 23:34)Rabenaas schrieb: [ -> ]Wenn ich raten soll, dann würde ich sagen, Henne hat das meiste durch Code Review herausgefunden. Allerdings ist die Methode, einfach printf's einzufügen, auch erfolgversprechend. Dokumentieren würde ich das im Quelltext, und den dann hier zum Einbau hochladen.

Das ist korrekt!

Das wäre die einfachste Variante.
Alternativ nehme ich auch Patches oder (die Krönung) wäre mit Git.
Also gut, das war ja schonmal ziemlich aufschlussreich. ich wühle mich noch ein bisschen weiter durch den Code. Wenn ich tatsächlich fähig sein sollte, etwas Brauchbares zu generieren/dokumentieren/kommentieren, kriegst du einen pull request ;)

Eine Sache noch: Wie überprüfst du die Funktionen auf "borlandified and identical"? Ich habe mich mit seg066.cpp, Zeile 183, beschäftigt - dort soll es ja noch Probleme geben. Also habe ich mit der Zeile `..\BIN\BCC.EXE -mlarge -O- -c -1 -Yo SEG066.CPP` aus deiner compile.bat und meinem BCC.EXE eine entsprechende OBJ-Datei erstellt und im IDA inspiziert, konnte aber nicht den Fehler finden. Gut, ich habe `nc2fc.py` nicht ausgeführt (hab nicht so ganz verstanden was da Eingabe-Datei ist und was man mit der Ausgabe macht) und die ganzen globalen Variablen sehen erwartungsgemäß anders aus und das Register `as` wird nicht auf 0 gesetzt. Aber macht das denn einen Unterschied?
Diese Überprüfung ist eine Linuxlastige Scriptsammlung (works for me), welche ich noch nicht ins Repo eingepflegt habe,
da bisher niemand einen BCC hatte. Da sich das soeben geändert hat ist es nun an der Zeit das Ganze zu sortieren und hochzuladen.

Kleine Skizze:

Initial wird aus der SCHICKM.EXE mit ndisasm (nasm) der Code für die einzelnen Code-Segmente disassembliert und in einzelnen Dateien abgelegt. (./tools/disassemble.sh ist schon im Repo). Dazu muss SCHICKM.EXE im tools-Verzeichnis liegen.

Noch nicht im Repo ist folgende Toolchain:
Es wird Qemu und ein Festplattenimage mit FreeDOS und dem BCC benötigt.

Anschließend kann ich mit einem Script
  • alle cpp,h,*.bat Dateien im Verzeichnis auf das Image kopieren,
  • die OBJ-Dateien erzeugen (Qemu),
  • die OBJ-Dateien vom zurückkopieren,
  • den Binärcode extrahieren,
  • mit ndisasm disassemblieren und
  • zweispaltig mit diff Vergleichen.

Das ganze habe ich auch als commit-hook in mein lokales Repo integriert,
damit ich nicht aus versehen identischen Code kaputt mache.

Zur Frage mit SEG066.CPP:
Das ist nicht der Fehler, sondern eher ein Notbehelf, der gleichlangen Code erzeugt und das gleiche Resultat hat.

Das Problem liegt etwas tiefer und hängt damit zusammen ob der Compiler schon weiß, ob sich die aufgerufene Funktion
in der gleichen cpp-Datei befindet oder nicht.
Wenn er zum Zeitpunkt der Codeerzeugung nur der Funktionskopf bekannt ist z.B. aus einer Vorausdeklaration aus der Cpp-Datei und/oder aus einem Header, und die Argumenttypen nicht immer übereinstimmen kann soetwas schonmal vorkommen.

Das könnte ich sicherlich mit noch etwas mehr Präprozessor-Magic lösen,
aber das hat auch noch etwas Zeit.
Vielen Dank, dass du dir so viel Zeit nimmst, meine Posts zu lesen und so ausführlich zu beantworten!

Linuxlastig ist prinzipiell schonmal gut, etwas Anderes habe ich nämlich nicht. Aber warum wird Qemu benötigt bzw. warum reicht nicht dosbox für BCC? Gibt es da Probleme mit irgendwelchen Systembibliotheken (AIL in seg002.cpp?) auf die BCC zurückgreift?
Könntest Du Dir den Vergleich nicht sparen, wenn Du die Ausgabe von objdump durch md5sum jagst, oder ist das eh nicht ganz identisch?
@gaor: Ich habe ein Benutzerkonto auf http://bright-eyes.obiwahn.de/ für dich erstellt. Es sollte eine Mail an deine Adresse gegangen sein, die du auch hier im Forum benutzt hast. Guck mal, ob du dich anmelden kannst. Das hat vor ein paar Monaten mal nicht funktioniert.....
@Obi-Wahn: Hm, ich habe jetzt noch keine Email bekommen. Die Adresse habe ich überprüft, die ist noch aktuell.
Ich habe jetzt mal was anderes versucht. Wer einen Zugang zum Wiki haben will und noch keine PN von mir hier im Forum bekommen hat, schreibt mich bitte nochmal an. :)
(02.02.2016, 17:26)gaor schrieb: [ -> ]Linuxlastig ist prinzipiell schonmal gut, etwas Anderes habe ich nämlich nicht. Aber warum wird Qemu benötigt bzw. warum reicht nicht dosbox für BCC? Gibt es da Probleme mit irgendwelchen Systembibliotheken (AIL in seg002.cpp?) auf die BCC zurückgreift?

:bigsmile:

Ich wollte nicht so ganz ins Detail gehen, aber jetzt da Du fragst:
Wenn ich an einer einzelnen Datei arbeite, benutze ich zur händischen Kontrolle DOSBox,
weil es für eine einzelne Datei so am schnellsten geht.

Für den automatischen Test mit Git dauert es mir zu lange, da mittlerweile bei jedem Commit 85 cpp-Dateien
kompiliert werden. Mit Qemu und KVM, also mit HW-Unterstützung, geht es wesentlich schneller.

Vergleich: DOSBox ~ 172s Qemu ~ 36s => Qemu ist um Faktor 4.77 schneller

Es geht sicher noch _besser_ , aber das Hauptziel ist schließlich SCHICK und nicht die optimale Toolchain. :D

Die Benutzung von DOSBox oder Qemu kann in einer Zeile geändert werden.


Achso, das AIL-Verzeichnis mit den Headern habe ich im Moment auch nicht eingecheckt.
Du kannst dir die AIL2.ZIP
herunterladen. Darin ist A213_D2.ZIP, daraus brauchst Du
AIL.ASM AIL.H AIL.INC AIL.MAC AIL.MAK
um seg002.cpp kompilieren zu können.


(02.02.2016, 19:37)Rabenaas schrieb: [ -> ]Könntest Du Dir den Vergleich nicht sparen, wenn Du die Ausgabe von objdump durch md5sum jagst, oder ist das eh nicht ganz identisch?

Ich habe gerade mal probiert mit objdump zu disassemblieren, aber mit dem Resultat bin ich nicht zufrieden,
da objdump m.W. keinen 16-Bit Code unterstützt und somit fragwürdige Listings erstellt.
An den Output von ndisasm (kann 16-bit) habe ich mich jetzt schon gewöhnt und meine Toolchain darauf aufgebaut.
Never run a changing system.

Außerdem gibt es auch Dateien, bei denen der Unterschied nur ein paar Zeilen ist.
Für diese (von mir persönlich geprüften Fälle) habe ich Ausnahmen zugelassen, on-top-of-ndisasm.
Schön, dass das mit dem Wiki jetzt wieder funktioniert. :)
Jep, hab gleich mal noch ein paar Updates bei den CHR-Dateien gemacht.