Status: Im Aufbau
Unser Testobjekt:
E.V.O. - Search for Eden (USA)
Ohne Header, CRC32: DD49911E
Benötigte Tools:
"Geiger's Snes9x Debugger"
"Lunar Adress und Lunar Expand", gibt's hier
"xkas v0.6", gibt's hier
Was sind MTEs/DTEs?
MTE (Multiple Table Entry) bedeutet dass mehrere Zeichen auf einem Byte in der Table liegen, zum Beispiel: 80=have
DTE (Dual Table Entry) bedeutet dass mehrere Zeichen auf 2 Bytes liegen, dabei wird das erste zum Identifizieren genommen (sollte eins sein was nicht in der normalen Table auftaucht), und das zweite legt fest welcher Eintrag es ist, zum Beispiel: 0367=someone
Bitte PM schreiben falls "MTE (Multiple Table Entry)" oder "DTE (Dual Table Entry)" die verkehrten Begriffe sind, kann mich da nur schwach erinnern :o)
Wozu dienen MTEs/DTEs?
Einfach um Platz zu sparen, eine Liste der meistbenutzten Wörter oder SIlben wird irgendwo abgelegt, mit Pointern versehen und im Haupttext wird jede dieser SIlben oder jedes dieser Wörter durch das jeweilige Byte ersetzt.
Beispiel MTE: "You have a question?" <- Länge 20 Bytes... Ersetzen wir jetzt "have" mit dem Wert 80 erhalten wir "You \0x80\ a question?" und unser Text ist nur noch 17 Bytes lang.
Beispiel DTE: "Ask someone else!" <- Länge 17 Bytes... Ersetzen wir jetzt "someone" mit dem Wert 0367 erhalten wir "Ask \0x0367\ else!" und unser Text ist nur noch 12 Bytes lang.
Vorbereiten der ROM (MTE):
Als erstes expandieren wir die ROM auf 16MBit, oder ihr sucht euch freien Platz irgendwo, dann erstellen wir uns eine Pointertable mit unseren TEST MTEs.
Beispiel: PC Offset 0x180800, SNES LoRom Adresse $30:8800
Finden der Textroutine:
Als erstes sucht ihr euch etwas Text in der Rom, am besten irgendwo am Anfang des Spiels, in unserem Beispiel nehmen wir "Hi! My name is Gaia......", zu finden an Offset 0xEE8F3, dann konvertieren wir das Ganze in eine LoRom Adresse $1D:E8F3, mit dieser Adresse starten wir nun Snes9x und warten das der Breakpoint ausgelöst wird.
Im Debug Log sollte nun dies auftauchen: $03/B608 B9 00 00 LDA $0000,y[$1D:E8F3] A:002E X:0000 Y:E8F3 P:envmxdIZc
Wenn wir jetzt einige Male "Step Into" machen, können wir so ziemlich die ganze Routine sehen:
Und so weiter, und so fort...
Ich habe die ganze Routine mal nachgeschrieben und mit Kommentaren versehen, damit man es besser nachvollziehen kann:
Einige Infos vorab:
- Unser Rombank im Beispiel ist $1D
- Unser erste Rombank Position ist $E8F3 (also Register Y=$E8F3)
- Der OR Key an Offset $32 ist hier immer $2000
READ_NEXT_LETTER:
LDA $0000,y —————————> ; Y enthält unsere derzeite Position in der Rombank in der wir uns befinden ($1D:E8F3)
AND #$00FF ——————————> ; AND Operation, z.B.: (Wert $6948 in A) -> $6948 AND �FF = $0048
ORA $32 —————————————> ; OR Operation, z.B.: (Wert $0048 in A) -> $0048 OR $32 = $2048
STA $34 —————————————> ; Speichern von A in die Ram Adresse $34 (Unsere Bytes $2048 nach Offset $34)
AND #$00FF ——————————> ; AND Operation mit A ($2048 AND #$00FF = $0048)
CMP #$00FF ——————————> ; Vergleichen (CoMPare) ob der Wert in A $00FF ist
BEQ END_FOUND ———————> ; Wenn Ja (BRanch Equals), dann gehe zu END_FOUND (siehe weiter unten)
CMP #$00FE ——————————> ; Vergleichen (CoMPare) ob der Wert in A $00FE ist
BEQ NEXT_LINE_FOUND —> ; Wenn Ja (BRanch Equals), dann gehe zu NEXT_LINE_FOUND (siehe weiter unten)
Wenn der Code bis hierhin durchläuft haben wir weder einen Zeilenumbruch, noch
das Ende unseres Textes erreicht, sondern wir haben einen normalen Buchstaben
PHX —————————————————> ; X in den STack schieben (PusH X)
TXA —————————————————> ; Transferiere X nach A
CLC —————————————————> ; CLear Carry, löscht den Carry falls was "drin" ist
ADC $36 —————————————> ; ADd with Carry , adde das was an Ram Offset $36 steht zu A
TAX —————————————————> ; Transferiere A nach X
LDA $34 —————————————> ; LaDe nach A was an Ram Offset $34 steht (Unser Buchstabe)
STA $7F6000,x ———————> ; STore A, Speichern von A an Ram Offset $7F6000+x
INY —————————————————> ; INcrease Y (Unsere derzeite Position in der Rom)
PLX —————————————————> ; PuLl X, jetzt holen wir uns unser Index Register X wieder zurück
INX —————————————————> ; INcrease X (Unser Schreiboffset für die Buchstaben)
INX —————————————————> ; INcrease X
JMP READ_NEXT_LETTER
NEXT_LINE_FOUND:
LDA $36 —————————————> ; LaDe nach A was an Ram Offset $36 steht
CLC —————————————————> ; CLear Carry, löscht den Carry falls was "drin" ist
ADC #$002E ——————————> ; AD to Carry, adde $002E zu A
STA $36 —————————————> ; STore A, Speicher A in Ram Offset $36
INY —————————————————> ; INcrease Y, Y erhöhen (Unsere derzeite Position in der Rom)
LDX #$0000 ——————————> ; LaDe $0000 in X
JMP READ_NEXT_LETTER
END_FOUND:
TXA —————————————————> ; Transferiere X nach A
CLC —————————————————> ; CLear Carry
ADC $36 —————————————> ; ADd with Carry , adde das was an Ram Offset $36 steht zu A
TAX —————————————————> ; Transferiere A nach X
LDA #$FFFF ——————————> ; LaDe $FFFF in A
STA $7F6000,x ———————> ; STore A, Speicher A in Ram Offset $7F6000+x
JML $03B653 —————————> ; JuMp Long, Springt zu dem Punkt nach der Originalen Routine
Jetzt packen wir ein wenig eigenen Code dazu, alles was neu ist ist Rot markiert:
org $308000
READ_NEXT_LETTER:
LDA $0000,y —————————> ; Y enthält unsere derzeite Position in der Rom
AND #$00FF ——————————> ; Die letzten 8 Bit prüfen und in A laden
ORA $32 —————————————> ; R auf den Akkumulator mit dem Wert der in der Ram Adresse $32 steht
STA $34 —————————————> ; Speichern von A in die Ram Adresse $34 (Unser Buchstabe)
AND #$00FF ——————————> ; Die letzten 8 Bit prüfen und in A laden, entfernt OR wieder
CMP #$00FF ——————————> ; vergleichen ob der Wert in A $FF ist
BEQ END_FOUND ———————> ; Wenn Ja (BRanch Equals), dann gehe zu END_FOUND (siehe weiter unten)
CMP #$00FE ——————————> ; vergleichen ob der Wert in A $FE ist
BEQ NEXT_LINE_FOUND —> ; Wenn Ja (BRanch Equals), dann gehe zu NEXT_LINE_FOUND (siehe weiter unten)
CMP #$0080 ---------—> ; CoMPare, Vergleichen ob A den Wert $0080 oder höher hat, wenn ja wird der "Carry Flag" gesetzt
BCS READ_MTE_POINTER-> ; Branch if Carry is Set, Wenn der Carry Flag gesetzt ist, springe weiter und lese den MTE Pointer
PHX —————————————————> ; PusH index register X, speichern von X im Stack
TXA —————————————————> ; Transferiere X nach A
CLC —————————————————> ; CLear Carry
ADC $36 —————————————> ; ADd to Carry , adde das was an Ram Offset $36 steht zu A
TAX —————————————————> ; Transferiere A nach X
LDA $34 —————————————> ; LaDe nach A was an Ram Offset $34 steht (Unser Buchstabe)
STA $7F6000,x ———————> ; STore A, Speichern von A an Ram Offset $7F6000+x
INY —————————————————> ; INcrease Y (Unsere derzeite Position in der Rom)
PLX —————————————————> ; PuLl X, jetzt holen wir uns unser Index Register X wieder zurück
INX —————————————————> ; INcrease X (Unser Schreiboffset für die Buchstaben)
INX —————————————————> ; INcrease X
JMP READ_NEXT_LETTER
Sollte unser "Carry Flag" gesetzt gewesen sein, landen wir hier
READ_MTE_POINTER:
INY -----------------> ; Y erhöhen
PHY -----------------> ; Y in den Stack verschieben
PHX -----------------> ; X in den Stack verschieben
SBC #$0080 ----------> ; SuBstract with Carry, wir ziehen jetzt $0080 von unserem gelesenen Wert ab
ASL -----------------> ; Arithmethik Shift Left, einmal nach links "shiften", ist dasselbe wie mit 2 multiplizieren
TAX -----------------> ; Transferiere A nach X
LDA $318800,x--------> ; LaDe nach A von Offset $318800+X
TAX -----------------> ; Transferiere A nach X
Das Lesen des Pointers der zum MTE Eintrag führt ist beendet, weiter zum lesen des MTEs
JMP READ_MTE
Jetzt lesen wir unseren MTE, den Pointer dahin haben wir vorher generiert und in X abgelegt
READ_MTE:
LDA $318800,x -------> LaDe nach A von Offset $318800+X,
AND #$00FF ----------> Wir holen uns die Bits die wir brauchen
CMP #$00FF ----------> Vergleichen ob wir das Ende erreicht haben (Terminierer ist $FF hier)
BEQ MTE_READ_END ----> Wenn das Ende errecith wurde ($FF), dann sind wir fertig mit lesen und springen weiter
ORA $32 -------------> OR mit dem Wert an Offset $32 auf A
STA $34 -------------> Speichern von A an Offset $34
TXY -----------------> Transferiere X nach Y
INY -----------------> Erhöhe Y um 1
PLX -----------------> X aus dem Stack holen
TXA -----------------> Transferiere X nach A
CLC -----------------> CLear Carry
ADC $36 -------------> ADd with Carry, adde das was an Offset $36 steht zu A
INX -----------------> Erhöhe X um 1
INX -----------------> Erhöhe X um 1
PHX -----------------> PusH X, Speichern von X im Stack
TAX -----------------> Transferiere A nach X
LDA $34 -------------> LaDe nach A von Adresse $34
STA $7F6000,x -------> Speichern unseres generierten Wertes im RAM
TYX -----------------> Transferiere Y nach X
JMP READ_MTE --------> Da unser Vergleich am Anfang der Routine kein Ende errreicht hatte springen wir zurück und lesen das nächste Byte, bis es den Wert $FF hat
Wenn unser Terminier-Byte ($FF) erreicht wurde ist das Lesen des MTEs beendet unn wir holen uns unsere Offsets für die normale Routine die wir ganz am Anfang in den Stack geschoben haben wieder zurück, damit die Routine damit weiter arbeiten kann
MTE_READ_END:
PLX -----------------> X aus dem Stack holen
PLY -----------------> Y aus dem Stack holen
JMP READ_NEXT_LETTER-> Und hier geht's wieder zurück um den nächsten Buchstaben zu lesen
NEXT_LINE_FOUND:
LDA $36 —————————————> ; LaDe nach A was an Ram Offset $36 steht
CLC —————————————————> ; CLear Carry
ADC #$002E ——————————> ; AD to Carry, adde 002E zu A
STA $36 —————————————> ; Speicher A in Ram Offset $36
INY —————————————————> ; INcrease Y (Unsere derzeite Position in der Rom)
LDX #$0000 ——————————> ; LaDe $0000 in X
JMP READ_NEXT_LETTER
END_FOUND:
TXA —————————————————> ; Transferiere X nach A
CLC —————————————————> ; Clear Carry
ADC $36 —————————————> ; ADd to Carry , adde das was an Ram Offset $36 steht zu A
TAX —————————————————> ; Transferiere A nach X
LDA #$FFFF ——————————> ; LaDe $FFFF in A
STA $7F6000,x ———————> ; Speicher A in Ram Offset $7F6000+x
JML $03B653 —————————> ; Springt zu dem Punkt nach der Originalen Routine