Themabewertung:
  • 2 Bewertung(en) - 3 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Reverse Engineering der NLT II
(19.07.2025, 17:12)NewProggie schrieb: Kannst du sagen, was genau schief läuft bei Windows?

Ich habe mir bisher nur die Webseite des Projekts angeguckt, ich habe noch keinen Checkout/Download gemacht. Ich kenne mich mit dem Build über GitHub überhaupt nicht aus.

Ich sehe diese Fehlermeldung:
   
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
(19.07.2025, 17:29)Obi-Wahn schrieb:
(19.07.2025, 17:12)NewProggie schrieb: Kannst du sagen, was genau schief läuft bei Windows?

Ich habe mir bisher nur die Webseite des Projekts angeguckt, ich habe noch keinen Checkout/Download gemacht. Ich kenne mich mit dem Build über GitHub überhaupt nicht aus.

Ich sehe diese Fehlermeldung:

Ah, so, nein, die Jobs sind chronologisch von unten nach oben sortiert. Oben ist stets der neueste und der ist grün: https://github.com/NewProggie/BrightEyes...6389311718
Zitieren
Kleine Zwischenmeldung. Henne bastelt fleißig weiter. Die Namen und die Beschreibungen der Code-Einträge werden auch immer verständlicher für mich als Laie. Globale Variablen werden bearbeitet und alle möglichen Spielbestandteile sind zu erkennen. :up:
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
Das möchte ich meinen. Der Zustand des Codes ist nur was für Hartgesottene.
Bei Schick gibt es aktuell eine Binäräquivalenzcodedifferenz von 4740 Bytes.
Diese Zahl sollte mit fortschreitenden Arbeiten kleiner werden. Heut morgen waren es 4879 Bytes.

Eine andere Fortschrittsmetrik ist die Anzahl der auskommentierten Zeilen in der Datei symbols.h.
Dort sind es aktuell 15%.

Für Interessierte: Der Status des Spiels (Savegame) befindet sich als zusammenhängender Bereich im Datensegment.
Dieser Bereich muss gewissen Vorgaben entsprechen, damit gespeicherte Spielstände auch wieder geladen werden können.
Diese globalen Variablen beginnen mit gs_ anstelle von g_.
In diesem Bereich befinden sich u.A. auch die Farbpaletten für die Dämmerzustände. :up:

Leider befinden sich auch ein paar Zeiger (Variablen welche Speicheradressen enthalten) in diesem Bereich. :down:
Das konnte bisher u.U zu seltsamen Effekten und kaputten Spielständen führen.
Beispiele sind:
  • UNICORN_HERO_PTR,
  • RANDOM_TLK_HERO,
  • RUIN_HERO,
  • MAIN_ACTING_HERO,
  • ROUTE_COURSE_PTR,
  • ROUTE_COURSE_START,
  • ROUTE_COURSE_PTR2,
  • SEA_TRAVEL_COURSES,
  • TEVENTS_TAB_PTR,
  • TRAVEL_ROUTE_PTR,
  • TRAVEL_MAP_PTR.

Wer Bock auf ein Gedankenexperiment hat, kann sich einen Zeiger raussuchen und annehmen, dass diese Adresse ungültig ist. Mwn gibt es zwei Begegnungen mit dem Einhorn. Wer nach der ersten Begegnung speichert und den Spielstand neu läd, könnte böse Überraschungen erleben. :think:

Zitieren
Zwischenstand:
* Binäräquivalenzcodedifferenz = 4520 Bytes
* auskommentierte Zeilen in der symbols.h = 22 %
* es kompilieren auch schon einige Dateien (53 / 112) mit dem GCC
Zitieren
Danke für die Infos und Erklärungen! Ich bin immer wieder beeindruckt, dass du dich durch diese Unmengen an Programmcode durchkämpfst!
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
Es kann nur besser werden. Das ist gerade wie mit der Machete durch den Dschungel fetzen. :D :D :D

Im Moment werden Speicherzugriffe aufs Datensegment durch echte, lesbare globale Variablen
und hoffentlich gut benannte Datenstrukturen ersetzt.
Am Ende dieses Kapitels von Phase 2 sollten keine ds_read/write()-makros und auch
kein p_datseg mehr im Code sein und die symbols.h wird dann nicht mehr gebraucht.

Die Binäräquivalenzcodedifferenz ist ein wichtiger Wert, der mir herauszufinden hilft,
ob einzelne Variablen ein Vorzeichen haben oder nicht.
Wird dieser Wert kleiner oder bleibt gleich ist alles in Ordnung.
Wird er größer stimmt etwas nicht und ich muss dann genauer nachgucken.

Das bessert unsaubere Arbeiten meinerseits aus der Anfangszeit von Bright-Eyes aus,
falls ich mal ds_readb() anstelle von ds_readbs() benutzt habe.
Das entspricht dem Lesezugriff auf eine Variable im Datensegment vom Typ: unsigned char bzw. signed char.
Manchmal spielt es keine Rolle, manchmal kann so etwas auch zu Fehlern/Abweichungen geführt haben.

Wenn der Code dann in Reinform ist, ist es dann auf jeden Fall einfacher diesen Dingen nachzugehen.

Zwischenstand:
* Binäräquivalenzcodedifferenz = 4098 Bytes (geht gegen 0)
* auskommentierte Zeilen in der symbols.h = 28.6 % (geht gegen 100%)
* es kompilieren auch schon einige Dateien (54 / 112) mit dem GCC
Zitieren
(04.08.2025, 18:41)HenneNWH schrieb: Es kann nur besser werden. Das ist gerade wie mit der Machete durch den Dschungel fetzen. :D :D :D

Genau den Eindruck habe ich beim Blick auf die Commits. Durchhalten! :up: :respect:
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
Zwischenstand:
* Binäräquivalenzcodedifferenz = 4064 Bytes
* auskommentierte Zeilen in der symbols.h = 31.6 %
* es kompilieren auch schon einige Dateien (57 / 112) mit dem GCC
Zitieren
Zwischenstand:
* Binäräquivalenzcodedifferenz = 4064 Bytes
* auskommentierte Zeilen in der symbols.h = 36.1 %
* Kompilierte "seg*.cpp" Dateien mit dem GCC = (63 / 110)
Zitieren
Meine Vermutung, dass mit den Zeigern in den Spielständen etwas kaputt gehen kann hat sich bestätigt: :down: 

Bei der ersten Begegnung wird ein Held ausgewählt und die Speicheradresse (RAM-DOS) im Spielstand abgelegt.
Verändert man die Reihenfolge der Helden kann das zu Storyinkonsistenzen und/oder Abstürzen führen.

   

Dazu kommt: unter DOS hat ein Zeiger eine Größe von 4 Byte, unter 64-bit Systemen 8 Byte.
Ob mir da eine Lösung einfällt...
Zitieren
Was genau sind Zeiger? Maus-Zeiger?
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
(07.08.2025, 19:05)HenneNWH schrieb: Bei der ersten Begegnung wird ein Held ausgewählt und die Speicheradresse (RAM-DOS) im Spielstand abgelegt.

Heiland Sack!

Zitat:Dazu kommt: unter DOS hat ein Zeiger eine Größe von 4 Byte, unter 64-bit Systemen 8 Byte.
Ob mir da eine Lösung einfällt...

Doppelter Heiland Sack!!

(07.08.2025, 20:34)Obi-Wahn schrieb: Was genau sind Zeiger? Maus-Zeiger?
Andere sagen auch Senkel oder Sack. Im Zusammenhang mit "das geht mir auf den ..." ;-)

Aber im Ernst:
Anstatt den Wert einer Variable weiter zu geben, z.B hero="Alrik" gibt man die Speicheradresse, in der der Wert gespeichert ist, weiter und implizit auch, wieviel Speicherplatz ab dieser Adresse für diesen Wertreserviert ist.
Eine Variable hat also 2 Komponenten. Explizit Komponente 1, den Inhalt und Komponente 2, die Speicheradresse, wo der Inhalt steht. Komponente 2 heisst im Deutschen Zeiger, im Original "pointer".
Wenn jetzt im Spielstand "Alrik" stehen sollte und dort nur eine Speicheradresse steht, die evtl kürzer als der Platz für Alrik ist, kommt man in Teufelsküche, wenn an diesem Speicherplatz plötzlich kein Alrik sondern ein Borbarad ist. Oder sonst irgendetwas undefiniertes.
Welcome to C.

Fals ich Unsinn erzählt haben sollte, bitte berichtigen. Ist schon etwas länger her.
Grotho Garax, grotho Greifax, Graf von Gratenfels
Zitieren
Eine Zeiger/Pointer ist eine Variable welche die Speicheradresse einer Variable enthält. Der Compiler kennt zu Compilezeit auch deren Typ.
Es ist zu erwarten, dass etwas Undefiniertes an dieser Speicheradresse steht (siehe Bild).

Storytechnisch sollte der vom Einhorn erwählte Held/Heldin anders kenntlich gemacht werden.
Die Speicheradresse kann sich ebenso ändern wie die Position in der Gruppe.


Zwischenstand:
* Binäräquivalenzcodedifferenz = 4042 Bytes
* auskommentierte Zeilen in der symbols.h = 40.2 %
* Kompilierte "seg*.cpp" Dateien mit dem GCC = (65 / 110)
Zitieren
Verstehe ich das richtig: Unter DOS wurde der Spielstand nicht serialisiert, sondern 1:1 auf die HDD geschrieben, in der Annahme, dass beim nächsten Start wieder alles an derselben Adresse gespeichert wird? Wild, aber damals, ohne viel malloc/new und mit fast nur globalen Variablen, vermutlich eine valide Möglichkeit. Auf modernen Systemen gilt diese Annahme nicht, da Speicheradressen durch Mechanismen wie ASLR variieren und das Speicherlayout von Compiler, Linker und Betriebssystem beeinflusst wird.

Ich bin nicht wirklich in der Sache drin und könnte es falsch verstanden haben, aber ich würde hier einfach einen (De-)Serialisierer schreiben. Also die Zeiger beim Speichern auflösen in so etwas wie „3. Held“ und beim Neueinlesen wieder entsprechend setzen. Hilft natürlich nicht, wenn der Held dann im laufenden Spiel entfernt wird. ich vermute, er wird dann ausgenullt. In diesem Fall würde, wie hier im Screenshot zu sehen, ein „\0“ gelesen, was aber ein Bug im Originalspiel wäre und separat behoben werden müsste.

Edit: Wenn man Spielstände zwischen Versionen kompatibel halten will, kann man beim Speichern statt wie vorgeschlagen „3. Held“ den Wert DosHeldenStartOffset + 3 * Heldengröße ablegen. Beim Einlesen lässt sich der Index mit (value - DosHeldenStartOffset) / Heldengröße bestimmen und der Zeiger entsprechend setzen.
Zitieren
Ganz genau, cmfrydos.


Der Spielstatus (gs_) ist aktuell in der Datei datseg.cpp und "nur" ein zusammenhängender Bereich von globalen Variablen.
Bsp.:
Code:
Bit8s gs_datseg_status_start = 0; // ds:0x2d34, 99 = game finished, area of the datseg that is stored one to one in savegame files
Bit8s  gs_current_group = 0; // ds:0x2d35
Bit8s  gs_group_member_counts[6] = {0}; // ds:0x2d36, members per group
Bit8s  gs_total_hero_counter = 0; // ds:0x2d3c
Bit8s  gs_direction = 0; // ds:0x2d3d
Bit8s  gs_groups_direction[6] = {0}; // ds:0x2d3e
Bit16s gs_x_target = 0; // ds:0x2d44
Bit16s gs_y_target = 0; // ds:0x2d46
...


Die sauberste Lösung ist, ein unbenutztes Bit in der Datenstruktur des Helden/Heldin zu definieren.
Ist der Held nicht mehr in der Gruppe kommt das Einhorn bei zweiten mal gar nicht.
Wenn das Spiel erfolgreich beendet wurde, wird dieses Bit gelöscht und der Held/Spielstand bleibt mit Sternschweif kompatibel.

Der Zeiger (gs_unicorn_hero_ptr) und die Position (gs_unicorn_hero_pos) gehören überhaupt nicht in den Spielstand,
weil diese Daten synchron gehalten werden müssten. Single-Point-of-Truth!

Das kann aber erst umgesetzt werden, wenn Schick in dem Zustand ist, wie der Charaktergenerator zum jetzigen Zeitpunkt.
Das heißt auch, dass diese Information aus "alten" Spielständen nicht mehr genau rekonstruiert werden kann.
Hoffe dass das bei den anderen Zeigern nicht sosehr ins Gewicht fällt.
Zitieren
Zwischenstand:
* Binäräquivalenzcodedifferenz = 4036 Bytes
* auskommentierte Zeilen in der symbols.h = 42.5 %
* Kompilierte "seg*.cpp" Dateien mit dem GCC = (71 / 110)
Zitieren
Wow, du legst echt ein Tempo vor! :up:
--------
Warnung! Geschichte kann zu Einsichten führen und verursacht Bewusstsein!
Avatar by: Keven Law (CC BY-SA 2.0)
Zitieren
@Obi-Wahn: Will ja auch mal fertig werden.

Zu den 12 Zeigern/Pointer im Spielstand: Es ist mir heute gelungen für zwei Exemplare davon eine Lösung zu finden.

Ein Zeiger wird im Code nur in den Spielstand geschrieben und nie gelesen.
Das tolle daran ist, dass dieser Codeteil nie ausgeführt wurde und somit alles in Ordnung ist. :jippie:


Exemplar Nummer 2 ist der Zeiger auf die Reisekarte.
Dieser wird beim Lesen der Karte aus der SCHICK.DAT jedesmal neu gesetzt,
hat aber während des Spiels immer denselben Wert.
Für die Zukunft habe ich diesen Zeiger anderswo platziert und im Spielstand steht dort 0000. :ok:

In der DOS-Version muss der Zeiger aus Binäräquivalenzcodedifferenzgründen erstmal im Spielstand bleiben,
was durch das Aktualisieren jedoch keine negativen Auswirkungen hat,
außer dass der Spielstand pseudo-randomisierten Datenmüll enthält.


Zwischenstand:
* Binäräquivalenzcodedifferenz = 3958 Bytes
* auskommentierte Zeilen in der symbols.h = 46.7 %
* Kompilierte "seg*.cpp" Dateien mit dem GCC = (71 / 110)
Zitieren
ihr legt ja ein ganz schönes Tempo vor, wenn das für Sternenschweif und Riva genauso fix geht dann Hut ab, meinen Respekt von einem NLT-Fan der vom Programmieren aber keine Ahnung hat.
Hacke Tau, Kumpels!

Ihr seid Freunde der alten NLT? Freunde des Mikromanagements? Ihr sucht eine neue Herausforderung, weil euch die NLT zu leicht war?

Dann spielt doch mal Schicksalsklinge HD 1.36 von Crafty Studios!
Zitieren




Benutzer, die gerade dieses Thema anschauen: 1 Gast/Gäste