Neue Version 0.8.0 der Konverter-Tools veröffentlicht

Die neueste Version der Konverter-Tools, 0.8.0, wurde soeben veröffentlicht und kann hier heruntergeladen werden.

Diese Version enthält einen verbesserten Run Length Encoding-Algorithmus, der auch einzelne verschiedenfarbige Farbblöcke codieren kann.

Posted in BMH, BMP, EM, General information, Graphics, Tools, Veröffentlichungen | Tagged , , , , , | Leave a comment

Bilder in WordPress mit Caption einfügen

Ich habe demletzt ein paar Skripte geschrieben, um die .VGA/.CP-Dateien von BMP, EM und BMH automatisch umzuwandeln. In erster Linie ging es mir darum, die Konverter zu testen, dabei wird die Konvertierung VGA->BMP->VGA->BMP gemacht und die zwei BMPs (nach einer initialen manuellen Inaugenscheinnahme) verglichen – sind sie identisch, ist die Konvertierung okay, ansonsten gibt es bei einem der Schritte wohl Probleme. Das hat mir dann auch ermöglicht, ein paar Seiten mit Übersichten über alle Grafikdateien der jeweiligen Spiele und Versionen zu machen. Der besseren Unterscheidbarkeit habe ich die Dateien vorher umbenannt, also zum Beispiel in bmp-de-1.03-0.vga.bmp. Nach dem Hochladen wurde dann von WordPress der Titel gesetzt (Dateiname ohne Bild-Endung), nicht aber die Caption, die musste ich händisch setzen. Das ist natürlich nervig, die Caption ist aber wichtig, weil die in der Gallerieansicht unter den Bildern erscheint. So was mache ich dann mal zwischenrein, auch zwei und drei Mal, aber auf Dauer ist das ja nichts. Also hab ich geschaut, wie ich WordPress die Caption vermitteln kann.

Erstmal habe ich mich dazu hab entschlossen, JPG-Dateien hochzuladen (mogrify -format jpg *.bmp reicht ja zur Konvertierung), die sind natürlich viel kleiner als die Bitmaps, die waren aber halt eh da. In den JPGs kann man dann auch schonmal den Titel setzen und eben auch die Caption, ein Script, dass das mit exiftools macht, hab ich mir schnell zusammengeschustert, hier am Beispiel von BMP 2.0 auf Französisch:


#!/bin/bash

TARGET="bmp-fr-2.0"

for file in *.jpg;
do
name=`echo ${file} | sed -e 's/.jpg//'`
exiftool -IPTC:Headline="${TARGET}-${name}" -IPTC:Caption-Abstract="${name}" ${file}
cp ${file} ${TARGET}-${name}.jpg
done

Aus 0.vga wird so bmp-fr-2.0-0.vga.jpg, bei den Metadaten stehen dann als Headline der Dateiname und als Caption-Abstract der Name der Datei ohne Endung. Beim Hochladen wird das dann aber von WordPress in das Feld “Description” übernommen – bleh, das Feld Cpation gibts aber doch auch, das bleibt allerdings leer. Nach einiger vergeblicher Bastelei hab ich dann auf dieser Seite die Lösung gefunden und mir einfach ein Plugin geschrieben:


/**
Plugin Name: FreeBMP Captioner
Description: Fix up WordPress to set captions properly from image meta data (inspired by http://tinyurl.com/ph9xu8r).
Author: Roland Kübert
Author URI: http://sourceforge.net/projects/freebmp/
**/
function adjust_attachment($id) {
$attachment = & get_post( $id, ARRAY_A );
if ( !empty( $attachment ) ) {
$attachment['post_excerpt'] = $attachment['post_content'];
$attachment['post_content'] = '';
wp_update_post($attachment);
}
}
add_action( 'add_attachment', 'adjust_attachment' );
?>

Bei Hinzufügen eines Attachments wird also einfach der Inhalt des “post_content”-Feldes (mit dem Label “Description”) nach “post_excerpt” verschoben (Feld mit dem Label “Caption”). Jetzt ist quasi der ganze Prozess automatisiert, weder Caption noch Titel muss ich jetzt ja überhaupt anfassen. Für mein privates Blog sollte ich mir das auch mal überlegen, macht ja eigentlich eh mehr Sinn die Captions in den JPG-Dateien zu schreiben als sie nur exklusiv in WordPress zu haben.

Posted in BMH, BMP, EM, General information, Graphics, Web | Tagged , , , , , | Leave a comment

Schwarz ist nicht gleich schwarz

Ich bin gerade dabei, ein verbessertes Run-Length Encoding (RLE) zu implementieren, dass auch aufeinanderfolgende Blöcke unterschiedlicher Farben kodiert. Bisher ist es so, dass zum Beispiel die folgenden Farben (bzw. Indizes in die Palette):


1F 10 08 1F

wie folgt kodiert werden:


00 1F 00 10 00 08 00 1F

Das funktioniert zwar, gibt aber eine relativ große .VGA-Datei, da aus den 4 Bytes so 8 Bytes werden.

Eine freundliche Seele aus dem Managerspiele-Forum hatte schon eine verbesserte RLE-Implementierung für den bmp_to_vga-Konverter gemacht (siehe auch diesen Thread im Managerspiele-Forum), ich hab allerdings ein bisschen gebraucht bis ich sie übernehmen konnte, außerdem musste ich den Code noch etwas adaptieren, da der letzten Pixel in einer Datei nicht korrekt behandelt wurde. Die Implementierung macht dann folgendes aus den obigen Bytes:


03 1F 10 08 1F

Die 03 zeigt an, dass 4 einzelne Bytes folgen, der Overhead ist also deutlich geringer, vor allem für längere Blöcke mit verschiedenen Farben.

Im Prinzip kommt da, zumindest beim BMP, bei vielen Daten nach einer Konvertierung VGA->BMP->VGA eine fast identische Datei raus, also gäbe es eine gute Möglichkeit, die korrekte Funktionalität automatisch zu prüfen. Das hatte ich dann auch mal mittels einem Bash-Script für alle Dateien des BMP gemacht, das hat aber nicht funktionier und jetzt kommen wir wieder auf die Überschrift zurück.

In der Tat ist es nämlich so, dass es zum Beispiel bei der Palette für die 0.VGA in BMP (die 4 Managerköpfe) 32 Farben gibt. Schwarz, also RGB 0/0/0 kommt dabei an Index 0 und Index 31 vor. Und natürlich ist es so, dass in der .VGA-Datei schwarz mal als 1F vorkommmt (fast immer) aber auch mal als 00. Naja, das hat dann das Testen in der Form leider unmöglich gemacht, ich habe aber einen, glaube ich, ganz guten Weg gefunden, doch dazu später mehr, ebenso zur verbesserten Version des RLE-Algorithmus.

Posted in BMH, BMP, EM, Graphics, Tools | Tagged , , , , , | Leave a comment

Neue Version 0.7.5 der Konverter-Tools veröffentlicht

Die letzte Version der Konverter-Tools, 0.7.5, wurde soeben veröffentlicht und kann hier heruntergeladen werden.

Diese Version ist eine Bugfix-Veröffentlichung und erhält gegenüber den vorherigen Versionen keine zusätzlichen, neuen Funktionen. Der Fehler, dass in Version 0.7.4 keine GIMP-Palette mehr geschrieben wurden, wenn eine VGA-Datei eine Palette enthält, wurde behoben.

Posted in BMH, BMP, EM, General information, Graphics, Tools, Veröffentlichungen | Tagged , , , , , | Leave a comment

Neue Version 0.7.4 der Konverter-Tools veröffentlicht

Die letzte Version der Konverter-Tools, 0.7.4, wurde vor kurzem veröffentlicht und kann hier heruntergeladen werden.

Diese Version ist eine Bugfix-Veröffentlichung und erhält gegenüber den vorherigen Versionen keine zusätzlichen, neuen Funktionen, trotzdem sollte ab sofort mit der neuen Version 0.7.4 gearbeitet werden.

Posted in BMH, BMP, EM, General information, Graphics, Tools, Veröffentlichungen | Tagged , , , , , | Leave a comment

Einen frisch gespeicherten Spielstand laden

Im vorherigen Beitrag beschrieb ich einen Patch, der es erlaubt, einen Spielstand mehrfach zu laden. Dabei äußerte ich die Vermutung, dass man einen frisch gespeicherten Spielstand deshalb nicht laden kann, weil beim Speichern ebenfalls die Identifkationsbytes ins RAM geschrieben werden.

In der Tat ist das auch richtig, folgender Code-Block schreibt die Idenfikationsbytes beim Speichern ins RAM:


3268:0A2E 8BD8 mov bx,ax
3268:0A30 D1E3 shl bx,1
3268:0A32 D1E3 shl bx,1
3268:0A34 8E06D4A3 mov es,[A3D4] ds:[A3D4]=40C2
3268:0A38 8B8602FF mov ax,[bp-00FE] ss:[ACF0]=09F2
3268:0A3C 8BD1 mov dx,cx
3268:0A3E 2689873ABA mov es:[bx-45C6],ax [illegal]
3268:0A43 2689973CBA mov es:[bx-45C4],dx [illegal]
3268:0A48 8E06D2A3 mov es,[A3D2] ds:[A3D2]=4C73
3268:0A4C 26FE068E23 inc byte es:[238E] es:[238E]=0000

Wir wissen damit zwar immer noch nicht, wie die Daten initialisiert werden, aber egal. Analog zu vorher können wir die relevante Zeile (3268:0A38) wie folgt in zwei Befehle ändern:


250000 and ax,0000
90 nop

Wir stellen so also wie vorher sicher, dass AX = 0. In der BMMAIN.EXE (DE, 1.03) müssen wir dazu die Bytes 202904 bis 202907 bearbeiten und 8B8602FF durch 25000090 ersetzen.

Beim Ausführen der modifizierten BMMAIN.EXE und Breakpoint auf 3268:0A34 sehen wir folgendes:


3268:0A34 8E06D4A3 mov es,[A3D4] ds:[A3D4]=40C2
3268:0A38 250000 and ax,0000
3268:0A3B 90 nop
3268:0A3C 8BD1 mov dx,cx

Wir können danach zwar wie erwartet nicht weiterspielen, allerdings können wir jetzt, so wir denn auch die vorherige Modifikation durchgeführt haben, den gerade gespeicherten Spielstand laden und dann weiterspielen – sweet!

Fazit beider Änderungen

  • Ab Offset 205436: 8B86BAFE in 8B000090 ändern
  • Ab Offset 202904: 8B8602FF in 25000090 ändern
Posted in BMP, DE-1.03, General information, Save games, Technical information | Tagged , , , , | Leave a comment

Einen heutigen Spielstand laden

In meinem letzten Beitrag habe ich berichtet, dass ich daran arbeite es zu ermöglichen, in BMP einen Spielstand auch mehrfach laden zu können – normalerweise ist es in BMP nämlich so, dass man einen Spielstand nur ein Mal laden kann. Ebenfalls ist nach dem Speichern ein Weiterspielen unmöglich, was heißt dass man nach dem Speichern das Spiel verlassen und neu starten muss, um dann den eben gespeicherten Spielstand zu laden, aber das ist eine andere Baustelle. Mittlerweile habe ich es geschafft, die Prüfung, ob ein Spielstand bereits geladen wurde, zu umgehen. In diesem Beitrag möchte ich beschreiben, wie ich vorgegangen bin.

Die verantwortliche Stelle im Spielstand

Ganz richtig beschrieb ich in meinem vorherigen Spielstand, dass die Bytes 29-32 (nullbasiert) im Spielstand selbigen identifizieren. Genauer gesagt tun das Byte 29 und Byte 30, denn die weiteren zwei Bytes sind immer 0. Jetzt musste ich erstmal rausfinden, wann diese beiden Bytes überprüft werden

Ladevorgang eines Spielstand

Bei BMP werden Spielstände nicht komplett geladen sondern durch viele einzelne Lesevorgänge aus der Spielstandsdatei, identisch zur Liste der Schreibvorgänge. Meine Idee war jetzt, zu schauen ob der ganze Spielstand geladen wird und dann die relevanten Bytes untersucht werden oder ob sie direkt nach dem Laden untersucht werden und der Spielstand dann gar nicht komplett geladen wird.

Also flugs einen Breakpoint auf INT 21,3F – Read From File or Device Using Handle gesetzt und geschaut wie oft der Interrupt aufgerufen wird – bei einem bereits vorher geladenen Spielstand nur drei Mal, nämlich zwei Mal um den kompletten Header zu lesen und beim dritten Mal, um die 4 Identifikations-Bytes zu lesen. Wurde der Spielstand bereits geladen, ist danach Schluss und die Meldung “Keinen heutigen Spielstand laden” wird angezeigt.

Wie hab ich diesen Screen geliebt… ;)

Und die Abfrage ist wo?

Ich steppte ein bisschen durch den Code nach dem dritten INT 21,3F, konnte aber keinen Vergleich identifizieren. Der Vergleich wäre eigentlich die eleganteste Stelle zum Patchen gewesen, aber fürs erste kam ich da nicht weiter.

Wo stehen die Daten?

Meine nächste Idee war, zu schauen wo die Daten der bereits geladenen Spielstände abgelegt werden – irgendwo im Speicher muss BMP ja die Identifikation vorhalten um sie dann zu prüfen. Ich manipulierte drei Spielstände derart, dass die Identifikation FF FF 00 00, FF FE 00 00 und FF FD 00 00 war, das sollte ja im Speicher zu finden sein. In DOSBox machte ich dann einfach einen binären MEMDUMP mittels MEMDUMPBIN 0000:0000 FFFFFF. 16 MB Speicherabbild sind vielleicht Quark, aber da ich mit dem DOS(Box)-Speicherlayout nicht so vertraut bin schien mir das okay.

In der MEMDUMP.BIN habe ich dann mit einem Hex-Editor nach der obigen Zeichenkette gesucht und bingo! Ab Position 312922 stand sie im Speicher. Jetzt bestand nur das Problem, von der linearen Adresse auf die Adresse im Speicher zu kommen. Die Speicher-Segmentierung bei DOS ist nicht trivial (ganz gut hier bei en.wikipedia.org erklärt), man muss die Adresse aber nur passend umrechnen:

312922 = 0x4C65A, 0x4C64A → 4C60:005A

Und schau an, wenn man im DOSBox-Debugger den Speicher mittels D 4C60:005A auf den passenden Bereich setzt, findet man die gesuchten Daten:

Und die gesuchten Daten werden wann geschrieben?

Nachdem ich ja bei der Abfrage vorher keinen Erfolg hatte, dachte ich mir ich suche einfach nach der Stelle, wo die Identifkationsbytes in den Speicher geschrieben werden. Das ist insofern einfacher, weil DOSBox es erlaubt, beim Schreiben in einen Speicherbereich zu Breaken. Da das beim Lesen mit DOSBox nicht geht, ist es viel schwieriger, den Vergleich zu finden, da hätte ich die Hilfe von SoftICE gebraucht.

Also das Spiel beendet, nochmal neu gestartet und vor dem ersten Ladevorgang einen Speicher-Breakpoint auf 4C60:005A gesetzt: BPM 4C60:005A und mal einen Spielstand geladen – Erfolg! DOSBox zeigt die Meldung “DEBUG: Memory breakpoint : 4C60:005A – 00 -> FF” und hält den Programmlauf an, wir sehen, nachdem wir den Code etwas hochgescrollt haben, folgendes, die Ausführung ist pausiert Zeile 3268:1429 (grün), denn die vorherige Zeile hat die gesuchte Speicherstelle verändert:


3268:1418 8E06D4A3 mov es,[A3D4] ds:[A3D4]=40C2
3268:141C 8B86BAFE mov ax,[bp-0146] ss:[ACAA]=FFFF
3268:1420 8B96BCFE mov dx,[bp-0144] ss:[ACAC]=0000
3268:1424 2689873ABA mov es:[bx-45C6],ax [illegal]
3268:1429 2689973CBA mov es:[bx-45C4],dx [illegal]

Wir sehen daraus folgendes:

  • AX enthält anscheinend die ersten zwei Bytes der Identifikation
  • DX enthäklt anscheinend die zweiten zwei Bytes. Das könnten wir noch überprüfen, indem wir sie anders setzen, soll uns aber jetzt nicht interessieren
  • Wir sind in einer Funktion, die die beiden Werte auf dem Stack übergeben bekommen hat, als [bp-0146] und [bp-0144]
  • Die beiden Werte werden dann in den Speicherbereich verschoben, an dem wir sie auch in MEMDUMP.BIN gefunden haben: es:[bx-45C6] und es:[bx-45C4]
  • Eigentalich egal, aber trotzdem: warum der Index für dieses Verschieben so komisch ist, weiß ich nicht, BX ist ja an sich 0, insofern ergibt BX-z für eine nichtnegative Zahl z ja eine Zahl kleiner 0 – DOSBox zeigt wohl deshalb die Adresse als “illegal” an. Wir wissen aber, dass es:[bx-45C6] zum Beispiel 4C60:005A ist. ES ist im aktuellen Fall, siehe Bild, 40C2, das heißt die Instruktion zeigt auf die Adresse 40C2:BA3A. Dort finden wir auch die Daten. Ich habs nicht ganz verstanden, ist aber eine Overflow-Geschichte, denn von der 0 die 45C6 abziehen lässt BX ins negative überfließen und am Ende kommt der richtige Offset dabei raus.

Auf zum Patch

Jetzt wissen wir erstmal genug, um uns schonmal einen Patch zu überlegen. Da gibt es jetzt verschiedene Möglichkeiten, am einfachsten erschien mir, die oben rot markierte Zeile zu verändern:


3268:141C 8B86BAFE mov ax,[bp-0146]

Anscheinend sind die ersten zwei Bytes, die nach AX geladen werden, in echten Spielständen immer ungleich 0, zumindest habe ich da nie zwei Null-Bytes gesehen. Warum also nicht einfach den Wert, der im Speicher zur Identifikation verwendet wird, auf 0 setzen? Ein bisschen die Suchmaschinen bemüht, denn meine Kenntnis über Opcodes ist aus dem Stegreif nicht sonderlich gut. Ergebnis war dann, dass ich den einen Befehl oben wie folgt ersetzt habe:


8B0000 mov ax,0000
90 nop

Anstatt den Wert vom Stack zu laden setze ich das Register also einfach auf 0 und da die Instruktion ein Byte kürzer ist als das Original fülle ich mit einem NOP auf. Gut, soweit wäre das erledigt, muss nur noch die BMMAIN.EXE gepatcht werden. Suchen wir mal nach 8B86BAFE, wir finden die Byte-Folge zwar drei Mal, aber nur die zweite hat den richtigen Kontext an Bytes außenrum. Die 8B86BAFE ändern wir in 8B000090, löschen alle bisherigen Breakpoints im DOSBox-Debugger, setzen einen auf auf die Instruktion vor der, die wir geändert haben, also BP 3268:1418, und starten die modifizierte BMMAIN.EXE.

Da ist unser modifierter Code

Et voilà, DOSBox bricht ab wie im Bild gezeigt, wir sehen den geänderten Code und beim Drücken von F5 läuft das Spiel weiter und lädt unseren Spielstand – beim erneuten Laden lädt sie ihn nochmal, und so weiter. Mission erfolgreich, wir können jetzt Spielstände mehrfach laden, was einerseits zum Testen hilft, andererseits auch ganz nett sein kann, wenn man mal unbedingt ein enges Spiel gewinnen will, dann lädt man halt so lange bis es klappt. :)

To do

Immer schön, wenn man mal was rausgefunden hat, aber wie so oft bleiben fast mehr Fragen als gelöst wurden. :)

  • Rausfinden, wie und wann die relevanten Bytes (29-32 im Spielstand) erzeugt werden – wird vielleicht so eine Art Zufallszahlengenerator sein.
  • Einen frisch gespeicherten Spielstand kann man nicht laden – wann werden beim Speichern die Bytes gesetzt? Sitzen die dann an der gleichen Stelle?*
  • Andere Baustelle, aber trotzdem: weiterspielen nach dem Speichern. Sollte der vorherige Punkt geklärt werden, dann kann man vielleicht so vorgehen, dass man einen frisch gespeicherten Spielstand neu lädt und dann weiterspielt?

* Ist nicht unlogisch, dass das nicht geht, denn wir haben ja nur verhindert, dass der Wert, der aus einem Spielstand geladen wird, in den Speicher geschrieben wird. Beim Speichern gibt es eine eigene Routine, die die Identifikationsbytes ebenfalls in den Speicher schreibt, das sollte sich aber durch beobachten des relevanten Speicherbereichs, den wir ja jetzt kennen, überprüfen lassen. Edit: Jepp, so siehts aus. Die Bytes werden an die gleiche Stelle geschrieben wie beim Laden, ändert man diese im Speicher ab, kann man den gerade gespeicherten Spielstand laden und dann weiter spielen. Mehr dazu bei Gelegenheit in einem weiteren Beitrag.

P.S.: Bezieht sich alles auf die deutsche BMP v1.03.
P.P.S.: Um den Patch selbst durchzuführen, BMMAIN.EXE laden und die 4 Bytes 8B86BAFE ab Offset 205436 in 8B000090 ändern.

Posted in BMP, DE-1.03, General information, Save games, Technical information | Tagged , , , , | Leave a comment

Keinen heutigen Spielstand laden

Aktuell bin ich daran, zu schauen wie bei BMP verhindert wird, dass man einen Spielstand zwei Mal lädt. Anscheinend wird das über die 4 Bytes nach dem Header gemacht, wobei bisher Byte 3 und 4 immer 0 sind; man sieht das daran, da nach den zwei Lesevorgängen für den Header nach dem dritten Lesevorgang abgebrochen wird, wenn die Datei schon mal geladen wurde. Was ich schon getestet habe, ist das wenn ich die Bytes, nachdem sie gelesen wurde, ändere, die Datei anstandslos nochmal geladen wird, der Dateiname hat damit also nichts zu tun. Ebenso kann eine Datei mit anderem Namen aber identischem Inhalt auch nicht (nochmal) geladen werden.

Der Code, der aus Dateien liest, ist relativ schwer zu durchschauen, mal sehen ob ich rausfinden kann, wo die 2/4 Bytes, die eine Datei identifizieren, gespeichert und dann mit den aktuell gelesenen verglichen werden, dann könnte ich diese Abfrage zumindest rauspatchen. Was ich danach noch gerne hätte, neben einem kompletten Reverse Engineering ;) , wäre die Möglichkeit, nach dem Abspeichern noch weiterzuspielen – bei BMP ist es nämlich so, dass man nach dem Abspeichern das Spiel neu starten muss, um weiterspielen zu können.

Posted in General information, Save games, Technical information | Tagged , , , | Leave a comment

Neue Version 0.7.2 der Konverter-Tools veröffentlicht

Die letzte Version der Konverter-Tools, 0.7.2, wurde vor kurzem veröffentlicht. Zum ersten Mal besteht jetzt die Möglichkeit, .VGA-Dateien zu erzeugen, die eine eingebaute Palette haben (für Eishockey Manager und Bundesliga Manager Hattrick).

Zusätzlich wird jetzt das erweiterte .VGA-Format von Bundesliga Manager Hattrick unterstützt, dass es ermöglicht, größere .VGA-Dateien zu erzeugen.

Um es für Anwender bequemer zu gestalten wurde eine .ZIP-Datei mit allen Tools in einem Ordner zum Download bereit gestellt. Dieses ZIP-Archiv kann hier heruntergeladen werden.

Posted in General information, Graphics, Tools, Veröffentlichungen | Tagged , , , | Leave a comment

Dann macht es bumm, ja und dann kracht’s

Obwohl im Blog schon länger nichts passiert ist war ich nicht untätig, auch wenn sich die greifbaren Fortschritte in Grenzen halten. Heute war ich aber mal wieder richtig fleißig und hab mir die Torszene vorgehalten.

Rausgefunden habe ich schon mal, dass die .TSZ-Dateien, die der Editor ausspuckt, nur “Blaupausen” für Torszenen sind, im Spiel selbst aber nicht verwendet werden können. Aus dem Editor muss man die Torszenen dann noch exportieren, um sie im Spiel anzeigen lassen zu können.

Im Wiki habe ich angefangen, die Struktur der .TSZ-Dateien zu dokumentieren, nähere Infos zu den Torszenen gibt es im dazugehörigen Artikel im Wiki.

Ich vermute, dass sich die Information im Frame im wesentlichen auf die Sprite-Datei und die Koordinaten der jeweiligen Animation beziehen, aber Details kenne ich noch nicht. Dass ich jemals den Editor nachbaue sehe ich so jetzt auch nicht, aber wenn die Dateien mal komplett entschlüsselt sind, sollte das für jemanden mit einem Händchen für sowas kein Problem sein.

Mehr Infos zu den Torszenen-Dateien und dem Editor bei Gelegenheit, wenn ich alle auf dem Schmierblock notierten Informationen auch sicher ins Wiki übertragen habe. :)

Posted in General information, Graphics | Tagged , , , , , | Leave a comment