Beiträge von manakoDE

    Nur zur Info, es geht weiter mit neuem Editor. :o)



    Der neue Editor hat eine komplette Preview was das In-Game testen unnötig macht,inklusive Tonnenweise anderer Funktionen die das Übersetzen wesentlich leichter gestalten.
    Derzeit arbeiten leitwolf, Blatrix_FB und ich daran, und ich würde sagen wir haben in den letzten 2 Tagen ca 10% gemacht, wenn es wo weiter geht dauert es nicht mehr lange.

    Werte Community, ich würde gerne eine PD Rom erstellen um meine ASM Kentnisse zu erweitern... Leider fehlen mir die Ideen, ich dachte an sowas wie einen Roman oder etwas ähnliches!
    Falls jemand Ideen oder Material hat dass ich verwenden könnte, dann mal los :o)


    MfG Ronny

    So, jetzt mal Butter auf die "FISCHE" ;o)


    Status: Font und Grafiken dekomprimiert und wieder eingefügt, Texte gedumpt und fast fertig (schon länger), Probleme machen hier die ganzen Eigennamen... Neue, beschleunigte Routine um den Text zu lesen inklusive MTE-"Kompression" um möglicher weise die ROM nicht erweitern zu müssen...


    Werde die nächsten Tage weitermachen und Updates posten, sofern ich Zeit finde, hab noch was "etwas mehr wichtigeres" nebenbei *wink*


    Font:


    Code:

    So, mal bisschen was zum Status dieses Projekts... Ich habe mir endlich mal die Zeit genommen das Skript zu bearbeiten und in die ROM einzufügen, was mich dabei sehr verwundert hat ist, dass genau 102 Pointer beim Inserten fehlten, 2 davon waren "Flüchtigkeitsfehler" die beim Copy/Paste während des Übersetzens aufgetreten sind, die restlichen 100 Pointer wurden einfach ausgelassen und nicht in die Tabelle mit aufgenommen!


    <#Pointer 0523> --> <#Pointer 0624>, alles dazwischen fehlt.


    Ich möchte jetzt niemandem die Schuld in die Schuhe schieben, derjenige der das Skript hochgeladen hat möchte sich bitte bei mir melden via ICQ oder Skype, damit ich hier weitermachen kann.


    mfG Ronny

    Name: LZSS
    Typ: LZ77-Erweiterung
    Verlustfrei: Ja
    Beispiel: Clock Tower II - The Struggle Within (PSX)
    Link: Wikipedia



    Hier beschreibe ich an einem kleinen Beispiel wie LZSS funktioniert...
    Auf die Herkunft und wie es entstanden ist gehe ich nicht weiter ein, dafür lest bitte den Wikipedia Artikel :o)



    Ein Screen von den Daten die wir dekodieren möchten:


    Rauskommen soll dabei das hier:


    Unser erstem 4 Bytes sind nur ein Header ("EXS") gefolgt von einer Nummer, die wahrscheinlich den Typ der Kompression angibt, bisher habe ich aber nur die ID 1 gefunden.
    Die nächsten 4 Bytes geben die dekomprimierte Größe an, also 0x674D4 (423124 Bytes).
    Danach startet unser LZSS Stream bei Offset 0x8.


    Ein LZSS-"Frame" besteht aus 3 Komponenten:
    1.) das "Flag Byte"
    2.) Unkomprimierte Einträge
    3.) Komprimierte Einträge


    Das erste Byte (Flag Byte) wird immer dazu genutzt um anzuzeigen wie die nachfolgenden Daten abgespeichert wurden, unser erstes Byte hier ist 0xBB, dieses müssen wir in seine Bits zerlegen, was 10111011 binär ergibt, in diesem Fall müssen wir die Bits noch umdrehen, da sie von hinten nach vorne gelesen werden, also: 11011101.
    In unserem Beispiel wird die 1 für einen Unkomprimierten Eintrag verwendet, ein sogenanntes "Literal"-Byte (^Müllbyte^) und die 0 für Komprimierte Einträge.
    Wie schon gesagt, das Literal ist nur 1 Byte, ein Komprimierter Eintrag besteht hier immer aus 2 Byte.


    Zerlegen wir mal unsere Bits:
    1 - Literal - 1 Byte
    1 - Literal - 1 Byte
    0 - Komprimiert - 2 Byte
    1 - Literal - 1 Byte
    1 - Literal - 1 Byte
    1 - Literal - 1 Byte
    0 - Komprimiert - 2 Byte
    1 - Literal - 1 Byte


    Wir wissen nun, dass die ersten beiden Bytes nach dem Flag Byte unkomprimiert sind, also haben wir schonmal einen Output von: "05 00"


    Jetzt kommt ein Komprimierter Eintrag, den wir wiederum zerlegen müssen, 0x0200


    Komprimierte Einträge bestehen in unserem Fall aus 16 Bits, diese 16 Bits werden für Längen- und Positionsangaben verwendet, in unserem Beispiel 4 Bits für die Länge und 12 Bits für die Position, das macht 2^4 = 16 Bytes die mit diesen 4 Bits dekomprimiert werden können und 2^12 = 4096 Bytes für die Position wo sich diese Bytes befinden.


    Wie diese Bits angeordnet sind, kann je nach Kompression variieren (Jenachdem was sich der Entwickler einfallen lässt), es gibt auch Kombinationen von 11 Bits für die Position mit 5 Bits für die Länge, oder 13 Bits für die Position und 3 Bits für die Länge, und es gibt sogar Spiele die verwenden nur 1 Byte für Komprimierte Einträge, wie Grandia 2 (6 Bits für die Position und 2 Bits für die Länge)


    Wir drehen unseren Komprimierten EIntrag jetzt um, weil wir ja von rechts nach links lesen: 0x0002


    Zerlegen ihn in Bits: 0000000000000010


    Wir wissen das unsere Position 12 Bits hat und unser Länge 4 Bits:
    0000 000000000010
    LLLL PPPPPPPPPPPP


    Wenn wir das jetzt wieder in normale Zahlen umwandeln, erhalten wir 0x02 als Positionsangabe und 0x00 als Längenangabe,
    wir dekomprimieren also von Offset 0x02 0 Bytes....


    0 Bytes??? Da sgeht schlecht, wir zählen 2 dazu, so etwas wird meist gemacht, weil es Blödsinn ist zum Beispiel 1 Byte oder 2 Byte mit einem 16 Bit Eintrag zu kodieren (wobei letzteres doch hier gemacht wird),
    desweiteren müssen wir den Offset -1 rechnen (Keine Ahnung warum das hier so ist)
    Also: 0x01 Position, 0x02 Länge, Dekomprimiere 2 Bytes ab Offset 0x1


    Unser Derzeitiger Output ist "05 00",
    Jetzt nehmen wir ein Byte von Offset 0x1 und packen es hinten ran: "05 00 00",
    nehmen noch ein Byte von Offset 0x1 und packen es wieder hinten ran: "05 00 00 00".


    Die nächsten 3 Bytes sind Unkomprimiert, wir fügen sie einfach an den Output mit an und erhalten: "05 00 00 00 1D E6 20"


    Jetzt kommt wieder ein Komprimierter Eintrag: 0x0710, einmal drehen = 0x1007


    0001 000000000111
    LLLL PPPPPPPPPPPP


    1 Länge, 0x7 Position


    Länge = Länge + 2 = 3
    Position = Position - 1 = 0x6


    Wir kopieren 3 Bytes ab Position 0x6


    Einmal: "05 00 00 00 1D E6 20 20"
    Zweimal: "05 00 00 00 1D E6 20 20 20"
    Dreimal: "05 00 00 00 1D E6 20 20 20 20"


    Das nächste Byte ist wieder ein Literal und wird einfach an den Output angehängt, also: "05 00 00 00 1D E6 20 20 20 20 4D"


    Somit haben wir 11 Bytes insgesamt dekomprimiert, damit ist die Dekodierung dieses Frames abgeschlossen und der nächste Frame wird dekodiert, dieser beginnt diesmal mit 0xFF

    Und ich hab das BoF Tool weitergegegen und auch noch vorher paar stunden Arbeit reingesteckt damit es mit den spanischen Sonderzeichen kompatibel ist, hab im Gegenzug Tools für Dragon View bekommen und ne Menge andere, sehr hilfreiche Infos: Also ich kann mich nicht beklagen...

    Hier mal ein Statusupdate nachdem die R5 kläglich gescheitert ist: :o)


    Das Skript muss nochmal angepasst werden, also auf Zeilenlänge gefixt werden, ansonsten sollte es das mit dem Skript gewesen sein... Jetzt warte ich nur drauf dass es fertig wird und ich es inserten kann, die Bugs die aufgetreten sind, wurden mittlerweile alle von mir behoben , wie Grafikfehler, falsche Texte an bestimmten Positionen, etc!

    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 &#00FF = $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