Seite 1 von 1

Dwarf disassembled

Verfasst: 16. Oktober 2012, 19:00
von lytron
Hier ist ein Bericht, den ich geschrieben habe, während ich die Public-Domain-ROM "Dwarf" disassembled habe.
Dwarf (PD)_00001.png
Dwarf (PD)_00001.png (21.28 KiB) 3164 mal betrachtet
"Dwarf" ist eine Demo aus den Neunziger Jahren, in der eine Spielfigur über eine Graslandschaft und eine Brücke gesteuert werden kann, die jedoch nicht in einen vorbeifließenden Bach tritt. Mein Wunsch, diese ROM zu disassemblen, kam aus dem Interesse heraus, die Kollisionsabfrage zu verstehen und eventuell Anregungen für zukünftige Projekte mitzunehmen, wie man soetwas machen kann - oder, wie man es nicht machen sollte.
Diesen Bericht schreibe ich, da es sicherlich interessant zu sehen ist, wie man an soetwas herangeht.
---
Zu aller erst schaue ich mir die Datei in einem Hexeditor an (ich nehme HxD).
01 Hex.png
01 Hex.png (94.7 KiB) 3164 mal betrachtet
Ich bin zunächst verwundert, weshalb die Zeilen $000 bis $1FF fast vollständig leer sind und der Sourcecode erst ab Zeile $200 beginnt. Ich gehe zu diesem Zeitpunkt davon aus, dass es sich bei den ersten $200 Bytes um den SNES-Header handelt (was falsch ist; später dazu mehr). Ungefähr bis Zeile $7CF sind die Hexwerte ein sehr dichtes Gedränge mit wenigen "00", weshalb ich das als den Code ansehe. Danach kommen zwar immer noch einige Zahlenkombinationen jenseits der Doppel-Null, allerdings sind sie wesentlich "entzerrter", weshalb ich schätze, dass das alles bereits Grafikdaten sind. Ab $4600 ist die Datei schließlich vollkommen leer, allerdings geht sie bis $81FF weiter. Ich kann mir das zu diesem Zeitpunkt nicht erklären, auch nicht, weshalb der vorvorletzte Byte dann plötzlich doch noch einen Wert enthält.
Also suche ich als nächstes mir brauchbare Tools zusammen. Ich wende mich per PM an RedScorpion und frage ihn nach einem Tool, mit dem ich direkt im Sourcecode nach Grafiken suchen kann. Er empfiehlt mir TileMolester. Außerdem suche ich mit der Suchmaschine unser aller Misstrauens nach dem Schlagwort "SNES Disassembler". Das Programm DisPel taucht auf. Mit diesem habe ich bereits ein bekanntes, in Deutschland lokalisiertes Rollenspiel aus reiner Experimentierfreude vor einigen Monaten disassembled, und wollte es auch gern für dieses Projekt nutzen. Leider beschwert sich dieses bei der Dwarf-Datei, dass sie scheinbar zu klein ist, und verweigert die Arbeit. Also nehme ich das nächstbeste Suchergebnis, was ein JavaScript-gestützter Online-Disassembler ist.
Als nächstes will ich nun aber wissen, was das mit dem komischen SNES-Header ist, und suche bei selbiger Suchmaschine nach "SNES Header". Der erste Treffer ist eine Seite von Rom Hack City, in die ich mich kurz einlese und feststelle: Mensch, die ersten $200 Bytes sind der SMC-Header! Der richtige SNES-Header liegt demnach bei $81c0 bis $81ff, was wiederum erklärt, weshalb die Datei so groß ist. Außerdem erklärt es, warum dort plötzlich ein Byte gesetzt ist - mithilfe der Rom-Hack-City-Seite verstehe ich auch, dass es sich dabei um den "RESET"-Vektor im Native Mode handelt, folglich der Vektor, der zeigt, wo das Spiel anfängt. Was ja auch Sinn macht, da das Spiel dank des SMC-Headers nicht bei $0000 anfängt. Ich freue mich wahnsinnig, wie sich alles in ein Bild zusammenfügt. Ich verstehe in jenem Moment zwar nicht, warum das Ding einen SMC-Header hat, im Nachhinein erkläre ich es mir aber damit, dass dieses Programm wohl offensichtlich mit einem SMC geschrieben wurde und deshalb diesen Header hatte/brauchte.
Jetzt schaue ich mit dem Tilemolester das erste Mal in die Datei hinein und sehe:
03 Tilemolester.png
03 Tilemolester.png (46.88 KiB) 3164 mal betrachtet
Das macht alles irgendwie auch Sinn. Der obere, bunte Streifen ist als Grafik missverstandener Sourcecode. Danach kommen nach gewissem Abstand einige kleine Buchstaben, dann sehen wir in Rot und Blau den "Black Widow" Schriftzug und darunter, "phasenverschoben", die Sprites der Spielfigur und abschließend das Wasser.
Jetzt disassemble ich die ROM mithilfe oben angeführter Seite. Ich markiere in HxD die Zeilen $200 bis $7CF, füge sie dort ein und lasse disassemblen. Ich speichere das Ergebnis in zwei Textdateien: Einmal lasse ich den Hexcode und die Zeilenangabe stehen (ich vergesse natürlich, den Offset auf $200 zu setzen, folglich fängt er an, die Zeilen bei $000 und nicht bei $200 zu nummerieren. Das wird mir bestimmt noch einige Probleme bereiten, aber mai, ich bin zu faul es jetzt zu ändern. In die zweite Datei speichere ich nur den bloßen Assemblercode, das wird mir dann beim testweisen Neu-Assemblen helfen.
02 Disassemble.png
02 Disassemble.png (106.87 KiB) 3164 mal betrachtet
Also... fange ich an, mir den Sourcecode durchzulesen.
Ich lese los und stelle sofort etwas fest, was mir gar nicht gefällt. Ich sehe als siebten oder achten Befehl ein "BRK", was absolut keinen Sinn macht. Dann schaue ich mir die Zeilen davor an. "REP #$30" setzt die drei Prozessor-Register auf 16 Bit. Danach kommt ein "LDA #$00", danach kommt der "BRK"-Befehl mit einem Argument. Unerfreulich.
Glücklicherweise weiß ich, dass der "BRK"-Befehl den Hexcode "00" hat. Das heißt: Eigentlich müsste hier stehen "LDA #$0000", das Argument des BRK-Befehls selbst ist ein Opcode. Da steigt in mir eine gewisse Panik auf, dass alles nachfolgende aufgrund dieses Fehlers des Disassemblers falschrum interpretiert wurde, dass Opcodes als Argumente angesehen wurden und andersherum...
Dann spiele ich mit dem Hexcode im Disassembler herum und bekomme es so dargestellt, dass er mir die $58 als Opcode ausgibt: TCD. Setze den 16-Bit-Akkumulator als Direct Page Register. Glücklicherweise ist das ein Befehl vollkommen ohne Argumente, das heißt der Rest meiner Datei war doch noch richtig vom Disassembler interpretiert worden. In jedem Fall weiß ich, dass ich bei den 16-Bit-LDA-Befehlen genau schauen muss, ob sie wieder falsch interpretiert werden und alles nachfolgende "falschrum" ist.
Ich fange an, Kommentare zu schreiben, um den Sourcecode zu gliedern. Zu beginn sehe ich viele STA-Befehle, die ich als Register-Clearen ähnlich der SNES_Init.asm erkenne, falls mir ein mir nicht-geläufiger ASM-Befehl begegnet, schreibe ich mir als Kommentar dahinter, was er bedeutet.
Beim Durchschauen des Codes, wo scheinbar "nur" Register gecleart werden, werden auch einige Voreinstellungen bereits vorgenommen. Es werden Werte in Mode-7-Register gespeichert, es wird #$E0 in Register $2132 gespeichert, offensichtlich wird der "alte" Joypad-Style genutzt - alles Sachen, von denen ich keine Ahnung habe. Aber, ich mache einfach weiter, ich schaue einfach, wie weit ich komme.
Mir werden wohl des öfteren noch solche Fehler begegnen, bei deinen LDA- oder LDX-Befehle nicht korrekt übersetzt werden. Gerade hatte ich wieder ein solches "Ereignis", im ersten DMA-Prozess zur Übertragung der Farben. Das habe ich aber größenteils per Hand rekonstruiert, wie es eigentlich gemeint war:
Der Code ist ab $388 in der ROM zu finden, und lautet: A2 91 85 8E 02 43
Der Disassembler machte daraus: LDX #$91 STA $8E COP $43
Ich habe mir nicht vorstellen können, dass diese recht simple ROM einen Co-Prozessor benutzt (darüber hinaus weiß ich ja, dass der COP-Vektor nicht im SNES-Header gesetzt ist. ;) ). Außerdem wusste ich ja, dass die Indexregister 16 Bit groß sind. Also hieß es wohl: LDX #$8591. Mit dem Disassembler fand ich dann heraus, dass "$8E" STX ist, was dann auch plötzlich Sinn macht. Speziell, wenn es gerade um DMA geht und die nachfolgenden Bits dann die Adresse $4302 ergeben, wobei alles, was im Bereich $430x liegt, mit dem DMA Channel 0 zusammenhängt.
Für mich ist das gerade ein wirkliches Glücksmoment, denn gerade spielt alles wissen, was ich habe, zusammen und ergibt ein großes, logisches Ganzes. Darum interessiere ich mich so für das Thema SNES-Programmierung. Bleibt nur noch herauszufinden, was es bedeutet, #$8591 in $4302 zu speichern! ^_~
Ergibt sich nächstes Problem: Das bedeutet, dass die Adresse der zu übertragenden Daten für das DMA bei #$8591 liegt. Zu dumm, dass die ROM, wie oben erwähnt, nur bis #$81FF geht und dann zu Ende ist! Die Lösung kam durch generve im SNESFreaks-Chat, in dem mich ikari_01 darauf hinwies, dass bei LoROM immer nur die obere Hälfte einer Bank befüllt wird, soll heißen: Es wird nur $8000-$FFFF von jeder Bank genutzt, was wiederum bedeutet: Wenn $8591 dort steht, ist in der ROM $0591 gemeint; beziehungsweise $0791, denn die ersten $0200 gehören ja nicht zur ersten Bank der ROM-Daten, sondern sind der SMC-Header.
Also, teste ich das. Ich kopiere die smc-Datei, öffne sie im Hexeditor und schreibe andere Werte ab $0791 hinein, ungf. sechs bis zehn Bytes. Ich schreibe recht niedrige Doppelbyte-Werte rein. Da ich weiß, dass es sich hier um Farben handeln muss, muss dann damit irgendetwas dunkler werden. (Ich zerschieße gerne absichtlich Grafikdaten und schaue dann in einem Emulator nach, wo etwas kaputt gegangen ist, um herauszufinden, wo diese Daten jeweils hingehören).
Das Ergebnis ist folgendes:
04 Screen.png
04 Screen.png (19.57 KiB) 3164 mal betrachtet
Die Wolken sind schwarz geworden.
Also geht der Sourcecode (oder zumindest dieser "Satz" an Code) nur bis einschließlich $0790, ab $0791 beginnen die Palettendaten für die Wolken. Ich gucke weiter in den disassembleten Daten und sehe, dass $200 Daten übertragen werden. $200 Byte sind 512 Byte, und bei 2 Byte pro Farbe sind das 256 Farben, was wiederum bedeutet, dass es sich hierbei nicht um die Palette für die Wolken handelt, sondern um alle Paletten. Das teste ich, in dem ich in meiner Test-Kopie der smc-Datei bei ungefähr $0980 wiederum Werte durch einen beliebigen Wert ersetze. Ich schaue in meinem Emulator nach und finde den Figuren-Sprite in schwarzer Kleidung vor, was mir wiederum sagt: Korrekt, es sind die kompletten Farbdaten, und außerdem: Die Sprite-Farben liegen recht weit hinten. Schön. So weit, so gut.
Folglich erstelle ich jetzt eine Textdatei in dem Ordner mit meinem disassembleten Quelltext, benenne sie in "palette.inc" um, öffne Sie mit dem Hexeditor und kopiere aus Dwarf.smc die Zeilen $0791 bis $0990 in diese Datei. Jetzt habe ich die Palette als Binärdatei, zum Includen (.incbin) in den disassembleten Text. Woohoo! Die ersten Grafikdaten beisammen.
...
Habe gerade den gesamten Nachmittag an den nachfolgenden 5 Zeilen Code verbracht, u. a. weil ich das Zweier-Komplement immer noch nicht verstanden habe/hatte.
...
$2AD1 bis $2CF0: OAM Data
$0991 bis $1450: BG2 Tiles
$3811 bis $39A0: BG3 Tiles
$2CF1 bis $3010: BG1 Tiles
$1451 bis $22D0: Sprites Tiles
$22D1 bis $2AD0: BG2 Tilemap
$3011 bis $3810: BG1 Tilemap
$39A1 bis $41A1: BG3 Tilemap
Nachdem ich den gesamten Nachmittag auf der Stelle getreten bin, "flutscht" es gerade sehr gut. Ich bin gerade knapp zur Hälfte durch den Sourcecode durch. Natürlich ist das noch kein Verdienst, da gerade nur das grobe Handwerk erledigt wurde - bis jetzt wurden nur einige Bildschirm-Einstellungen vorgenommen und alle Grafikdaten per DMA übertragen. Aber zumindest das ist damit dann erst einmal absolviert.
...
Sehr interessant. Ich kenne es bisher so, dass man den NMI-Vektor setzt, um während eines VBLANKs etwas zu tun, und einen WAI-Befehl setzt, um nach dem VBLANK etwas zu tun. Hier wird mit dem Most Significant Big von Register $4210 gearbeitet.
...
Dieser Disassembler ist kacke, habe ich es schon erwähnt? Bei INX verschluckt er den nächsten Opcode, weil er ihn für ein Argument hält, bei INY geschieht das nicht. Denselben Fehler wie bei INX macht er auch bei PLP. Ich brech ins Essen.
...
Hui, die Subroutine für die Wasseranimation ist aber verschachtelt. Hier mal die Namen meiner Labels und die Hex-Adressen in der Original-ROM:
$055B CloudMovement
$0566 JoyPadRead
$0591 DPadRIGHT
$0596 DPadLEFT
$059B DPadDOWN
$05A0 DPadUP
$072E WaterAnimation
$0735 WaterStep1
$0739 WaterStep2
$073B WaterUpdate

...
Das Frustrierendste am Disassemblen ist wohl das Branchen. Ich kann mir zwar *vorstellen* wo die Abzweigung hingeht, aber ich gucke es sicherheitshalber trotzdem nochmal nach (um im Vorhinein Fehlerquellen zu minimieren), und das konstante Rechnen mit Hexzahlen in fünf Fenstern geht auf den Geist.
...
Ah, okay. In die Variable, die in $08 liegt, wird die Blickrichtung gespeichert. Darum wird sie auch zu Anfang auf #$06 gesetzt, damit Figürli nach unten schaut.
...
Ich bin jetzt dazu übergegangen, nun doch Variablen zu definieren; das hantieren mit nichtsagenden Adressen wie $08 und $0A wird auf dauer doch zu unübersichtlich. Ich weiß nicht, wozu eine bestimmte Variable gut ist, ich werde ihr deshalb jetzt einen Provisorischen Namen geben (und sei es nur "VERY_IMPORTANT_VARIABLE"). Dank meiner Hi-End-Software (WordPad) kann ich ja mittels des Shortcuts STRG + H den Namen ändern, wenn ich den tatsächlichen Nutzen herausgefunden habe...
---
Habe ich es schon erwähnt? Ich finde es sehr interessant, dass man keinen CMP-Befehl braucht, wenn man eine Variable lädt und schauen will, ob sie leer ist. Man kann direkt "LDA variable BEQ label" machen. Wusste ich nicht, dass das geht.
...
Okay, okay, okay.
Ich bin verloren.
Vollends.
Ich versuche mich seit zwei Stunden nur noch irgendwie hier durchzuhangeln, und jetzt gebe ich auf. Zumindest mit dieser Methode.
Das Problem ist, dass man, um das Ganze zu entwirren, bis zum Ende gehen muss und dann "rückwärts" läuft. Es wird ein Wert in eine Variable gesetzt, verglichen, multipliziert, mit einem zweiten unbekannten Variablenwert verglichen, dem Ergebnis des Vergleichs entsprechend gebrancht, von neuem multipliziert, addiert, dividiert, und anschließend dieser Wert abgespeichert.
Da die Variablen keine Namen mehr haben (und selbst wenn, dann diese so kryptisch sein könnten, dass mir die Namen auch nicht helfen), muss ich schauen, wo diese Variablen an Dingen anknüpfen, die ich bereits kenne, also wo ihre "Final Destination" ist, ihr entgültiger Bestimmungsort. Erst von diesem Punkt aus kann ich überblicken, wozu sie genau gebraucht werden, und kann dann schauen, wozu es gut war, dass sie auf diese und jene Weise umgerechnet sind.
Für mich stellt sich das so dar: Ich habe am Anfang des Programms begonnen, und konnte dem am Anfang sogar sehr stringend folgen, einmal, weil keine/kaum Branches kamen, ein andern Mal, weil die direkte Verwendung sehr schnell offensichtlich wurde. Beim jetzigen Punkt, wo ich angekommen bin, ist das nicht so schnell offensichtlich geworden.
Es ist wirklich ein sehr guter Vergleich, es mit einem dunklen Wald, Irrgarten oder handelsüblichen Labyrinth zu vergleichen. Der Hauptpfad ist bis jetzt gut ausgeleuchtet, und an dieser Stelle teilt er sich in viele einzelne, dunkle Wege. Ich habe versucht, einem der dunklen Wege zu folgen, bis ich auf eine "Randbegrenzung" komme: Um in den Bildern zu bleiben: Waldrand bzw. Lichtung, oder eine Außenmauer, Zaun, Außenhecke o. ä. Diese "Randbegrenzung" wäre für mich die erwähnte "Final Destination" einer Variable, bzw. auch nur, dass sich die gesamte Verästelung der Subroutinen beendet und ich zum "Hauptfluss" des Programmes mal zurückkehren mag. All das ist nicht passiert. Und da fühlt man sich ein wenig wie ein Schwimmer in einer Grotte, der versucht, unter einem Felsvorsprung hindurchzutauchen, der sich als länger erweist als gedacht, und dem allmählich die angehaltene Luft ausgeht.
Folglich versuchte ich "schneller durch den dunklen Wald zu laufen"; ich habe mir nicht mehr versucht genau zu merken, wie die einzelnen Variablen umgeformt wurden, sondern nur noch ansatzweise zu merken, was ich meinte, wozu sie genutzt werden könnten. Ein nächster Punkt war, auch wenn ich mich verirrt habe, mich an meiner Umgebung und ihren Besonderheiten zu orientieren. Soll heißen: Ich kann die ganzen Sprungadressen nicht adäquat benennen, da ich nicht weiß, was der Kram macht, wohin sie führen. Aber ich kann zum Beispiel eine Subroutine "Division" nennen, wenn dort dividiert wird, was ich noch niemals vorher in einem SNES-Programm gesehen habe.
Also, damit ich *überhaupt* irgendwie vorwärts komme, werde ich schauen, jetzt zumindest im Groben die Sprungadressen und Subroutinen auseinander geteilt zu bekommen. Irgendwann muss ich damit zu einem Ende kommen, oder zumindest überblicken können, wo die Werte am Ende landen, und dann werde ich mich von dieser Seite heran arbeiten.
Heißt also: Ich werde durch den Wald irren, bis ich irgendwo rauskomme, und von dort aus mir einen Weg zurück bahnen.
Oh, sollte ich erwähnen, dass ich gerade in all dieser Verwirrung glaube, die Kollisionsabfrage gefunden zu haben?
...
You are with stupid.
Gerade habe ich eine Subroutine gefunden, die ich gestern schonmal angefangen habe zu bearbeiten, aber nicht fertig gemacht habe. Von dieser Subroutine aus betrachtet ist der Rest ein Kinderspiel:
die Subroutine ging von einer Variable aus, deren Wert in der Subroutine für die Joypad-Daten-Abfrage gesetzt wird. Entscheidend nach diesem Wert wird eine von vier verschiedenen, weiteren Subroutinen aufgerufen, in denen sich mindestens zwei weitere Variablen für mich erklären, nämlcih die Zwischenspeicher für die X- und Y-Koordinate. Desweiteren werden von diesen vier Subroutinen immer dieselben zwei Subroutinen aufgerufen, eine für mathematische Umformung zweier Werte zu einem Wert für eine dritte Variable, deren Inhalt sich dadurch auch erklärt, die zweite für die Kollisionsabfrage.
Und ich denke...
... ich befürchte...
... damit hat sich dann der größte Teil des Programms auch schon erledigt.
...
Erschreckend, ja. Es hat sich viel erledigt. Gerade bereitet mir eher mangelnde Konzentration größere Probleme als der Sourcecode ansich.
Der Grund, weshalb ich dieses Projekt betrieben habe, hat sich geklärt. Zeit für Fettdruck.
DIE KOLLISIONSABFRAGE
Es gibt eine Collision Map. So habe ich das mal genannt. Man findet sie in der ROM ab $41A1. Wenn sie mit einem Zeilenumbruch mit 32 betrachtet, sieht man es sehr deutlich:
05 CollisionMap.png
05 CollisionMap.png (42.46 KiB) 3164 mal betrachtet
Es werden die X- und Y-Koordinaten des Sprites genommen, in eine laufende Nummer um gerechnet (32 Tiles sind in einer Zeile, also Y*32 + X, also: Ganz linker Tile in der ersten Zeile: 0, ..., ganz rechter Tile der ersten Zeile: 31, ganz linker Tile der zweiten Zeile 32 usw.). Da die Collision Map selbst nach diesem Muster aufgebaut ist, kann er so sehr leicht schauen, welchen Byte er aus dieser Collision Map braucht. Er lädt diesen Byte und prüft ihn, und wenn der Wert ihm nicht passt, wird eine Variable auf eins gesetzt, andernfalls bleibt sie null. Am Ende wird geprüft, ob besagt Testvariable = 1 ist, wenn ja, wird die gerade ausgeführte Bewegung (ein Pixel nach rechts, links, oben oder unten) rückgängig gemacht.
...
Oh, Korrektur. Bzw., Spezifikation:
Es werden zwei Variablen genommen, in die werden die "neuen Koordinaten" gespeichert, dann werden die getestet, ob sie passen oder kollidieren, und wenn es passt, werden die wirklichen Daten geupdatet.
...
Gut. Nachdem ich den Großteil des Codes entschlüsselt habe und das gefunden habe, was mich eigentlich interessierte (die Kollisionsabfrage, s. o.), ist der Rest eigentlich nur Makulatur, woll?
Denkste.
Der Rest ist ein wirrer, Wirrwarr, der mich verwirrt.
Ich weiß, dass es sich dabei um die Lauf-Animation des Helden handelt. Das weiß ich, weil ich die bisher noch nicht gefunden habe und nicht mehr viel anderen undurchschauten Code habe, um das zu prüfen.
Unvorteilhaft hierbei ist, dass die Variablen, die hierbei genutzt wurden, sich nicht mit denen überschneiden, die für den Rest genutzt wurden, das heißt es ist insofern auch "Neuland", was ich hier betrete, unabhängig von dem, was ich bereits herausgefunden habe. Das nächste Problem ist, dass sehr viele Funktionen verschachtelt sind; es werden ziemlich durchgängig zwei Variablen genutzt und deren Werte umgeformt (und als Mittel zur Adressierung genutzt, um Werte aus einer Tabelle zu entnehmen, die mir gar nichts sagt, und die ich deshalb "Special Value Table" getauft habe), und abhängig davon wird von einer Subroutine nur ein bestimmter Teil ausgeführt oder der Ganze (entweder wird eine Sprung-Marke genutzt, die direkt am Anfang der Subroutine liegt, oder eine, die ziemlich mittig platziert ist).
Um das Ganze zu entwirren beschloss ich, ein "DO NOT TRY AT HOME"-Verfahren anzuwenden. Ich habe mir ein Schmierblatt genommen und mitgeschrieben, wie bestimmte Variablen umgeformt werden und wo sie am Ende ankommen. Welche Variable ich dafür nehmen würde, war mir dann auch recht schnell klar: Im "Haupt-Cycle" werden zweimal dieselben beiden Subroutinen aufgerufen, allerdings wird vorher in Variable $0E einmal 0 und einmal 1 gespeichert.
Die erste Subroutine verformt die Werte nur ein wenig und liest dementsprechend etwas aus der "Special Value Table" aus. Das verwirrende ist, dass es in Abhängigkeit von $0E geschieht, also nur zwei Varianten geben kann (nämlich die, wo am Anfang $0E = 0 und die andere wo $0E = 1), und beide zu demselben Ergebnis führen: $10 = $04. Wegen des Inhalts besagter Tabelle. Wobei das seltsamste dabei ebenfalls ist: Diese Tabelle liegt in der Memory und wird zu Anfang in den RAM übertragen. Allerdings wird ein Doppelbyte gelesen, in $1900 abgespeichert, dann werden in der Tabelle vier Doppelbytes übersprungen, und erst der fünfte Doppelbyte direkt neben den ersten Doppelbyte bei $1902 (sic!)* abgelegt. Wahrscheinlich war hier noch "etwas im Entstehen". Genau wissen werde ich es erst, wenn ich alles durchgeschaut habe und am Ende feststellen werde, dass die jeweiligen Doppelbytes dazwischen wirklich keinen Nutzen haben.
*Bei $1900 liegt der eine, bei $1901 der andere Byte des Doppelbytes.
Die zweite Subroutine brancht in Abhängigkeit von den Joy-Pad-Werten (was wiederum zeigt, dass meine vormalige Aussage, dass es gar nichts mit den anderen Subroutinen zu tun hat, falsch war). Da ich die Joy-Pad-Wert-Auslese-Subroutine jetzt wie aus dem FF kenne, weiß ich, welche der beiden Subroutinen diejenige ist, die aufgerufen wird, wenn nichts passiert, und dieser folge ich (weil ich davon ausgehe, dass hier weniger passiert und ich dann schneller am Ende bin, außerdem nur "den wichtigen Teil" sehen werde).
Und am Ende vom Lied wird die OAM-Data im RAM geupdated, die am Ende jedes "Main-Cycles" (manche sagen auch forever-Loop, you know what I mean) vom RAM in die OAM geschoben wird. Das interessante dabei ist: Variable $0E stellt sich dann als die Variable heraus, die darüber entscheidet, welcher Sprite angesprochen wird. Denn unser Held besteht aus zwei Sprites, #$00 und #$01, entsprechend den Werten, die man in $0E einspeichert, bevor man die Subroutinen aufruft (#$00 ist der Kopf, #$01 ist der Körper). Das ist nicht unbedingt viel, was ich herausgefunden habe, aber mit diesen Informationen kann ich jetzt viele nichtssagende "$0E" mit einem netten Variablennamen ersetzen und kann schon im Groben auch anfangen, Subroutinen Namen zu geben. Und wenn das alles erfolgt ist, kann ich schauen, inwieweit ich den gesamten Rest überschauen kann. Notfalls nehme ich noch einmal einen Anlauf mit Schmierzettel wie zuvor.
...
Je mehr ich mich in diesen Schrott mit der Animation einlese, desto größer werden meine Fragezeichen. Es scheint durch und durch, dass hier jemand wohl ganz große Dinge vorgehabt hat. Dieses ganze Prozedere ist derart unnötig kompliziert gebaut, dass man sich wirklich vorstellen kann, dass er das für einen künftigen, größeren Zweck vorbereitet hat, zu dem es niemals gekommen ist. Den Quark zu entwirren ist kompliziert.
Um es in aller Kürze wiederzugeben: Es werden Variablen geprüft, und abhängig von ihrem Inhalt werden unterschiedliche Daten aus einer Tabelle geladen, wobei die Tabelle an den entsprechenden Stellen...
... jeweils exakt dieselben Werte enthält. Hmpf!?
...
So.
Geschafft.
Nach drei Tagen bin ich im Großen und Ganzen durch den Code durch. Ich habe einige Disassembler-Fehler ersetzt, ich habe gerade auf dem letzten Stück noch zwei Fehler gefunden, die ich gemacht habe (Hexadressen in falsche Jump Marks umgewandelt und andere Hexadressen mit falschen Variablennamen ersetzt), sodass ich jetzt, beim ersten Anlauf, den Kram zu assemblen, echt Manschetten habe, dass das Ganze zu einem zutiefst leidigen Schmarrn wird, eine weitere never-ending story, weil ich noch einige weitere Fehler gemacht haben könnte, die ich jetzt nicht gesehen habe, die mich dann zu ewiger Fehlersuche verdammen.
Ich will nicht, ich will nicht, ich will nicht, muääh... ~_~
...
Okay, es ist passiert.
Es gab viele Zicken beim Compilen, ungf. 15 Fehler, die in drei Gruppen einteilbar waren: 1) eine # bei Variablennamen zu viel, 2) ein $ bei Variablennamen zu viel, 3) ein .w bei Labels zu wenig. WLA DX war ohne die ".w"-Extension davon ausgegangen, ich wollte damit auf Variablen auf der Direct Bank verweisen, und war demnach verwundert, dass er mit diesen Dingern nichts anfangen konnte.
Das Problem, das jetzt aufgetreten ist: Der Heldensprite verschwindet nach dem Fadein. Das bedeutet, die Sprite-Update-Subroutine habe ich nicht richtig übersetzt. Was mich jetzt auch nicht verwundert, da ich durch die eh nicht sehr gut durchgestiegen bin. Was mich aber ärgert, weil ich mich da jetzt doch wieder dransetzen muss. >_<
...
Die Lösung
Die Lösung ist wirklich so wie die Moral eines Disneyfilms.
Man muss wie ein Jamaikaner Bobfahren.

All die Zeit habe ich versucht, ein Programmierer zu sein, dabei bin ich Bürokaufmann.
Es hat alles gut funktioniert, aber auf dem letzten Stück habe ich als Programmierer versagt.
Die Lösung war, es wie ein Bürokaufmann zu tun:
Die Fehlersuche gestaltete sich für mich als quasi unmöglich. Ich habe die gesamten eingebetteten Daten (Tilesets, Tables etc.) alle in dieselbe Reihenfolge gebracht wie in der Original-ROM, um noch näher am Original zu sein.
Ich hatte keine Chance den Fehler zu finden, so sehr ich mich auch bemüht habe.
Also habe ich OpenOffice Calc genommen (das weltbeste Tabellenkalkulationsprogramm, BTW), und habe dort geschrieben. Ich habe Formeln geschrieben, die mir Chunks von 512 Byte, die ich aus meinem Hexeditor herauskopiert habe, in einen-Byte-pro-Zelle isoliert. So habe ich dann den kompletten Code meiner ROM und der Originalrom in je drei Säulen gehabt. Dazwischen habe ich Spalten mit Formeln eingefügt, die die Hexwerte in Dezimalzahlen umrechenn. Und rechts daneben habe ich die korrespondierenden Werte meiner ROM und der Original-ROM von einandersubtrahiert, und wo es nicht gleich null ist, mittels bedingter Formatierung mir rot darstellen lassen. Das sah dann so aus:
06 OpenOffice.png
06 OpenOffice.png (50.01 KiB) 3164 mal betrachtet
Die Fehler waren sehr schnell gefunden. Und das Tolle ist: Als ich gesehen habe, was der Fehler war, wusste ich, dass ich nie darauf gekommen wäre.
Ein LDA-Befehl wurde falsch umgewandelt, weil ich Rauten vergessen hatte (wer sich den Sourcecode anschaut: In den Zeilen mit "SpecialValueTable.w+...". Folglich wurde das zu einem AD statt einem A9 umgehext, und so entstand der Salat. Und jetzt klappt alles.

Jetzt kommt das glorreiche Schlusswort.
Öh...

Be happy.
Be nerdy.
Be clerky!

Re: Dwarf disassembled

Verfasst: 16. Oktober 2012, 20:27
von Farbauti
Ganz großes Kino !
Dein Enthusiasmus ist echt unmenschlich, ich kann echt nicht nachvollziehen wo du die Energie hernimmst um sowas zu machen.
Wenn ich dann mal nach der Arbeit nach hause komme schaffe ich es nicht mich noch für sowas zu motivieren =(

Weiter so !

Re: Dwarf disassembled

Verfasst: 16. Oktober 2012, 20:31
von lytron
Farbauti hat geschrieben:Ganz großes Kino !
Dein Enthusiasmus ist echt unmenschlich, ich kann echt nicht nachvollziehen wo du die Energie hernimmst um sowas zu machen.
Wenn ich dann mal nach der Arbeit nach hause komme schaffe ich es nicht mich noch für sowas zu motivieren =(

Weiter so !
Ich habe zur Zeit Urlaub.

Re: Dwarf disassembled

Verfasst: 17. Oktober 2012, 17:07
von Svambo
Kleiner Tipp: Wenn ich Dateien vergleichen will um Unterschiede zu finden, nutze ich ein Programm namens Total Commander. Ist evt etwas kompfortabler als ein Tabellenkalkulationsprogramm...

Ansonsten Hut ab vor der Aktion als solches !!

Re: Dwarf disassembled

Verfasst: 17. Oktober 2012, 17:16
von lytron
Svambo hat geschrieben:Kleiner Tipp: Wenn ich Dateien vergleichen will um Unterschiede zu finden, nutze ich ein Programm namens Total Commander. Ist evt etwas kompfortabler als ein Tabellenkalkulationsprogramm...
Danke! Ich dachte mir schon, dass es dafür ein Tool gibt, aber ich war zu faul zu suchen. :D

Ansonsten danke für die Blumen, und auch dir danke, farbauti (im Nachhinein erscheint mir meine Antwort von gestern unhöflich knapp. War nicht so gemeint, war nur ein bisschen wuschig, wegen der vergangenen drei Tage, in denen ich dieses Projekt betrob). ;)