Maggi-Fix für SNES Programmierung

Es werden stets neue Spieletests, Berichte und Artikel für die Homepage gesucht. Beweist hier Euer Schreibtalent.

Moderator: Perikles

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 21. Oktober 2012, 12:19

Redscorpion hat geschrieben:Ich komm kaum noch hinterher alles zu lesen was du schreibst ;) Du wahni! Aber weiter so.
Yeah! ;D
Dankeschön. Aber für dich müsste das doch kalter Kaffee sein, Mister Hack. ;D

Da fällt mir ein, ich schiebe es schon länger auf, dich um einen CIC-gemoddeten SNES zu bitten, auf dem ich mein US-Kirby's-Dreamland-3 spielen kann. Preis --> PM? :)
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 21. Oktober 2012, 13:00

Aldäh, wenn wir jez net programmiern..........!!1
*schwitz*
Bitte mach mich nicht Messer! °___°'

Gut. Du hast deine Blanko.asm noch auf? Fein.
Fangen wir an.

Wir werden jetzt den Bildschirm in einer Farbe anmalen.
Danach bilden wir einen Stuhlkreis und reden darüber.
Nein, im Ernst, wir werden danach (für mehr) noch einmal einen ganzen Batzen Theorie wälzen, und anschließend noch einmal darauf zurückkommen, wie das hier funktioniert; für jetze reicht es zu wissen, dass man so den kompletten Bildschirm* einfärben kann.

Damit das alles schön übersichtlich bleibt, schreibe ich erstmal in einem Rutsch den Code. Den fügen wir zwischen dem "InitSNES" und dem "forever:" ein:

Code: Alles auswählen

STZ CGADD
LDA #%10111111
STA CGDATA
LDA #%01100110
STA CGDATA
LDA #$0F
STA INIDSP
Fertig.
Aber ich seh schon, du das tausend Fragen.
Zuerst: Da steht ja gar nix von REP und SEP, warum erklärst du dann den Schmarrn so lang und breit?
Antwort: Ich dachte, dass wir es hier schon bräuchten. Tun wir aber nicht. Aber das ist trotzdem eine Sache, die du wissen solltest, bevor du irgendwas machst. Und überhaupt: Du liest das hier, um was zu lernen, also beschwer dich nicht, wenn du was lernst. :P
Aber, wenn es dich interessiert: Wir verlassen die InitSNES mit REP #$10, SEP #$20, also: A = 8 Bit, X/Y = 16 Bit.

Zuzweit: Was ist den STZ?
"Z" ist kein geheimes, viertes Register. "STZ" heißt "Store Zero", und es erspart einem das Laden eines leeren Bytes. Das ist etwas, was dir viel Stress ersparen wird, wenn du es dir jetzt schon merken kannst: STZ speichert immer nur einen leeren Byte, keinen Doppelbyte, auch egal, wenn alle drei Register 16-Bit-Größe haben.
Also, summa summarum: Für STZ $xxxx könntest du auch schreiben LDA #$00 STA $xxxx.

So. Und den Rest erkläre ich jetzt Zeile für Zeile. Vielleicht willst du ja erstmal kompilieren und gucken, ob es funktioniert? Wenn alles funktioniert, wirst du mit der männlichsten aller Farben belohnt. ^.^

Zunächst möchte ich einen neuen Mitschüler in unserer Klasse vorstellen: CGRAM.
CGRAM heißt mit vollständigem Namen Character Generation Random-Access Memory, aber du darfst ruhig CGRAM sagen.
Der CGRAM ist der Speicherplatz für die Farben. Vielleicht kannst du dich erinnern, dass es hieß, das SNES kann normalerweise nur 256 Farben gleichzeitig darstellen? Heute erklär ich euch, warum.
Eine Farbe ist zwei Byte groß. Also 16 Bit. Wenn du oben den Sourcecode anguckst, siehst du, dass ich zwei Bytes in CGDATA schreibe. Das sind die beiden Farb-Bytes. Der CGRAM ist 512 Bytes groß. 512 Bytes geteilt durch 2 Bytes pro Farbe ergibt 256 Farben. Und schon hast du verstanden, warum das SNES normalerweise nur 256 Farben anzeigen kann! *tröt tröt* Herzlichen Glückwunsch! Du kannst dir zur Feier des Tages am Ausgang deinen persönlichen Überraschungskuchen™ abholen!**

Dementsprechend kannst du dir aber auch zusammenrechnen, dass der CGRAM für nix anderes als diese 256 Farben auch nur einen Bit Platz hat. Das bietet den Vorteil, dass man damit auch schon diesen Baustein "komplett verstanden" abhaken kann.
Im CGRAM ist eine 512-Bit-lange Kette aus Nullen und Einsen, und das SNES weiß selbst, dass die ersten sechzehn Bit die erste Farbe, d. h. Farbe numero 0, enthalten, die nächsten 16 Bit die nächste Farbe bilden usw.

Zum vereinfachten Verständnis sage ich dir jetzt, dass jeder Pixel auf dem Bildschirm angewiesen ist, Farbe 0 zu zeigen.
Warum ist das so?
Ganz einfach erklärt: Weil nichts gegenteiliges gesagt wurde.
Gehts ein bisschen genauer?
Ein bisschen genauer erklärt: Weil die InitSNES es am Anfang so einstellt und nichts gegenteiliges gesagt wurde.
Gehts vielleicht noch ein bisschen genauer?
Ja, geht.

(Später, Kindchen, später, später...)

Jedenfalls, jeder Pixel soll Farbe 0 zeigen. Deshalb reicht es zur Zeit(!!!), die Farbe 0 zu ändern, um den gesamten Bildschirm umzufärben (glaub man ja nich, dass das immer so einfach funktionieren wird, du! ;)).

Jetzt wissen wir das.
Wir wissen, wer CGRAM ist und keinen seine Anatomie.
Jetzt lernen wir, mit ihm zu sprechen.

Der CGRAM hat 256 Farb-Speicherplätze. Allerdings spricht man diese Speicherplätze nicht direkt an, sondern nur mit zwei Registern: $2121 (CGADD, "CGRAM Address") und $2122 (CGDATA, "CGRAM Data").

Mit CGADD steuerst du einen bestimmten Speicherplatz an, mit CGDATA schreibst du in den gerade angesteuerten Speicherplatz.
Vergleichen könnte man das mit einer Liste, die du nicht siehst, und einer Eingabemaske in Form eines kleinen Fensters mit zwei Textfeldchen und einem Button mit der Aufschrift "Schreib!". In das erste Feld trägst du ein, welchen Punkt auf der Liste du ändern willst, ins zweite Feld trägst du ein, durch was du den Listenpunkt ersetzen willst.

Warum ist dieses System sinnvoll? Warum kann man nicht einfach die Farb-"Slots" direkt ansteuern?
Antwort: 's platzsparender, so.
Beispiel:
Das System, wie's jetzt ist, ist so, wie wenn du als (Daten-)Paketbote vor einem Haus mit Fahrstuhl stehst, bei dem du ein (Daten)paket. Du gehst in den Fahrstuhl, drückst den Knopf für die entsprechende Etagenwohnung (STA CGADD) und schmeißt dann durch die Fahrstuhltür dein Paket raus (STA CGDATA).
Wäre jeder Farbspeicherplatz einzeln ansprechbar, wäre es so, als wenn es zu jeder Etagenwohnung eine eigene Treppe gäbe. Dann müsste das Grundstück aber ziemlich groß sein!
Kleiner Hinweis, der mir erst nachträglich aufgefallen ist:<br />Dass der Paketbote im Aufzug pfeifen und Arme hinterm Kopf verschränken kann, während die Treppengänger schwitzen oder sterben, ist nur stilistisches Mittel. Es würden keine Datenpakete verloren gehen oder länger brauchen, würde man die Speicherplätze einzeln ansprechen können. Dieser Eindruck sollte nicht entstehen.
Kleiner Hinweis, der mir erst nachträglich aufgefallen ist:
Dass der Paketbote im Aufzug pfeifen und Arme hinterm Kopf verschränken kann, während die Treppengänger schwitzen oder sterben, ist nur stilistisches Mittel. Es würden keine Datenpakete verloren gehen oder länger brauchen, würde man die Speicherplätze einzeln ansprechen können. Dieser Eindruck sollte nicht entstehen.
snes4.jpg (37.71 KiB) 22827 mal betrachtet
Jenseits des Bildes in der Realität ist es auf andere Art und Weise platzsparender, nämlich: Es spart Adressen. Wie gesagt, "Memory-Mapped Input/Output", "als Speicher kartographierte Eingabe/Ausgabe". Heißt: Je mehr Adressen man für Input und Output hergeben muss, desto weniger Adressen sind für tatsächlichen Datenspeicher zur Verfügung. Und so kriegt man auf diese Art und Weise 256 Sachen, die Adressen haben sollten, mit zwei Adressen angesprochen.

Ach ja, was du auch noch dir merken solltest:
Wenn du in CGDATA schreibst, wird der Wert in CGADD automatisch inkrementiert, das heißt um eins erhöht. Wenn du ein Datenpaket abgibst, fährt der Aufzug automatisch in die nächsthöhere Etage. Bietet den Vorteil, dass du, wenn du für das ganze Haus Pakete hast, nicht jedes Mal den Fahrstuhl bedienen mit CGADD musst, sondern immer nur der Reihe nach Pakete durch die CGDATA-Fahrstuhltür rausgibst.

Gut, kommen wir damit zurück zum Code.
Du speicherst null in die CGADD und schreibst dann den Zahlenwert in zwei einzelnen Byte in CGDATA.
Kann man nicht einfach den Akkumulator 16 Bit groß machen und das in einem Arbeitsschritt dort hineinspeichern?
Nein, leider nicht. Das hatte ich leider vergessen, weshalb ich auch dachte, wir könnten jetzt schon SEP und REP gebrauchen.
Stell dir CGDATA jetzt wie einen Pappkarton vor, dessen Grundfläche 8 Bit groß ist und der 2 Bit hoch ist. Du kannst 16 Bit darin verstauen, allerdings nur, wenn du zweimal hintereinander 8 Bit hineinschreibst. Wenn du 16 Bit da reinpackst, passt es nicht, es steht was über.

Soweit verbildlicht. In der harten Elektrotechnik-Realität sieht es so aus:
LDA #$XYZW
STA $2122
Diese beiden Befehle bewirken, dass zwei Byte (die hier mir Platzhaltern ersetzt wurden, die ansich keinen Sinn ergeben) in $2122 (CGDATA) abgespeichert werden und die anderen zwei Byte im Register daneben, nämlich ion $2123, und nicht, wie wir es gern hätten, dass es nacheinander in $2122 geschrieben wird.
Übrigens, was du dir schonmal merken kannst, worauf ich aber auch nochmal ausführlicher hinweisen sollte: Es werden #$ZW in $2122 und #$XY in $2123 gespeichert. Bei einem Doppelbyte spricht man bei soetwas vom "High Byte" und dem "Low Byte". Da es sich um Zahlen handelt, ist der Vergleich mit den "acht hohen Stellen" und den "acht niedrigen Stellen" einer Binärzahl naheliegend (bzw. den zwei hohen und niedrigen Stellen einer vierstelligen Hexadezimalzahl). Jedenfalls, wenn du einen Doppelbyte ausliest oder abspeicherst, wird immer zuerst die geladene Zahl als Low Byte aufgefasst bzw. zuerst der Low Byte abgespeichert. Das ist zuerst verwirrend, aber in sich konstant durchgesetzt; ich kann einen Doppelbyte-Wert irgendwo abspeichern und wieder laden, und die beiden Byte sind in derselben Reihenfolge wie zuvor. Keine Panik.

Okay, kommen wir zum letzten Teil des CGRAMs. Wir kennen seinen Namen, seine Anatomie, seine Sprache. Das letzte, was uns noch fehlt, ist sein Speiseplan.

Genauer:
Wie müssen die Farben aufgebaut sein?

Also.
Du kennst bestimmt dieses Fenster, nicht wahr?
Ich proll euch alle wech mit meine HiEnd-Grafikprogramme! WORD!
Ich proll euch alle wech mit meine HiEnd-Grafikprogramme! WORD!
PaintRBG.jpg (60.84 KiB) 22824 mal betrachtet
Rechts unten siehst du die RGB-"Regler" (RGB = Rot/Grün/Blau), und den Umgang damit kennst du ja auch. Je größer der Grün, desto grüner wird der Farbton. Der Spielraum liegt zwischen 0 und 255 bei allen drei Farbbestandteilen.

Die SNES-Farben haben auch diese Regler, und bei ihnen liegen die Zahlen zwischen 0 und 31. In Binärzahlen ausgedrückt: Bei aktuellen Computern liegen die Farbregler zwischen %0000.0000 und %1111.1111 (=255 bzw. $FF), beim SNES zwischen %0.0000 und %1.1111 (=31 bzw. $1F).
Das bedeutet wiederum, wenn du dir die Farben in SNES-Screenshots anguckst, wirst du auch sehen, dass die RGB-Werte alle in 8er-Schritten liegen.
Um sich das verständlich zu machen, ist es ganz nützlich, dass ich eben gerade den Kram in Bits umgerechnet habe, fällt mir auf.
SNES-Farbe %x.xxxx = Farbe %xxxx.x000 auf dem Computer.
Ein Rotwert von %0.0001 auf dem SNES ergibt auf dem Computer einen Rotwert von 8 (weil 8 = %1000).
Das strahlenste Weiß auf dem SNES hat bei allen drei Farben einen Wert von %1.1111, das ergibt auf dem Computer in allen drei Farbkanälen einen Farbwert von 248, weil 248 in Binär ist: %1111.1000.
Verrückt, was?

Mit diesem Wissen ist es ganz einfach, einen Farbwert zusammenzupanschen.

Der Doppelbytewert setzt sich so zusammen:
0bbbbbgggggrrrrr
Anders aufbereitet:
Low Byte: gggrrrrr
High Byte: 0bbbbbgg

(Wie dir auffällt, steht der Rotwert ganz rechts, was natürlich auch wieder verwirrt, weil es bei dem Begriff "RGB" offensichtlicherweise ganz links steht. Aber erklärt sich dadurch, dass, ne?, die Zahlen sich von rechts nach links "aufbauen", und somit der "erste" Wert ganz unten steht. Vielleicht hilft diese Eselsbrücke zum besseren Verständnis.)

Die 0 wurde in einem Tutorial als "Infamous Bit of Confusion" (zu deutsch: der "berüchtigte Bit der Verwirrung") bezeichnet, was ich immer noch so ulkig finde, das ich nicht aufhören kann, das anzumerken. ;)
Als Anweisung dazu wurde gesagt: "Just leave it 0." Lass es einfach auf null.

Okay. Soweit verständlich?
Denn, damit haben wir es jetzt. Zur Festigung des neuen Wissens können wir ja mal uns die Werte von oben ansehen, die ich in den Beispielcode eingesetzt habe:

LDA #%10111111
STA CGDATA
LDA #%01100110
STA CGDATA

Der erste Byte ist der Low Byte, das heißt, die beiden Bytes in ihrer "richtigen", zusammengesetzen Form ergeben #%0110011010111111.
Das ergibt:
R: %11111, G: %10101, B: %11001.
Das ergibt:
R: 31, G: 21, B: 25
Mal acht ergibt das:
R: 248, G: 168, B: 200

... und wenn du jetzt auf meinen Paint-Screenshot guckst, wird dir eine erstaunliche Ähnlichkeit auffallen...

Okay, bleibt eigentlich nur noch das letzte Viertel des Codes, nämlich "LDA #$0F" und "STA INIDSP".
*pausenklingel*
Halt, halt, alle sitzen bleiben! Wann hier Pause ist, bestimme immer noch ich!
Das geht aber schnell:
"INIDSP" heißt so viel wie "Initialize Display", also "Bildschirmvoreinstellung".
Bei dem Register INIDSP ($2100) handelt es sich um ein bytegroßes, nur beschreibbares Register. Mit den unteren vier Bits reguliert man die Helligkeit des auf dem Bildschirm angezeigten Bilds (der alleralleroberste Bit macht übrigens den Forced BLANK, nur so am Rande). Da wir also $0F, d. h. %0000.1111, in INIDSP speichern, drehen wir die Bildschirmhelligkeit voll auf, was der Normalzustand ist.

Übrigens sollte man das immer als letztes machen. Ich musste gestern abend leider feststellen, dass, wenn man das zuallererst macht und danach noch an einigen Nachbarregistern, die für andere Grafikeinstellungen zuständig sind, herumregelt, dass das SNES dann das Bild wieder ausmacht (was in ZSNES nicht passiert, wodurch ZSNES Dinge abspielt, die auf echter Hardware nicht laufen, wodurch ZSNES sich als schlechter Emulator herausstellt, wodurch man ZSNES auch nicht als Emulator nutzen sollte und das Ding geächtet werden sollte, wodurch sich hier noch einmal die Grundlage bietet, darauf hinzuweisen, dass man das Ding nicht benutzen sollte, auch wenn ich es benutze).

Also: Immer als letztes den Bildschirm anmachen!

In der nächsten Programmierstunde bringen wir dann Tiles auf den Bildschirm, und als Hausaufgabe lest bis dahin die nächsten zehntausend Seiten Grafik-Theorie über den Aufbau und die Gliederung der Grafiken des SNES. Viel Spaß!

*Das ist damit quasi dasselbe wie bazz' First Program. Kannst ja sein Tutorial lesen, wenn du meins nicht verstehst. ;)
** Der Kuchen ist eine Lüge.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 21. Oktober 2012, 22:56

Prinzessin Layer und die 8 Modes

Wir sollten zunächst zum ganz theoretischen Teil kommen, bevor wir zum Hardware-Teil kommen, bevor wir zum Programmierteil kommen.

Mode 7 schon mal gehört?
Ja?
F-Zero?
Flugsequenzen bei Terranigma?
Mario Kart, also, das erste?
Und woher hat der Mode 7 seinen Namen?
Es ist der achte von insgesamt acht Grafikmodi (Mode 0-7). Die anderen sind mindestens genauso häufig genutzt worden, sind aber nicht so spektakulär. Kleiner Gedankensprung.

Das Bild bei SNES-Spielen wird in mehreren Schichten (engl. layer) aufgebaut.
Bei diesen Schichten unterscheidet man zwischen Backgrounds (kurz BGs) und Sprites.
Backgrounds sind in einem festen Raster angeordnet. Dieses feste Raster sind dir sicherlich schon einmal aufgefallen. Wenn du nicht weißt, was ich meine, schau hier:
raster.png
raster.png (8.96 KiB) 22819 mal betrachtet
Wie du siehst sind die Blöcke alle in senkrechten Spalten und waagerechten Reihen angeordnet. Mario selbst ist da wesentlich freier in seinen Bewegungsmöglichkeiten. Das liegt daran, dass die Mario-Grafik ein Sprite ist. Sprites sind kleine, bewegliche Objekte. Diese haben kein Raster und sind deshalb auch in der Lage, sich wesentlich freier zu bewegen.
Die Sprites liegen zusätzlich auf einer eigenen Ebene alle diese Schichten werden am Ende übereinander gelegt und ergeben das Gesamtbild, ähnlich wie die Folien bei alten Disneyfilmen oder Animes, die man auch Cels nennt.
schichten.jpg
schichten.jpg (75.56 KiB) 22819 mal betrachtet
Das ist oben nur ein vereinfachendes Beispiel, ich habe absichtlich den Backgrounds keine Nummern gegeben. Die Menge der Backgrounds hängt ab vom bereits erwähnten Modus, desweiteren gibt es auch die Möglichkeit einzustellen, dass bestimmte Teile eines Backgrounds höher liegen als andere.

Hier eine Liste der BGs und deren jeweilige Menge an Farben, die man auf ihnen darstellen kann:
geklaut von http://wiki.superfamicom.org/snes/show/Backgrounds
geklaut von http://wiki.superfamicom.org/snes/show/Backgrounds
BGs.png (3.14 KiB) 22809 mal betrachtet
Wir tun einfach mal so, als würde da unter Mode 7 nichts stehen und als würdest du daraus resultierende Fragen gar nicht stellen wollen.

Als nächstes gibt es eine hilfreiche Tabelle mit einer Zusammenstellung der Reihenfolge des Spritelayers und der einzelnen BG-Layer:
geklaut von und zusammengestückelt aus http://wiki.superfamicom.org/snes/show/Backgrounds
geklaut von und zusammengestückelt aus http://wiki.superfamicom.org/snes/show/Backgrounds
BGs 2.png (30.55 KiB) 22809 mal betrachtet
Zunächst, warum der Kasten bei Mode 1 so breit ist: Mode 1 bietet eine Besonderheit, dass man auf ganz eigene Art und Weise den BG3 nach ganz vorne schieben kann. Das klappt nur mit BG3 und nur im Mode 1; wozu das gut ist, erklär ich ein andern Mal.

Dort siehst du sehr viel mit "Priority". Das ist, was ich vorhin meinte, als ich sagte, man könne einzelnen Teilen der BGs sagen, dass sie höher liegen sollen als andere. Das bewerkstelligt man mit der Priorität; die Priorität kann für jede einzelne "Kachel" der BGs festgelegt werden. Hier mal ein Beispielbild dazu, dabei sei gesagt, dass ganz unten gezeigt wird, wie die "Prioritäten" liegen:
priority bits.jpg
priority bits.jpg (67.95 KiB) 22809 mal betrachtet
Abschließend sei erklärt... die BGs haben eine bestimmte Größe. Sie können 32x32, 32x64, 64x32 oder 64x64 dieser "Kacheln" (englisch: Tiles) enthalten. Das ist größer als das Bild, das auf einem Fernseher dargestellt werden kann.
Der Vorteil bei den BGs ist -und hier zieht wieder der Vergleich mit den Animationsfilm-Folien-, dass jeder einzelne verschiebbar ist. Zum Beispiel kann man bei Super Mario sehen, dass sich die direkte Umgebung wesentlich schneller scrollt als die Berge in der Ferne. Die Berge in der Ferne liegen auf einem Layer und die Landschaft vorne auf einem anderen. Und der Effekt der Bildtiefe wird durch ein unterschiedlich schnelles Verschieben dieser Hintergründe erreicht.
Fernsehbildundlayer.jpg
Fernsehbildundlayer.jpg (114.97 KiB) 22809 mal betrachtet
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 23. Oktober 2012, 09:11

@Moi: Die nächste Kategorie wäre vielleicht "Utilities und ihre Anwendung"



Bilder SNES-fertig umwandeln

(In diesem Tutorial geht es um Tilesets, was Sprites anbelangt, sollte man nochmal darauf hinweisen, wie die Ausgangsdatei aufgebaut sein sollte; ich habe da nicht allzu viele positive Erfahrungen damit gesammelt, als ich versucht habe, eigene Sprites auf den Screen zu bringen)

Zunächst sollte man ein Tileset in einem Grafikprogramm seiner Wahl erstellen.
Das Grafikprogramm meiner Wahl ist nicht teuer und weitverbreitet, sei es auch mit wenigen chicen Features ausstaffiert.
1.png
1.png (45.32 KiB) 22799 mal betrachtet
Die Tiles müssen 8x8 oder 16x16 Pixel groß sein, und du kannst dir/deinem Programmierer viele Schmerzen ersparen, wenn du den ersten Tile komplett mit der Farbe füllst, die du als Transparenzfarbe willst. Wenn du keine Transparenzfarbe willst, dann nimm eine Farbe, die du in deinem Tileset nicht verwenden willst.
Übrigens ist es meine Angewohnheit, meine Tiles alle in einer Säule (oder Wurst, wenn du so willst) untereinander anzuordnen, weil ich weiß, dass das PCX2SNES das checkt. Bestimmt tut es das auch, wenn man das Tileset in mehreren Spalten anlegt, allerdings war ich bisher zu faul, es zu testen.

Zunächst speichert man das Tileset im BMP-Format und in irgendeinem anderen verlustfreien Format*. Als nächstes speichert man das Bild als PCX-Datei und reduziert die Farben. Das ist etwas, was Paint leider auch mit Windows 7 noch nicht gelernt hat, deswegen benutze ich persönlich ihmchen, hier:
2.png
2.png (19.73 KiB) 22799 mal betrachtet
GIMP.
Ein Freeware-Programm mit tausend super Features, wie man es von Sachen kennt, die ursprünglich für Linux gemacht sind.
GIMP kannst du dir hier runterladen; falls du wissen willst, welche Version ich benutze, weil zwischenzeitlich wieder alle Menüs in GIMP umgestaltet wurden: Zur Zeit, da ich dies schreibe, benutze ich Version 2.6.12.

Installiere GIMP, öffne GIMP und öffne deine Bitmap mit dem Tileset.
Dann gehst du über das Menü "Bild" --> "Modus" --> "Indiziert".
3.png
3.png (419.81 KiB) 22799 mal betrachtet
In dem nun sich öffnenden Fenster musst du nur bei "Optimale Palette erzeugen" die Menge der Farben eintragen, die dir dein Programmierer/der jeweilige BG korrespondierend zum Mode zugesteht.
4.png
4.png (45.48 KiB) 22799 mal betrachtet
Danach gehst du auf "Speichern unter" und ersetzt oben im Dateinamen die Endung ".bmp" durch ".pcx". Das Programm ist so zuvorkommend und erkennt automatisch, dass es eine PCX-Datei werden soll und die Datei dementsprechend anders speichern muss.**

Jetzt benutzen wir PCX2SNES.
Ich benutze die DOS-Version, weil die Windowsversion bei mir nicht läuft. Außerdem: Das Benutzen von Kommandozeilenprogrammen stärkt die Abwehrkräfte.
Das Programm gibt es bei Romhacking.net, aber ich archivier es mal auch gleich hier:
Pcx2Snes.zip
(31.67 KiB) 1002-mal heruntergeladen
Lade es dir runter, entpack es irgendwo, schmeiß deine PCX-Datei in denselben Ordner, öffne eine Eingabeaufforderung und bewege dich in ihr in diesen Ordner.

Jetzt gibst du ein:

Code: Alles auswählen

Pcx2Snes.exe -n -s16 -c16 Tileset
(Alle Befehle werden dir auch angezeigt, wenn du schlicht nur "Pcx2Snes.exe" eingibst)

Zur Erklärung:
"-n" sagt, dass du um deine Tiles nicht nicht noch einen Rand von einem Pixel zu jeder Seite gemalt hast. Warum solltest du soetwas auch tun?
"-s16" sagt, dass deine Tiles 16x16 Pixel groß sind.
"-c16" sagt, dass deine Tiles 16 Farben haben.
"Tileset" sagt, dass er eine Datei namens Tileset.pcx benutzen soll. Hättest du "Tileset.pcx" eingegeben, hätte er sich beschwert, dass er die Datei Tileset.pcx.pcx nicht finden kann.
Übrigens hat auch die DOS-Box eine Autovervollständigung. Das heißt, wenn du einen eigenen Ordner für PCX2SNES angelegt hast, reicht es, wenn du "p" drückst und die Tab-Taste haust, dass er dir "Pcx2Snes.exe" bereits ausschreibt.

Wie auch immer.
Als Output erhälst du "Tileset.pic" und "Tileset.clr", oder wie auch immer du deine PCX genannt hattest.
Die CLR-Datei enthält die Farben, die PIC-Datei sind die Bitplanes, also die "Malen nach Zahlen-Leinwand".

KNOW HOW
Bei großen Tilesets für Landschaften, die du für dein RPG, dass FF6 in den Schatten stellt™, brauchst, solltest du dir bewusst sein, dass die Limitierung auf 16 Farben nur pro Tile ist. Meine Empfehlung ist, dass du auf folgende Weise vorgehst:
- Du machst z. B. zwei Tilesetdateien (ich nenne es mal "Mini-Tilesets"), eine mit den Blümchen und Rasen (Grüntöne, starke Farben für die Blumen), eine mit den Kopfsteinpflasterwegen und den Häuserfassaden (Grau und gedeckte Farben)
- Beide jagst du auf oben beschriebene Weise durch GIMP/PCX2SNES.
- Du nimmst dir einen Hexeditor und klatschst die eine Datei hinter die andere.
- Beim Schreiben der Tilemap nimmst du für die "hinteren" Tiles immer die zweite Palette. Was ist die zweite Palette? Das, was du in die CLR-Datei hinten hinein geschmiert hast.

Wichtig ist, darauf hinzuweisen: Du kannst nicht Paletten mischen. Wenn du ein Tileset mit 20 Farben hast, musst du es splitten und wirst zwei Paletten mit je 16 Farben haben, wobei je 13 Farben dieselben sein werden (gesetzt den Fall, dass du einen BG mit 16-Farben-Tiefe hast, was ich hiermal als Normalfall hinstellen möchte). Auch insofern ist es eine Herausforderung, das große Tileset möglichst geschickt in "Mini-Tilesets" aufzuteilen! ;)

Als nächstes wäre noch darauf hinzuweisen: Transparenz funktioniert immer nur und grundsätzlich zu jedem Zeitpunkt mit Farbe 0 im CGRAM. Soll heißen: Alle Tiles, die Transparenz haben müssen, müssen mit Palette 0 gefüllt werden. Das bedeutet natürlich, dass du sehr gut jonglieren können musst; vielleicht solltest du überlegen, alle Transparenten Teile noch einmal einzeln auf eins dieser "Mini-Tilesets" aufzuteilen, dass sie noch einmal ihre eigene Palette kriegen...?

* mit verlustfrei meine ich nicht JPEG wegen der JPEG-Artefakte und auch nicht GIF wegen der beschränkten Palette. Ich glaube, PNG hat auch eine beschränkte Palette, weiß es aber nicht.
** Ich weiß, ich weiß: Als Windowsnutzer kann ich selbst bestätigen, dass der erste Reflex Misstrauen ist. "Hat er das jetzt wirklich so gemacht, wie ich wollte?"
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 28. Oktober 2012, 11:53

Tutorial-unrelated Filedump
versteckter Inhalt:
Waren ursprünglich zwei Dateien, wobei die erste am unteren Teil corrupted ist, wie man sieht.
Da es eh nur eine erste Fassung ist, die wharscheinlcih noch einmal von Grund auf neu gemacht werden muss, lohnt sich der Aufwand nicht.
sdd1.jpg
sdd1.jpg (405.42 KiB) 22773 mal betrachtet
Es fehlen: Beim Bit Demander:
(According to MPS it turns the value of the) bit around or not.

Beim Context Status Updater:
(...the MPS and LPS and the) context status, which might might lead to a different Golomb code level.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

maximhq
SNES-Newbie
SNES-Newbie
Beiträge: 1
Registriert: 6. November 2012, 20:21

Re: Maggi-Fix für SNES Programmierung

Beitrag von maximhq » 8. November 2012, 16:57

Danke für deine Mühe. Hab mir bis jetzt alles durchgelesen, hoffe es kommt bald noch mehr auch wenn ich sicherlich nicht alles verstehe^^. Ich denke aber, dass muss ich auch nicht, denn ich habe nicht wirklich vor ein Spiel am Ende programmieren zu wollen, da ich schon aus eigenen Erfahrungen weiß, wie schwer sowas sein kann.

Naja, hab mal aus Interesse versucht meine eigene Farbe hinzubekommen. Es hat zwar nicht viel mit der Programmierung an sich zu tun, aber ich war schon etwas Stolz auf mich xD. Weiter so!

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 11. November 2012, 08:34

maximhq hat geschrieben:Danke für deine Mühe. Hab mir bis jetzt alles durchgelesen, hoffe es kommt bald noch mehr auch wenn ich sicherlich nicht alles verstehe^^. Ich denke aber, dass muss ich auch nicht, denn ich habe nicht wirklich vor ein Spiel am Ende programmieren zu wollen, da ich schon aus eigenen Erfahrungen weiß, wie schwer sowas sein kann.

Naja, hab mal aus Interesse versucht meine eigene Farbe hinzubekommen. Es hat zwar nicht viel mit der Programmierung an sich zu tun, aber ich war schon etwas Stolz auf mich xD. Weiter so!
Cool, welche Farbe hast du genommen? ;)
Danke für die ermunternden Worte, es ist schön, Feedback zu bekommen, dass es wirklich jemand mal ausprobiert hat.

Danke übrigens, dass du dich scheinbar hier registriert hast, um das zu sagen. :)
Ich fühle mich geschmeichelt. :)







Tiles und der VRAM

Die Grafiken für die BGs, also die "Raster-Kachel-Folien", bestehen aus drei Teilen:
- Die Farben.
- Die Tiles als Tabelle, die statt der Farbe eines Pixels die Nummer der Farbe enthalten (= "Malen nach Zahlen")
- Eine Tabelle, die besagt, wie die Tiles im BG angeordnet werden sollen.
Als Beispiel: Badezimmerfliesen. Man stellt der Fliesenmanufaktur die Farben und sagt ihr, wie die Fliesen bemalt sein sollen, und anschließend sagt man dem Fliesenleger, welche der kunstvoll gefertigten Fliesen wie gedreht wohin ins neue Badezimmer kommen sollen.
Soweit vereinfacht. Jetzt zur Realität.
Wie mit den Farben verfahren wird, haben wir ja bereits im vorherigen Teil besprochen, als es um die Bildschirmfarbe ging. Exakt genauso wird auch bei der Umwandlung von Bildern in das Format, das für das SNES verständlich ist, verfahren: Alle Farben einer Grafik werden in einen Doppelbyte umgerechnet und hintereinander geschrieben, und entsprechend dieser Reihenfolge werden die Nummern in der "Malen-Nach-Zahlen"-Leinwand vergeben. Diese ist, wie man sich vorstellen kann, keine wirkliche Leinwand, es ist noch nicht einmal eine Tabelle, wie ich zuvor behauptet habe. Es handelt sich hierbei um Bitplanes.

Das muss man sich so vorstellen:
Eine 16farbige Grafik benötigt vier Bits pro Pixel, um festzusetzen, welche Farbe der Pixel hat; vier Bits ermöglichen Kombinationen zwischen 0b0000 und 0b1111, das heißt zwischen 0 und 15, also insgesamt 16 Kombinationen. Jetzt wird aber nicht eine Tabelle mit vier Bits pro Farbe angelegt, sondern vier Tabellen mit einem Bit pro Farbe, sogenannte Bitplanes.
Ein einfaches Beispiel: Es wird Farbe 13 benutzt.
13 = 0b1101.
Das bedeutet (von unten nach oben, d. h. von rechts nach links): Bitplane 0: 1, Bitplane 1: 0, Bitplane 2: 1, Bitplane 3: 1
Wenn das SNES nun Grafiken erstellt, nimmt es die Bitplanes, multipliziert ihre Zahlenwerte gemäß ihrer Reihenfolge mit 1, 2, 4, 8, 16 usw. und addiert sie. Bei unserer Beispielfarbe heißt das:
1*1 + 0*2 + 1*4 + 1*8 = 1+0+4+8 = 13
bitplanes1.jpg
bitplanes1.jpg (44.36 KiB) 22718 mal betrachtet
bitplanes2.jpg
bitplanes2.jpg (136.37 KiB) 22718 mal betrachtet
Diese Daten werden in den VRAM transferiert, den (Arbeits)Speicher für Videodaten. Die Tilemaps kommen übrigens ebenfalls in den VRAM.
tilesets.png
tilesets.png (104.26 KiB) 22718 mal betrachtet
Mit diesen drei "Bestandteilen", den Tilesets, geteilt in Paletten (1) und Bitplanes (2), und den Tilemaps (3), setzt das SNES anschließend die Grafiken wieder zusammen:
bild.png
bild.png (37.97 KiB) 22718 mal betrachtet
Die Tilemaps habe ich beschrieben als "Tabellen", in denen steht, welcher Tile wie wo erscheinen soll. In der Wirklichkeit kann man nicht wirklich von Tabellen sprechen. Die Tilemaps bestehen aus einer langen Reihe von Bytes, wobei je zwei Byte einen Tile beschreiben. Die ersten beiden Bytes beschreiben das erste Tile in der ersten Reihe, und das SNES weiß von selbst, dass es die Daten für das 33. Tile als das erste Tile der zweiten Reihe interpretieren muss.

Der Aufbau dieser zwei Bytes ist wie folgt:
wiki.superfamicom.org hat geschrieben:vhopppcc cccccccc
v/h = Vertical/Horizontal flip this tile.
o = Tile priority.
ppp = Tile palette. The number of entries in the palette depends on the Mode and the BG.
cccccccccc = Tile number.
Noch einmal auf deutsch:
Der High Byte (also der linke) enthält, von links nach rechts betrachtet:
Je einen Bit für senkrechte und waagerechte Spiegelung eines Tiles; wird er gesetzt, wird gespiegelt, wobei es da irgendwelche doofen Ausnahmen gibt, speziell bei 16x16 Pixel Tiles.
Ein Bit für die Priorität (die Priorität kennen wir ja bereits).
Drei Bit für die Palette (später mehr).
Zehn Bit für die Nummer des Tiles.

Zum Thema "Palette":
Drei Bit geben die Möglichkeit, acht verschiedene Paletten anzusprechen.
Wie viel Farben im CGRAM angesprochen werden, wird an anderer Stelle eingestellt. Je nachdem, welcher Modus (also: Mode 0 bis Mode7) für den Bildschirm eingestellt wird, haben die BGs unterschiedliche Farbtiefen, und dementsprechend weiß dann das SNES auch, wo es sich Palette 5 oder 8 zu suchen hat.
Und nu, obacht: Es kann sich ja jeder nachrechnen: Wenn 256 Farben im CGRAM sind, und ich einen BG mit 4 Farben habe, und 8 Paletten ansprechen kann... dann kann ich ja gar nicht alle 256 Farben ansprechen! Korrekt. Dementsprechend muss man jonglieren. Bei 4 Farben ist Palette 7 (also, die achte, da die erste ja Palette 0 ist) die Farbe 28 bis 31, der Rest ist nicht ansprechbar, *geht nich*.
Übrigens, wo wir gerade so schön im Tritt sind, könnte man ja meinen, dass dementsprechend es ja sein könnte, dass sich alles um die niedrigen Palettenplätze kloppt, während die Plätze mit den hohen Nummern quasi unbrauchbar sind. Vermutlich aus diesem Grund fängt das SNES an, die Paletten für die Sprites ab Farbe Nr. 128 zu suchen (zu deutsch: Sprites haben immer 16 Farben, und Palette 0 für einen Sprite ist Farbe 128 bis 143). Und so hat die Mutter aller Gamer wieder für ausgewogene, allumfassende Gerechtigkeit gesorgt. Iustitia Nintendae.

So. So? Ja. So. Weiter.

Du erinnerst dich an mein Beispiel mit dem Karton, der nur 8 Bit breit ist, in den aber zwei mal 8 Bit passen? Das ist die Sache mit dem Low Byte und High Byte gewesen.
Ich erklärs nochmal, da das ein wichtiger Punkt ist, der gern einen Knoten im Hirn produzieren kann:

Du hast zwei Byte und diesen Karton, und du weißt, welchen du im Karton unten und welchen oben haben möchtest (Low Byte und High Byte, Low Byte = "die unteren, niederen Stellen" einer 16-stelligen Binärzahl). Folglich nimmst du zuerst den Low Byte und packst ihn in den Karton.

Genauso ist es bei den Tilemaps. Wenn du sie dir anguckst und siehst dann im Hexeditor deiner Wahl:

00 20

heißt das nicht, dass das Tile $20 ist. Low Byte steht zuerst. Folglich ist, wenn in der Tilemap steht: $00 $20, ergibt das, in ein 16-Bit-Register geladen, $2000.
Folglich heißt $00 $20: Tile 0, aber mit Priority.

Soweit klar?

Noch ein kleiner Hinweis zum VRAM. Vielleicht etwas falsch positioniert, aber bestimmt nicht zu früh erwähnt.
Der VRAM ist $10000 Byte groß. Mit dem VRAM "spricht" man wie mit dem CGRAM über ein Adress- und dein Datenregister (wir erinnern uns die Aufzug-statt-viele-Treppen-Geschichte). $10000 Byte ergibt einen "16-Bit-Adressraum", zu deutsch: Alle Byte sind mit einer 16-Bit-Adresse versehen, von $0000 bis $FFFF.
In den VRAM schmeißen wir
- die Bitplanes der Tiles,
- die Tilemaps und
- später dann auch die Grafiken für die Sprites, denn das sind *trommelwirbel* faktisch auch nur Tile-Bitplanes (Oh no! Oh my!).
Dem SNES werden durch verschiedene Einstellungen gesagt, wo welche Daten im VRAM liegen. Dazu werden immer die "vollen Tausender" als Startpunkt angegeben. Man beachte: Die "vollen Tausender" sind natürlich Hexadezimal zu betrachten, also: $0000, $1000, $2000 usw.

So. Ich denke... jetzt haben wir alles, soweit, jetzt gehen wir wieder ins Klassenzimmer und programmieren. Bringt Schere und Kleber mit.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 16. November 2012, 17:58

Moooi... irgendwo hatte ich mal was wegen Banks, Slots und Org ausgelassen. Das hier gehört dort hin.





Was Banks sind, haben wir bereits besprochen. Die Etagen im grauen Haus.
Slots lassen sich in der Header-Datei einstellen; es handelt sich hierbei um ein WLA DX-Feature, hat also mit dem SNES nichts zu tun.
Es besteht quasi die Möglichkeit, eine Bank in verschiedene Slots einzuteilen. Bildlich gesprochen: Trennwände in die Etage einzuziehen (DSW, 2x12,5 GK, 50er U-Profil, Dämmstoffplatten).

Eine Bank ist, im Falle einer LoRom, $8000 Byte groß ($10000, also doppelt so groß, ist sie nur bei HiRom).
Man kann z. B. jetzt die Slot-Size auf $4000 setzen, was wiederum bedeutet, man hat auf jeder Bank die Möglichkeit, Slot 0 und 1 anzusprechen, wobei das jeweils eine Hälfte von dieser Bank ist.

Wozu das nützlich sein kann, weiß ich nicht. Ich weiß auch nicht, was passiert, wenn man einen Slot auf Größe x erstellt und in diesen Slot x+1 Byte an Daten schmeißt. Ich schätze, der Compiler (WLA) wird zetern, wenn man das zu kompilieren versucht.

"ORG" wiederum dient dazu, im Speziellen zu sagen, an welcher Stelle man losschreiben möchte. Ich schätze, die Ortsangabe ist relativ zum Slotanfang und nicht zum Bankanfang.
Soll heißen:
Wenn ich zwei Slots à $4000 Byte habe und schreibe

Code: Alles auswählen

.BANK 0 SLOT 1
ORG $13
beginnt er wohl bei Byte numero $4013, in die Bank zu schreiben, fängt bei der Angabe "ORG 13" nicht an, vom Anfang der Bank ($0000) an zu zählen.

Wen das interessiert und/oder wer das nutzen möchte, kann das in der offiziellen Dokumentation von WLA DX nachlesen.
Es gibt dort noch allerlei andere Spielereien, die damit möglich sind; die Slots lassen sich auf unterschiedliche Größe festsetzen (z. B. $1000-$2000-$5000, oder beliebig anderes), außerdem kann man "Zwang ausüben", wie stringend die festgesetzten Anordnungen zur Datenstruktur durchgesetzt werden sollen (FREE, SEMIFREE, FORCE).

Wir benötigen das hier aber nicht. Die Slots sind glücklicherweise automatisch im Header so eingestellt, dass man nur einen Slot hat, Slot 0, und der exakt Bankgröße hat.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
Moi-Même
Mr SNES
Mr SNES
Beiträge: 4025
Registriert: 15. März 2009, 01:04
+Positive Tradingpoints+: 6 von 6

Re: Maggi-Fix für SNES Programmierung

Beitrag von Moi-Même » 16. November 2012, 18:03

ähm. "irgendwo"?

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 16. November 2012, 18:14

Moi-Même hat geschrieben:ähm. "irgendwo"?
Ich präzisiere: Irgendwo in diesem Thread. Ich denke, ich habe es fett gemacht oder rot hervorgehoben, das müsste zweite Seite sein, wo ich mit dem Programmieren angefangen habe.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 16. November 2012, 18:51

Noch ein Nachholer... vielleicht kann ja dazu jemand etwas sagen, der wirklich Ahnung von der Thematik hat, über die ich schreibe...?



SNES-Header/SMC-Header, SlowROM/FastROM, LoROM/HiROM

Als Vorwarnung: Das hier ist für mich ein weißer Fleck auf meiner Wissenslandkarte. Deswegen schreibe ich das sehr knapp, um das relevante/mir bekannte Wissen weiterzugeben, ohne viele Spekulationen zu verbreiten.

SNES-Header/SMC-Header
Von ROM-Hacks kennst du vielleicht die konstante Frage: "Muss die ROM headerless sein?". Dabei handelt es sich um die SMC-Header.
SMC steht bekanntermaßen für "Super Magicom", von einer Kopierstation. Diese jene hat vor die Daten von den SNES-Cartridges immer noch 512 Bytes ($200) mit einem Header gepappt, die nichts, nichts, gar nichts sinnvolles an Informationen enthielten. Faktisch sind die SMC-Header auch fast vollständig leer. Ich weiß auch nicht, weshalb sie überhaupt davor gesetzt wurden. Wenn du es weißt, erzähls mir nicht, es interessiert mich nicht.
Diese Header können auch nicht einfach mit einem Hexeditor entfernt werden. Der Grund dafür ist einfach: Die Sprungmarken z. B. werden ja vom Compiler in feste Zahlenwerte umgerechnet, wie ich bereits erwähnt habe. Ich sage: "Geh zur Subroutine, die bei $83DF" beginnt, er geht zu "$83DF" und macht da weiter. Und weil da die Subroutine liegt, ist alles gut. Wenn ich jetzt den Header wegnehme, verschiebt sich der gesamte Quelltext um $200 nach vorne. Die Sprungmarke zeigt allerdings immer noch auf $83DF. Das heißt, wenn ich die ROM ausführe, sieht er den Befehl, geht dort hin, springt aber faktisch $200 Byte zu weit und landet irgendwo, wo er nicht landen soll. Und da das logischerweise alle Sprungmarken in der gesamten ROM betrifft, kann eine ehemaligst geheaderte ROM, die (am achten Tag) an ihrem Header beschnitten wurde, nicht mehr allzu viel sinnvolles machen, bis es *bumm* macht.

SNES-Header sind etwas vollkommen anderes, und die sind auch tierisch wichtig. Geht nicht ohne.
SNES-Header liegen in jeder ROM am Ende der ersten Bank und füllen die letzten 64 Bytes aus, das heißt, bei einer LoROM findet man sie bei $7FC0, bei HiROM bei $FFC0. Das interessante ist: Das SNES bekommt nicht die Information, dass es eine LoROM oder HiROM ist, und guckt dementsprechend bei $7FC0 oder bei $FFC0 nach dem Header, sondern schaut sich die Daten bei beiden Adressen an und beurteilt nach den vorliegenden Daten, welches von beiden den Header enthält, und dementsprechend, ob es LoROM oder HiROM ist.
Was der Header genau enthält, wird z. B. hier alles aufgelistet.
Z. B. wird mit einem Byte gesagt. ob das Spiel einen Zusatzchip besitzt, ob die Cartridge SRAM oder zusätzlichen RAM enthält, ein Byte gibt an, für welche Region das Spiel ist, usw. Es gibt die Möglichkeit, 21 ASCII-Buchstaben für den Titel der ROM einzugeben. Das wichtigste sind allerdings die Interrupt-Vektoren. Interrupts sind "Notfallsignale", die die Hardware an den Prozessor sendet, mit denen dem Prozessor mitgeteilt werden: "Es ist etwas vorgefallen, lass alles stehen und liegen und behandle diesen Vorfall". Ähnlich eines Notfallplans oder eine Notrufliste sind die Interruptvektoren eine Reihe von Verweisen, wo der Prozessor als erstes hingeht, wenn einer dieser Interrupts auftritt ("da werden sie geholfen").
Es gibt insgesamt je fünf Vektoren jeweils für den Native Mode und den Emulation Mode. (HALBWISSEN)Es wurde mir gesagt, dass die wenigsten dieser Vektoren wirklich genutzt werden, da sie eher in einem Computer auftreten, nicht aber beim SNES (/HALBWISSEN). Die relevantesten Vektoren sind der "RESET"-Vektor und der "NMI"-Vektor. Der RESET-Vektor wird ausgeführt, wenn das SNES gestartet wird. Es sagt dem SNES, wo in der ROM das Programm anfängt. Der NMI-Vektor sagt dem SNES, was es machen soll, wenn der VBLANK eintritt.

SlowROM/FastROM
Der Unterschied liegt wohl alleinig in der Zugriffsgeschwindigkeit auf den Speicher. (HALBWISSEN)Der Unterschied war darin begründet, dass es einen schnelleren ROM gab, der teurer war, dementsprechend hat man sich überlegt, ob man die zusätzlcihen Materialkosten aufsich nehmen musste oder nicht.(/HALBWISSEN)

LoROM/HiROM
Bei HiROM besteht der gesamte Adressraum einer Bank ($0000-$FFFF) aus Speicher.
Bei LoROM ist nur die obere Hälfte des Adressraums ($8000-$FFFF) Speicher, die untere Hälfte ($0000-$7FFF) dient für Memory Mapped I/O, also die Register, um mit den Controllern, dem Bildschirm und ähnlichem zu kommunizieren.
Das ist sehr wichtig beim Disassemblen. Das heißt nämlich: Wenn man beim Auseinandernehmen einer LoROM auf einen Jump-Befehl trifft, und dieser auf $83DF auf Bank 0 zielt, muss man in seinem Hexeditor zu $03DF gehen. Ein Jumpbefehl bei $83DF auf Bank 1 führt zu $83DF in der ROM, $83DF auf Bank 2 liegt bei $103DF usw.

Dementsprechend leite ich mir einige Vor- und Nachteile ab, die wohl den Grund dafür liefern, weshalb diese Auswahlmöglichkeit besteht:
- HiROM hat größere Bänke, d. h. es kommt dementsprechend zu weniger Bankwechseln (die Prozessor-Arbeitsschritte, sog. Cycles, und damit Zeit kosten)
- Bei LoROM kann man z. B. den RAM über eine 16-Bit-Adresse ansprechen, da er ja quasi "auf derselben Bank" liegt; bei HiROM erreicht man den RAM nur über die Adresse der beiden Bänke, auf denen er liegt (Bank $7E und $7F). Das sind dann 24-Bit-Adressen und das wiederum wird mehr Cycles verschlingen.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 2. Dezember 2012, 20:32

Kategorie: Programmieren



Tiles on Screen

Guten Morgen, Klasse!
Wir haben heute viel vor, es könnte sein, dass wir auch fünf Minuten in die Pause reinmachen werden, aber mit so Elite-Schülern wie euch weiß ich, dass ich das schaffen kann!

Heute wird gekachelt.
Heute bringen wir Tiles auf den Bildschirm.
Dazu schreiben wir ganz viel in unsere Blanko.asm.
Holt sie alle doch schonmal raus.

Falls ihr nicht weiterkommt, lasse ich die Lösung hier auf dem Lehrertisch liegen.
Für die Schlaumeier: Ja, wir machen das heute ohne DMA. DMA machen wir nächstes Mal.
versteckter Inhalt:

Code: Alles auswählen

.INCLUDE "header.inc"
.INCLUDE "InitSNES.asm"
.INCLUDE "define.asm"

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

Start:
    InitSNES

    LDA #INIDSP_FORCE_BLANK.b
    STA INIDSP.w
    LDA #BGMODE_MODE_1.b
    STA BGMODE.w
    LDA #T_BG1_ENABLE.b
    STA TMAIN.w
    LDA #$10.b
    STA BG1SC.w
    STZ BG12NBA.w

    STZ CGADD

    LDX #$0000.w
    TransferCycle0:
    LDA Palette.w, X
    STA CGDATA.w
    INX
    LDA Palette.w, X
    STA CGDATA.w
    INX
    CPX #$0080.w
    BNE TransferCycle0

    LDA #VMAIN_INCREMENT_MODE.b
    STA VMAIN.w

    LDX #$0000.w
    STX VMADDL.w
    TransferCycle1:
    LDY Tileset.w, X
    STY VMDATAL.w
    INX
    INX
    CPX #$0200.w
    BNE TransferCycle1

    LDX #$1000.w
    STX VMADDL.w
    LDX #$0000.w
    TransferCycle2:
    LDY Tilemap.w, X
    STY VMDATAL.w
    INX
    INX
    CPX #$0800.w
    BNE TransferCycle2

    LDA #$0F.b
    STA INIDSP.w

forever:
    jmp forever

.ENDS

.BANK 1 SLOT 0
.ORG 0
.SECTION "Bilddaten"

Tileset:
.INCBIN "Tileset1.pic"

Palette:
.INCBIN "Tileset1.clr"

Tilemap:
.INCBIN "Tilemap.bin"
.ENDS
So.

Als erstes sucht ihr euch euer Arbeitsmaterial aus. Ich habe euch hier drei Schablonen mit Tilesets vorbereitet. Ihr könnt hier sehen, wie die Schablonen aussehen, und dann kann jeder sich das entsprechende Paket runterladen, mit dem er gern kacheln möchte. Aber jeder nur eins!
Tileset.png
Tileset.png (12.85 KiB) 22572 mal betrachtet
Tileset1.zip
(289 Bytes) 959-mal heruntergeladen
Tileset2.zip
(298 Bytes) 960-mal heruntergeladen
Tileset3.zip
(337 Bytes) 968-mal heruntergeladen
So.
Jetzt entpackt jeder mal seine ZIP-Datei und packt sie in seinen Blanko-Ordner.
Wer möchte, kann sie auch in einen gesonderten Ordner tun.

Jetzt gehen wir zu unserer Blanko.asm-Datei über.
Wir werden als erstes dafür sorgen, dass diese Grafikdateien beim Kompilieren mit in die ROM integriert werden.
Dazu schreiben wir folgendes an das Ende der Datei; ganz ans Ende, noch jenseits des ".ENDS":

Code: Alles auswählen

.BANK 0 SLOT 0
.ORG 0
.SECTION "Bilddaten"

Tileset:
.INCBIN "Tileset1.pic"

Palette:
.INCBIN "Tileset1.clr"

Tilemap:
.INCBIN "Tilemap.bin"
.ENDS
Jetzt möchten wir über das reden, was gerade passiert ist.
Wir erstellen eine neue Sektion auf Bank 1, in Slot 0 (also im einzigen Slot, den wir haben, der exakt so groß ist wie die Bank... wir hatten das schon mal), ab Byte numero 0 in dieser Bank (".ORG 0"). Dann geben wir dieser Sektion einen Namen... wozu auch immer. Aber wir tuns. So.

Jetzt binden wir die erste Datei ein und setzen ein Label davor.
Dateien bindet man mit den Befehlen "INCLUDE" und "INCBIN" ein.
Wie ich bereits erwähnt habe, handelt es sich hierbei um WLA-Befehle und nicht um Assembler-Befehle, denn es ist ein Befehl für das Programm, das die ROM erstellt, wie es die ROM zu erstellen hat.
Der Unterschied zwischen "INCLUDE" und "INCBIN" ist, dass "INCLUDE" für Textdateien ist, "INCBIN" für Binärdateien. Wenn du eine Datei öffnest und nur viele Sonderzeichen und Leerstellen siehst, verwendest du "INCBIN". Kannst du mit einem Texteditor die Datei öffnen und findest sinnhaften Text, z. B. vorzugsweise Assembler-Code, benutzt du "INCLUDE".

Da die Grafikdateien aus meinen Zip-Archiven Binärdateien sind (wers nicht glaubt, kann sie sich gern im Texteditor anschauen), verwenden wir "INCBIN". Davor setzen wir jeweils ein Label: Die Dateien werden beim Kompilieren direkt hintereinander gesetzt. Direkt hinter dem letzten Byte der ersten Datei kommt der erste Byte der nächsten Datei. Das Label hilft uns dann, die genaue Adresse des Anfangs der entsprechenden Dateien ansprechen zu können.

Noch in Wort zu den Dateiendungen:
Diese sind beliebig. Speziell, wenn du ein Programm nur für dich schreibst. Du kannst deine Dateien auch "EXE" oder "DOC" nennen; solange der Inhalt richtig ist, sollte es funktionieren, da der Dateiname und seine Endung nur der eindeutigen Idenfizierung dient. Mehr nöcht. Das Bild-Umwandlungsprogramm meines Vertrauens (PCX2SNES) erstellt immer "CLR"-Dateien für Paletten und "PIC"-Dateien für Bitplanes, daher diese Dateiendungen. Die Tilemap nennen wir in diesem Beispiel "MAP"-Datei, ich nenne sie normalerweise immer "INC"-Datei (weil: Datei für INClude, woll?). Es macht keinen Unterschied. Nur erwarte gehobene Augenbrauen (und einiges anderes...), wenn du sie wirklich "EXE" o. ä. nennst.

---

Nun gehen wir zum Hauptteil über. Wir schreiben jetzt nach dem "INITSNES" und vor dem Forever-Loop.
Dabei kann man von drei Teilen sprechen:
  • Die Bildschirmeinstellungen
  • Der Übertragung der Grafikdaten in den VRAM
  • Den Abschluss
Beginnen wir mit den Bildschirmeinstellungen.
Was passiert im Theater, bevor das Bühnenbild umgebaut wird? (Ihr wisst doch noch, was ein Theater ist, oder?)
Richtig! Der Vorhang fällt. Also machen wir auch das Licht aus.

Code: Alles auswählen

LDA #INIDSP_FORCE_BLANK.b
STA INIDSP.w
Wie ich schon einmal erwähnte, sind die Begriffe der define.asm entnommen, die vom großartigen d4s stammt und ohne den es hier keinen Tutorialschreiber für euch gäbe. Nein, er ist nicht mein Vater!
Die Begrifflichkeiten ersetzen eigentlich folgenden Zahlenwert bzw. Registeradresse:

Code: Alles auswählen

LDA #$80.b
STA $2100.w
Hierzu sei noch einmal erwähnt: Der Most Significant Bit, also "die höchste Stelle des Binärzahlenwerts" des Bytes, den man in Register $2100 schreibt, macht den "Forced Blank", also quasi die Sicherung für den Lichtschalter. Der Lichtschalter selbst sind die vier "unteren" Bits, 0-F entscheiden über die Bildschirmhelligkeit. Da wir den "Forced Blank"-Schalter auf "ein" schalten ($80 = %1000.0000), isset nu zappenduster und unsere digitalen Bühnenschieber vermögen es, das neue Szenenbild aufzubauen.

Code: Alles auswählen

LDA #BGMODE_MODE_1.b
STA BGMODE.w
Nächster Schritt: Wir setzen den BG-Mode auf 1. Ohne die define.asm sähe es so aus (dieser Code funktioniert natürlich auch mit define.asm):

Code: Alles auswählen

LDA #$01.b
STA $2105.w
Dazu sei gesagt: Bei Register $2105 entscheiden die untersten drei Bits über den BG Mode (3 Bits können die Zahlen 0 bis 7 abbilden, folglich schreibt man dort eine 1 für Mode 1, eine 3 für Mode 3 und eine 7 für Mode 7), der vierte Bit (also... Bit 3 bei einer Zählung von 0 bis 7) entscheidet im Mode 1 darüber, ob BG3 vor BG1 und BG2 sein soll oder nicht (brauchen wir hier nicht, weil wir eh erst nur mit BG1 arbeiten wollen).
Die anderen vier Bits entscheiden über die Größe der Tiles auf den 4 potenziell vorhandenen BGs: Ist z. B. Bit 7 gesetzt, hat BG4 16x16 große Tiles, sind die Bits 4, 5 und 6 geleert (also auf Null), haben (entsprechend in derselben Reihenfolge) BG1, BG2 und BG3 8x8 große Tiles.
Wir benutzen hier nur BG1 mit 16 Farben und 8x8 großen Tiles, also setzen wir in diesem Register nur den BG-Mode auf 1 und gut ist.

Code: Alles auswählen

LDA #T_BG1_ENABLE.b
STA TMAIN.w
Auch bekannt als:

Code: Alles auswählen

LDA #$01.b
STA $212C.w
In diesem Register aktivieren wir BG1 auf dem "Mainscreen". Das ist deshalb nötig, weil das Nachbar-Register ($212D) für den "Subscreen" zuständig ist, namentlich: Color-Math-Halbtransparenz-Effekt-Tralala. Das machen wir nächstes Schuljahr. Sofern ihr alle die Versetzung schafft. Aber da mache ich mir außer bei dem kleinen Timo ansonsten bei keinem von euch Gedanken. Und jetzt hör auf, den Prittstift zu essen, Timo!
In TMAIN (und dementsprechend TSUB) kann man die vier BGs und die Objektebene (= Sprites) hinzuschalten.

Soweit hierzu. Das waren zunächst die "tatsächlichen" Bildschirmeinstellungen.
Rekapitulieren wir:
Nach dem Forced Blank kamen folgende Register zum Einsatz:

BGMODE aka. $2105 für den BG-Mode und eventuell die Tile-Größe und die BG3-Priority
TMAIN aka. $212C für die Aktivierung des BG1 auf dem Mainscreen

Jetzt kommt der zweite Teil der Bildschirmeinstellungen, nämlich die Einstellungen des VRAM. Bevor wir gleich die Daten in den VRAM übertragen, sagen wir jetzt dem SNES, wo es im VRAM nachschauen muss, um bestimmte Daten zu finden. Also schreiben wir als nächstes:

Code: Alles auswählen

LDA #$10.b
STA BG1SC.w
oder:

Code: Alles auswählen

LDA #$10.b
STA $2107.w
BG1SC/$2107 entscheidet über die Größe der Tilemap und deren Adresse. Die Größe wird in den beiden niedrigsten Bits angegeben. Da wir beide auf 0 setzen, heißt das, die Tilemap ist 32x32 Pixel groß. Setzt man Bit 0, hat der BG 64 Tiles in horizontaler, bei Bit 1 in vertikaler Dimension. Setzt man diese beiden Bits, muss man dann vier 32x32-Tiles-Tilemaps hochladen; statt eine große 64x64 Tilemap schreibt man vier 32x32 Tilemaps als Flicken und legt sie hintereinander im VRAM ab, das zusammennähen übernimmt das SNES.
Die restlichen sechs Bits dienen der Adresse der Tilemap im VRAM. Dabei sind die Startpositionen "gestaffelt", dazu eine Veranschaulichung:

Ich sagte, die oberen sechs Bit sind für die Adresse zuständig.
Grafisch ausgedrückt: %0000.00xx setzt die Adresse für die Tilemap zu VRAM-Adresse $0000. Was xx ist, ist ja auch wurscht, da gehts ja nur um die Größe der Tilemap (siehe vorherigem Absatz).
Jetzt nehme ich mal spaßeshalber den nächsthöheren Wert: %0000.01xx. Jetzt sucht der SNES die Tilemap bei $0400 im VRAM.
%0000.10xx = $0800 (im VRAM)
%0000.11xx = $0C00 (im VRAM)
Und so weiter.

Wie gesagt, "gestaffelt", man kann die Tilemaps nur in einem "Raster" von $400 Byte ablegen.

Der Vorteil ist: Das ist recht leicht zu merken. Wenn man %0000.0100 in Hexadezimal umschreibt, hat man $04. Speichert man das in BG1SC, sucht das SNES die Tilemap ab $0400 im VRAM.
Speicher ich $08 in BG1SC, sucht es die Tilemap ab $0800.
Speicher ich, wie wir es jetzt hier tun, $10 in BG1SC, sucht es die Tilemap in $1000.
Das System verstanden?
$2C = $2C00 im VRAM.
$80 = $8000 im VRAM.
$05 = ... naaa? Richtig! Tilemap ab $0400 im VRAM + 32x64-Tiles-Tilemap! ($05 = %0000.0101)
Also, das System ist klar, aber obacht, man ist versucht, aus diesem $04er-Schrittmuster auszubrechen, und schon sind sie da, die Bugs.

Code: Alles auswählen

STZ BG12NBA.w
oder

Code: Alles auswählen

STZ $210B.w
oder aber auch, wenn man ein paar Prozessorarbeitsschritte zu verschenken hat:

Code: Alles auswählen

LDA #$00.b
STA $210B.w
Mit $210B wird die Adresse des Tilesets angeben. Dabei sind die unteren vier Bits für die Adresse des Tilesets für BG1 zuständig, die oberen vier für BG2. Man kann, wie hier verwandt, für beide BGs dasselbe Tileset gebrauchen, dann setzt man bei beiden dieselbe Zahl ein. Normalerweise wäre hier wohl die Zahl unterschiedlich. Man kann aber, wie in diesem Fall, auch dann dieselbe Zahl einsetzen, wenn man nur einen von beiden BGs gebraucht...
Die Schrittweite ist hier immer folgende tausender. Also: #$41 in $210B lässt das SNES das Tileset für BG1 ab $1000 im VRAM suchen, und das Tileset für BG2 sollte bestenfalls bei $4000 und folgenden Speicheradressen liegen.

Und damit haben wir auch schon alles eingestellt und kommen jetzt schon zum zweiten Teil!

Falls es aber wen wundert: Die Grafikdaten werden doch in drei Teile zerschnippelt, warum müssen wir nur zwei Sachen im VRAM einstellen? Ganz einfach: Das dritte waren die Farben, und die Einstellung der Paletten für die Tiles sind Bestandteil der Tilemap. Aber gut mitgedacht! Sowas lob ich mir! Eine aufgeweckte Schülerschaft! Darum seid ihr auch alle meine Eliteschüler!

... na ja, bis auf Timo, natürlich. Es wird nicht im Unterricht gegessen, Timo!

Zweiter Teil: Datenübertragung
Die Datenübertragung beginnen wir mit dem Transfer der Farben. Dazu verwenden wir eine Schleife.
Für die absoluten Programmieranfänger:
Schleife nicht wie bei den Turnschuhen (auch wenn die meisten von euch sie können und nicht mehr auf Klettverschluss-Schuhe angewiesen sind... euer Lehrer trägt die wirklich nur aufgrund von modischen Aspekten), eher Schleife wie bei Zeitschleife, you know?, Bill Murray, Murmeltier...?
Eine Schleife ist eine bestimmte Folge von Befehlen, die eine bestimmte oder unbestimmte Anzahl von Durchläufen wiederholt wird. Eigentlich ist es schon viel zu spät, das zu erklären, denn wir kennen ja bereits den Forever-Loop. Das ist eine Schleife. Allerdings ohne Ende. Jetzt schreiben wir eine Schleife mit Ende.

Jetzt gehts wirklich ganz tief rein ins Programmieren, und es kommen einige neue Befehle.
Wir schreiben:

Code: Alles auswählen

STZ CGADD

LDX #$0000.w
TransferCycle0:
LDA Palette.w, X
STA CGDATA.w
INX
LDA Palette.w, X
STA CGDATA.w
INX
CPX #$0080.w
BNE TransferCycle0
Zunächst STZ in CGADD. Zu deutsch: Speichere null in CGADD. Zu englisch: Store Zero in CGADD.
CGADD gehört zu CGRAM, und da denken wir zurück an das Beispiel mit dem Haus mit Aufzug und Treppe. Wir drücken den Aufzugsknopf für "Erdgeschoss", und gleich fangen wir an, durch die Aufzugstür Datenpakete zu schleudern.

Aber zuerst laden wir in X den Wert "$0000". Wir machen das X-Register leer. X dient uns jetzt als Zähler, dessen Inhalt darüber entscheidet, ob die Schleife noch einmal wiederholt werden soll oder nicht.

Als nächstes setzen wir ein Label namens "TransferCycle0", hier soll unser Loop, unsere Schleife, beginnen (wenn es auf Englisch "Loop" heißt, warum hab ichs dann eigentlich "TransferCycle" genannt...? Egal).

Im nächsten Schritt benutzen wir einen uns unbekannten Befehl. "LDA Palette.w, X". Wir kennen zwar "LDA", aber das hier ist eine ganz eigene Variante von "LDA". Wir kennen "LDA" zum Laden von festen Zahlen ("LDA #$00.b", "LDA #$1200.w", oder "LDA #BG_MODE_MODE_1.b") und wir können mit LDA auch den Inhalt von Registern auslesen (z. B. "LDA $4210").

Wenn ich schreibe "LDA Adresse, X", dann nimmt der Prozessor den Inhalt von X, addiert ihn auf "Adresse", und lädt den Inhalt.

AUFGABE:

Code: Alles auswählen

LDA #$0A.b
STA $0001.w
LDA #$0B.b
STA $0002.w
LDA #$0C.b
STA $0003.w
LDX #$0001.w
LDA $0002.w, X
Welcher Wert befindet sich am Ende in A?

Lösung:
versteckter Inhalt:
A = #$0C.b
Die ersten sechs Zeilen packen A, B, C in drei Register. Die siebte Zeile lädt den Zahlenwert $0001 in X. In der achten Zeile steht der Befehl dafür, ab Register $0002 um Inhalt-von-X Register weiterzugehen und den Inhalt zu laden. X = #$0001. #$0002 + #$0001 = #$0003. Der Inhalt von $0003 = #$0C (siehe Zeile 5/6). Et voila.
Gut. Zurück zum Programm. Jetzt nehmen wir das Label "Palette" und nehmen X, unseren Counter, um weiterzugehen. "Palette" haben wir vorhin vor das "INCBIN" der clr-Datei geschrieben. Beim ersten Durchlauf der Schleife wird jetzt der erste Byte der Grafikdaten geladen (da ja X = #$0000 ist und somit 0 auf die Adresse des "Palette"-Labels addiert wird) - es ist übrigens ein Byte, weil der Akkumulator ja bereits die ganze Zeit 8-Bit-groß ist.
Jetzt speichern wir in CGDATA, also "werfen" ein Datenpaket durch die Aufzugstür. Dann machen wir "INX", das steht für "Increment X", also "Mach den Wert von X um eins größer".
Diese drei Befehle wiederholen wir jetzt noch einmal*. Durch das INX lädt er jetzt beim zweiten Mal "LDA Palette.w, X" den zweiten Byte (weil: Palette hat sich nicht bewegt, aber X ist ja bereits eins größer geworden). So weit, so gut.

Nach dem zweiten Inkrementieren schreiben wir "CPX #$0080.w".
Compare X. Vergleiche X (mit)...
Der Inhalt des X-Registers wird mit dem nebenstehenden Wert verglichen. Wenn die Werte gleich sind, wird im Statusregister ein Flag gesetzt (das Zero-Flag, um genau zu sein).
Die Gegenstücke für die anderen beiden Register heißen übrigens CPY und CMP.

Zum Abschluss schreiben wir "BNE TransferCycle0".
BNE steht für "Branch Not Equal", grob (und verständlich) übersetzt: "Nimm die Abzweigung, wenn das Ergebnis nicht gleich ist". Der Befehl guckt, ob das entsprechende Flag (das Zero-Flag, um genau zu sein) im Statusregister gesetzt ist, wenn ja, geht es normal mit dem nächsten Befehl weiter, wenn nicht, springt es zum Label.
Das Gegenteil von BNE ist BEQ ("Branch Equal").

Fassen wir noch einmal zusammen:
Wir setzen den Counter auf null, dann steigen wir in den Schleifenbereich ein: Zweimal laden wir in Abhängigkeit von der Höhe des Counters einen Byte aus den Paletten-Daten und speichern den Wert in CGDATA und erhöhen dann den X-Wert, danach gucken wir, ob X bereits $80 (= 128) erreicht hat**, und wenn nicht, wiederholen wir die Schleife.

Übrigens müssen wir den Wert in CGADD nicht bearbeiten. Wenn zweimal ein Byte in CGDATA geschrieben wird, also "ein vollständiger Farb-Datensatz" geschrieben wurde, wird der Wert in CGADD automatisch inkrementiert. Das hatte ich beiläufig beim Fahrstuhl-Beispiel erwähnt gehabt, falls man sich erinnert. Ich weise noch einmal darauf hin: Das wird erst inkrementiert, wenn zwei Byte geschrieben wurden.

Okay, damit haben wir das abgeschlossen. Wir sind zwar immer noch beim Datentransfer, aber als nächstes muss doch noch eine Einstellung getroffen werden:

Code: Alles auswählen

LDA #VMAIN_INCREMENT_MODE.b
STA VMAIN.w
In schnöden Zahlen ausgedrückt:

Code: Alles auswählen

LDA #$80.b
STA $2115.w
Warum treffe ich diese Einstellung erst jetzt?
Eine beliebte Möglichkeit, sich das Hirn beim Bugfixing zu brechen, sind Bugs, die nur auf echter Hardware und den guten Emulatoren (wie BSNES) auftreten, nicht aber bei den vereinfachten, vereinfachenden Emulatoren (an deren Spitze ZSNES steht, den man nicht zu verwenden hat!). Und eine gute Möglichkeit, solche Bugs zu produzieren, ist die Reihenfolge, wie man Befehle ausführt. Scheinbar gibt es bestimmte Befehle der Bildschirmeinstellungen, die auf echter Hardware einen Forced Blank auslösen. Deswegen sollte man den Bildschirm immer zu allerletzt wieder einschalten (es sei denn, man möchte wie ich tagelang nach dem Grund suchen, weshalb das Bild schwarz bleibt). Deshalb habe ich mir angewöhnt, alle Einstellungen immer erst zu treffen, wenn ich sie tatsächlich auch brauche, auch wenn es unübersichtlich werden mag (für mehr Übersichtlichkeit kann man ja Leerzeilen und Kommentare setzen, wie man will). Darum diese Einstellung erst jetzt.

Was bewirkt diese Einstellung?
Das... ist eigentlich ganz einfach. Mit dem VRAM kommuniziert man wie mit dem CGRAM. Man hat zwei 8-Byte-große nur-beschreibbare-das-heißt-nicht-auslesbare Register für die Adresse und dasselbe für die Daten. Logischerweise hat man beim VRAM zwei Adressregister statt nur eins, da der CGRAM ja nur 256 Plätze zum Ansprechen braucht (#$00-#$FF), während der Adressraum vom VRAM ja 16-Bit-groß ist (d. h. eine vierstellige Hexadezimalzahl... wir haben eingestellt, dass die Tiles bei $0000 und die Tilemaps bei $1000 liegen, du erinnerst dich?).
Mit dem Most Significant Bit im Register VMAIN/$2115 stellt man ein, wann der Inhalt der Adress-Register erhöht werden soll:
0 = Beim Schreiben in VMDATAL/$2118
1 = Beim Schreiben in VMDATAH/$2119
Der Unterschied ist: Wenn ich ein 8-Bit-großes Register habe und in $2118 schreibe, schreibe ich nur in $2118. Habe ich ein 16-Bit großes Register, schreibt es die unteren acht Bit in $2118 und die oberen in $2119. Dementsprechend, auch wenn das jetzt sehr kompliziert ist, muss mach sich eigentlich nur eins merken:
Diese Einstellung (#VMAIN_INCREMENT_MODE in VMAIN, bzw. #$80 in $2115) muss man machen, wenn man mit einem 16-Bit-Register Daten übertragen will. Wenn man mit einem 8-Bit-Register übertragen will, macht man das nicht.

Machen wir weiter:

Code: Alles auswählen

LDX #$0000.w
STX VMADDL.w
TransferCycle1:
LDY Tileset.w, X
STY VMDATAL.w
INX
INX
CPX #$0200.w
BNE TransferCycle1

LDX #$1000.w
STX VMADDL.w
LDX #$0000.w
TransferCycle2:
LDY Tilemap.w, X
STY VMDATAL.w
INX
INX
CPX #$0800.w
BNE TransferCycle2
Das sieht nach viel Code auf einmal aus, aber das ist im Endeffekt ganz viel altbekanntes. Wir laden in X wieder Null und speichern es in die VRAM-Adress-Register (da VMADDL/$2116 nur 8-Bit groß ist, werden automatisch die oberen 8-Bit von X in VMADDH/$2117 gespeichert). Jetzt beginnt der zweite TransferCycle:
Es wird ein Doppelbyte vom Tileset geladen und in VMDATAL gespeichert; da Y wie X 16-Bit-groß ist, ist es hier wie bei den Adress-Registern: Die untere Hälfte des Doppelbytes kommt in VMDATAL/$2118, die obere in VMDATAH/$2119. Dann wird X zweimal inkrementiert (klar, weil wir ja auch einen Doppelbyte in einem Zug übertragen haben), und der ganze Spaß wird mit 512 Bytes gemacht, denn sobald X $200 (= 512) erreicht hat, ist das Ganze vorbei.

Wenn man jetzt im Explorer bei der .pic-Datei nachschaut (Rechtsklick, Eigenschaften), sieht man da auch, dass die Datei tatsächlich exakt 512 Bytes groß ist.

Der zweite Teil ist dasselbe wie der erste, nur dass zuerst $1000 in VMADDL gespeichert wird, wir also die Anfangsadresse auf $1000 setzen. Danach muss man nochmal $0000 in X laden, um den richtigen Counterwert zu haben.

Wenn euch aufgefallen ist, habe ich keine Tilemap-Datei mitgeliefert. Die müsst ihr gleich selbstschreiben. Aber machen wir erst das hier fertig.

Kommen wir zum dritten Teil, dem Abschluss.
In der klassischen Musik spräche man von der Coda.

Code: Alles auswählen

LDA #$0F.b
STA INIDSP.w
Zu deutsch: Bildschirm an, gib vollen Saft drauf.

Fertig. Jetzt schreiben wir noch die Tilemap. Dazu nehmt ihr euren Hexeditor, ich kann euch HxD empfehlen, falls ihr den nicht schon habt.

Jetzt erstellst du eine neue Datei in dem Ordner (ist egal, was für eine; nimm z. B. eine neue TXT-Datei) und benenn sie um, wie du deine Tilemap nennen möchtest. Jetzt öffnest du diese Datei mit dem Hexeditor. IN HxD gibt es eine schöne Einstellung, die du so treffen kannst:
01.jpg
01.jpg (145.58 KiB) 22572 mal betrachtet
Wähle 64 aus. Das ist die Einstellung, ab wie viel Byte ein Zeilenumbruch in HxD angezeigt werden soll. Bei 64 stimmt es mit dem Zeilenumbruch überein, der bei einer Tilemap ist - soll heißen: Bei einem Zeilenumbruch nach 64 Byte sind die Daten eines Tiles über den Daten des Tiles, dass am Ende auch wirklich auf dem Bildschirm dargestellt werden.
Und jetzt... fang an zu schreiben.
Wie die Daten für die Tilemap aufgebaut sind, habe ich im vorherigen Teil beschrieben. Um dir zu helfen: Du kannst folgende Dinge schreiben: 01 00 oder 02 00 oder 03 00. Entsprechend dem wird dann jeweils das Tile eingefügt, was der Nummer entspricht (guck oben auf die Übersicht der Tilesets für die Reihenfolge der Tiles).

Und wenn du fertig bist... speicher alles, kompilier... und fertig! :)

Und, ach ja, prüf am besten, ob die Dateinamen der Tilesets auch mit den Dateinamen im INCBIN-Teil des Sourcecodes übereinstimmen! ;)

~~~

* Der Schlaumeier mag quaken: "Warum zweimal dieselben Befehle? Schreiben wir nicht eine Schleife, um nicht immer dieselben Befehle hintereinander zu kleistern?"
Ja, stimmt. Aber:
Wir erinnern uns an den 2x8-Bit-Karton, was das Register CGDATA ja ist. Euer Lehrer weiß, dass diese Register gern zickig werden, wenn sie nicht sofort nach den ersten 8 Bit den Rest ihrer Daten erhalten; wenn sie es nicht tun, schmeißen sie den ersten Teil sehr bald weg und tun so, als hätten sie nie etwas erhalten. Und da euer Lehrer nicht geneigt ist, große Experimente durchzuführen, ob dem so ist, wenn man nur eine Hälfte eines Farbdatensatzes überträgt und dann eine etwaige Schleifenwiederholung prüfen lässt, macht er das lieber so.

** Der Schlaumeier mag quaken: "Aber eine Palette mit 16 Farben hat doch niiie im Leben 128 Byte Größe!"
Richtig. Wenn du die clr-Dateien mit einem Hex-Editor öffnest, siehst du da ganz viele Nullen. Wenns dich stört, änders doch. Du könntest sogar die ganzen Nullen rauslöschen; wenn du dann trotzdem den Loop bis $80 laufen lässt, werden sogar die ersten Bytes der nachfolgenden Datei (der zur Zeit noch nicht existierenden Tilemap-Datei) in den CGRAM übertragen, was total wurstige (und höchstwahrscheinlich recht dunkle) Farben ergeben wird. Aber, solange du deine Tilemap ordentlich schreibst und immer nur Palette 0 verwendest, müsste alles glatt gehen. Wobei ich bezweifle, dass du dazu in der Lage bist, Timo. Ich möchte mal beim nächsten Elternabend mit deinen Eltern sprechen.











WETTBEWERB! WETTBEWERB! WETTBEWERB!

Das da oben ist ne Menge Holz, also bitte ich darum: Wenn das wer liest und was nicht versteht: Bitte fragt. Bitte postet Verständnisfragen hier. Nur so kann der Kram besser werden.

Und ansonsten, wenn jemand das alles ausprobiert: Bitte zeigt die Ergebnisse. Ich würde mir eine hübsche kleine Galerie wünschen, was man alles dadraus basteln kann. ;)
Dateianhänge
Grafik-Demo.rar
(117.15 KiB) 946-mal heruntergeladen
Zuletzt geändert von lytron am 19. Februar 2013, 19:50, insgesamt 1-mal geändert.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
Moi-Même
Mr SNES
Mr SNES
Beiträge: 4025
Registriert: 15. März 2009, 01:04
+Positive Tradingpoints+: 6 von 6

Re: Maggi-Fix für SNES Programmierung

Beitrag von Moi-Même » 2. Dezember 2012, 20:44

Also ich bin soweit mit dem Kopf noch halbwegs dabei. ^^'

Ist echt wahnsinnig beeindruckend, dieser ganze Thread. °__°

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 2. Dezember 2012, 20:57

Moi-Même hat geschrieben:Also ich bin soweit mit dem Kopf noch halbwegs dabei. ^^'

Ist echt wahnsinnig beeindruckend, dieser ganze Thread. °__°

Und wie beeindruckend es erst ist, dass du das ganze noch nachbearbeiten wirst! :P ;)

Danke für die Blumen, gerade der letzte Post hat schon mehr als nur Überlänge. Aber ich denke, damit zeichnet sich jetzt auch schon das Ende ab.

Roadmap (für mich zur Orientierung):
  • Nachbereitung (Wiki/WLA Doc/WDC Doc - Nachbereitung Bildschirmfärbung)
  • DMA
  • Timing
  • Zweiter BG
  • Subscreen (Color math)
  • Sprites
  • Buttons
  • Schlusswort Grundkurs
Okay, soll heißen, wenn ich das gerade richtig überschaue, brauche ich noch 8 Artikel, wovon wohl nur 6 richtige Artikel sind, und ab dann bin ich mit dem Grundkurs fertig. Alles, was danach kommt, könnten dann unzusammenhängende Artikel in beliebiger Reihenfolge sein. :)
Blanko_00000.png
Blanko_00000.png (1.5 KiB) 22569 mal betrachtet
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 3. Dezember 2012, 21:09

Nach der Unterrichtsstunde

Gut. Du scheinst noch ein wenig Zeit zu haben? Dich quälen Fragen, und dafür opferst du sogar einen Teil deiner Großen Pause? Mensch, das ist ja löblich...!

Eine noch ausstehende Erklärung wegen des Bildschirmeinfärbens. Wie hat das alles funktioniert, wenn wir keine Bildschirmeinstellungen vorgenommen haben, keine Tiles erstellt, keine Tilemaps hochgeladen usw. usw.?
Die Erklärung ist einfach. Wir haben keine Bildschirmeinstellungen vorgenommen, also gilt das "#$00"-Gespeichere der InitSNES.asm. Das bedeutet: Tileset und Tilemap sucht sich das SNES im VRAM bei #$0000. Jetzt guckt es in den Anfang des VRAMs und sieht überall nur Nullen (der VRAM wird auch während des InitSNES-Makros/der InitSNES-Subroutine gecleart). Es interpretiert das als Tilemap und liest: Okay, setz überall Tile 0 ein, ohne weitere Sondereinstellungen. Dann guckt es nach, wo es die Tiles findet, sieht: "Okay, ab $0000 im VRAM", und findet dort dieselben Nullen vor. Da das SNES nicht denken kann, kommt es sich auch an dieser Stelle nicht verkaspert vor. Es schaut auf dieselben Nullen wie vorher und interpretiert die jetzt auch als Bitplanes, und sieht überall nur Nullen. Folglich füllt er alles mit Farbe 0.

So kann man logisch nachvollziehen, wie es kommt, dass man, wenn man keine weiteren Einstellungen an Tilemaps und Tilesets getroffen hat, mit der Farbe 0 die gesamte Bildschirmfarbe bestimmen kann. Und außerdem kann man man logisch nachvollziehen, weshalb ich besonders darauf hingewiesen habe, dass diese Funktion nicht funktioniert, wenn man bereits Tiles und anderes auf den Bildschirm gebracht hat.

Gut, soweit dazu.

Das nächste, was schon längst überfällig ist, sind die Handbücher und Dokumentationen, die man nutzen kann.
Dazu führe ich nochmal die drei Kategorien an, für die es einzelne Hilfsmittel gibt:
Wie schon des öfteren gesagt, findet man alle Assembler-Befehle in alphabetischer Reihenfolge ab Seite 327 erklärt. Der Rest davor fängt in etwa da an, wo ich mit diesen Tutorials begonnen habe, allerdings weniger bildlich und, sprachlich gesehen, wesentlich angelsächsischer, und erklärt danach sehr detailliert die "theoretischen Konzepte" hinter den Assemblercodes. Ich persönlich habe das stringente Lesen ab Seite 90, ungefähr, abgebrochen, wobei ich damit nicht sagen will, dass ich es nicht für sinnvoll halten würde, es zu lesen. Wer den Teil vorher aufmerksam liest, kann sich viele Bugs ersparen, denn ich musste von meinem programmiertechnischen Sozialarbeiter auf viele Besonderheiten hingewiesen werden, von denen mir später aufgefallen ist, dass ich sie in diesem Handbuch schon gelesen haben muss.

Bezüglich der Liste der Register möchte ich kurz den Aufbau dieser Seite erklären:
Die oberen Tabellen führen die SNES-Register. Ich schätze, das sind alle, wenn nicht, dann zumindest die meisten. Es gibt in dem Adressraum viele freie Flecken. Die Spalte "Name" enthält die Kürzel, die wir aus der define.asm kennen. Links daneben, in der Spalte "Address", stehen die typischen Hexadezimal-Adressen. Mit der define.asm zu arbeiten, ist sicherlich von Vorteil, weil man Fehlerquellen damit ausmerzt und außerdem sich gewisse Dinge schneller merken kann; allerdings ist ein Nachteil, dass man beim Disassemblen nicht diese Registernamen hat, und auch ansonsten etwas wie die define.asm kein "internationaler Standard" ist, tatsächlich benutzen die allerallermeisten Programmierer noch die typischen Hex-Adressen. Deswegen erreicht man das Beste (Best of Both Worlds), indem man zweigleisig fährt und die define.asm nutzt, während man auch die Hex-Adressen nicht aus den Augen verliert (Meine Meinung).
Interessant sind hier auch die anderen Spalten: "Style" nennt die Größe des Registers ("single": 8 bit, "dual": 16 bit), Access die Les- und Beschreibbarkeit und "Timing", wann man etwas in dem Register ändern kann/sollte. Du erinnerst dich, dass ich im vorigen Teil erwähnte, dass ich mal aufgrund eines zu früh eingeschalteten Bildschirms lange Zeit kein Bild hatte und nicht gefunden habe, wo der Fehler lag? Hätte ich die Spalte mehr beachtet, wäre mir das wohl nicht passiert.

Das ist der obere Teil. Der untere Teil erklärt den Aufbau jedes dieser Register im Genauen. Es erklärt, welcher Bit was auslöst. Ziemlich kompakt, gelegentlich fachchinesisch und/oder schwammig, aber definitiv das Beste, was das Zwischennetz zu bieten hat.
Außerdem ist diese Registerliste wohl das von mir mit Abstand am Häufigsten genutzte dieser Hilfsmittel: Auf jedes Mal, dass ich in der WLA-Doc was nachschaue, schaue ich zwei- bis dreimal in die 65816-Manual und acht- bis zehnmal in die Register-Wiki-Seite.

Ein weiteres Hilfsmittel ist diese Seite. Eigentlich ist die Tabelle der Opcodes (ganz unten) nur eine Zusammenführung der Daten, die man in der WDC-Manual zum 65816 auch findet, allerdings ist das für mich speziell beim Disassemblen sehr hilfreich, die Hexcodes der Opcodes sehen zu können.

Joa. Aber ich verplapper mich schon wieder. Die Pause ist auch schon so gut wie um. Tust du mir den Gefallen, und nimmst das Klassenbuch mit, ja?
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Antworten