Back in Action… oder so…

Der letzte Eintrag liegt 16 Monate zurück, der letzte SR:SNES:DRMM betreffende überhaupt schon 20 Monate…

Es mag vielleicht den/die eine(n) oder andere(n) enttäuschen aber ich existiere noch. Und obwohl 2019 sich mir gegenüber ziemlich derb gebärdet hat und 2020 global gesehen auch nicht wirklich das Gelbe vom Ei ist, darf ich sagen das es mir eigentlich verhältnismäßig gerade recht gut geht. Anyway, wenn ich jetzt jedesmal wenn ich Pausen einlege einen Rechtfertigungsbeitrag verfasse, kommen wir hier nie zu was.

Ich habe Shadowrun:Hong Kong jetzt tatsächlich noch eine Chance gegeben und bereits nach zwei Sessions bereits wieder abertausende Ideen für SR:SNES:DRMM.

Grund genug also den SpriteXtraktor wieder auszugraben und weiterzu… uhm. Mich langsam wieder in den Code einlesen, versuchen herauszufinden was ich da eigentlich gerade getan habe und wo genau ich warum stehen geblieben bin.

'==============================
' SR:SNES:DRMM SpriteXtractor
'------------------------------
'SXT_day5.bas    Fr.28/12/2018
'SXT_day4.bas    So.25/12/2018
'SXT_day3.bas    So.24/12/2018
'SXT_day2.bas    So.23/12/2018
'SXT_day1.bas    So.16/12/2018
'------------------------------
'All (d) by -=[d.s.R.=- except:
'"Directory Enviroment"-Demo by Dav -> "http://qb64.org/wiki/Windows_Libraries#Directory_Environment"
'"Windows User"-Demo by Michael Calkins -> "http://qb64.org/wiki/Windows_Libraries#Windows_User"
'"SENDKEY.BAS" by Dav -> "http://qb64.org/wiki/Windows_Libraries#Send_Keys"
'"Mouse Area"-Demo -> "http://qb64.org/wiki/Windows_Libraries#Mouse_Area"
'"_SCREENIMAGE"-Demo -> "https://qb64.org/wiki/SCREENIMAGE"
'==============================

Tjo… da habe ich ja jetzt gar nicht mal so viel Zeit reingesteckt wie ich angenommen habe … egal.

VerzeichnisseEruieren
INFolderAuswerten
vSNESanpassen
vSNESoeffnen

((fyi der Übersicht wegen füge ich nicht mehr den kompletten Quelltext sondern nur die relevanten Auszüge ein.))

VerzeichnisseEruiern ist soweit mal klar und hat „lustigerweise“ auf meinem frisch aufgesetzten Notebook zu einem Fehler geführt da der Screenshot-Folder noch nicht existiert hat. Hier sollte ich eventuell eine „Order erstellen„-Option einfügen….

INFolderAuswerten. Na schau dir was an. In einem der letzten SR:SNES:DRMM-Beitrag labber ich noch was von wegen HotFoldern und hier ist er tatsächlich schon implementiert. Der Name bedeutet nämlich IN-Folder-auswerten und nicht INFO-Folder-auswerten. Und in eben diesen IN-Folder kommen alle auszuwertenden .zst-Dateien rein.

vSNESanpassen ist auch klar. Hier werden in der vSNES.ini alle Pfadangaben sowie alle notwendigen Größenangaben soweit angepasst das der SpriteXtraktor damit funktioniert und ohne weiteres Zutun des Benutzers alleine alle Daten im Hotfolder abarbeitet.

vSNESoeffnen spricht auch für sich selbst.

ExtrahierenVorbereiten
ROMoeffnen
SaveStateOeffnen 4

AllesExtrahieren

AllesBeenden
END

ExtrahierenVorbereiten. Okay, ab hier beginnt es kompliziert zu werden: Zuerst wird per Alt+S der Reiter „screen“ ausgewählt. Dann wird der Cursor über „8-bit“ plaziert und einmal geklickt. Als letztes wird mit CTRL+1 BG1 ausgewählt.

((Die angewählten Punkte habe ich der Übersicht wegen Gelb hervorgehoben.))

ROMoeffnen ist notwenig da vSNES sonst beim Laden jedes Savestats rumjammert.

SaveStateOeffnen 4 ist auch klar… killt mich aber hier @wrk wo ich ganz andere Ordnerstrukturen habe. :-/

Das bedeutet ich sollte herausfinden wie ich Ortsunabhängig den notwendigen Pfad in diese Auswahl bekomme. Wahrscheinlich in der vSNES.ini.

to be continued…

Acht mal acht

Eine Sache die ich beim Herumspielen entdeckt habe und die mir in Folge Kopfzerbrechen bereitet hat, war das manche Scenes größer als der Bildschirm (512x488px) sind.

Ein gutes (und afaik das erste) Beispiel hierfür sind die Einzelteile des Parallax-Scrolling-Skyline-Intros. zB. ist der Skyline-Layer.

Kommando zurück. Wie ich jetzt beim Auslesen der Werte entdeckt habe, ist der fertige Screen 512x488px und der Skyline-Layer 512x256px. Und das obwohl im Screen nur ein Ausschnitt des Layers zu sehen ist. Wie das geht? Durch Zoom aka Pixelwiederholung. Ich habe im Screen keinen einzigen einzelnen Pixel gefunden, alle waren 2×2 Pixel groß.

Was bedeutet das jetzt für mich? Nichts. Nach wie vor werden rechts und unten im Scene_View-Fenster Zeilen ausgeblendet wenn man die 1:1 Ansicht wählt. Da ich in der vsnes.ini keinen Eintrag über die Zoomstufe finde, und ich mich nicht darauf verlassen möchte das tatsächlich niemals ein Layer einen größeren Wert als 512 aufweißt, bleibe ich meiner ursprünglichen Idee mit einer verkleinerten Zoomstufe zu arbeiten treu.

Warum? Im ersten Schritt werden ja alle Layer exportiert. Im zweiten werden sie miteinander verglichen um herauszufinden in welchen es tatsächlich Änderungen gab (zB. wird sich der Skyline-Layer die gesamte Sequenz lang nicht ändern.). Dann werden die Savestates die diese Undikat-Layer enthalten wieder geladen und die Sprites exportiert. Da geschickte Grafiker Sprites so designed haben, daß ein und das selbe mehrmals verwendet werden kann und ich schon im Vorfeld verhindern möchte das zumindest pro Savestate nicht x-mal das selbe Sprite gespeichert wird, mache ich mir ein weiteres vSNES-Feature zu eigen.

Bewegt man den Mauscursor im Reiter „layers“ über das Bild, werden rechts unter „tile info“ Details über das sich aktuell unter dem Cursor befindliche Sprite angezeigt. zB. auch „tile index“. Wenn ich es schaffe diese Infos auszulesen (Pseudo-OCR incoming…) kann ich Duplikate vermeiden.

Das war jetzt viel Bla, von dem ich afair einiges schonmal erwähnt hatte, aber dafür sollte das Gesamtbild jetzt auch außerhalb meines Kopfes klar(er) sein. *g*

Womit ich heute Teile des Vormittages verschwendet habe, war die verschiedenen Zoomstufen in die entsprechenden Quadrate zu zerlegen. Bei 1:1 war es logisch:

Bei 1:2 ebenfalls. Ein Quadrat hat 4×4 Pixel. Geht man davon aus das die Skyline 512x256px groß ist, kann man in dieser Zoomstufe 1024x512px große Layer zerlegen.

Eigentlich hatte ich photoshopgeschädigt mit kreativen Zoomstufen wie 1:3 oder 1:2,75 gerechnet, aber die nächste Stufe die vSNES anbiete ist 1:4. Hier entspricht ein Quadrat (Trommelwirbel bitte) 2×2 Pixel und es sind 2048x1024px große Layer möglich.

Es gäbe noch die Stufe 1:8 bei der jeder Pixel einem Sprite entspricht und Layer bis 4096x2048px auswertbar sind aber ich behaupte mal das ich diese Stufe nie benötigen werde. (Auch wenn es verführerisch wäre den Curosr immer nur einen Pixel weiter zu verschieben und fix davon ausgehen zu können das der Layer immer komplett angezeigt wird. Hm…)

Und damit wäre das Kapitel Zoomstufen und alle Sorgen die ich mir diesbezüglich gemacht habe abgehakt. Als nächstes sollte ich mich entweder mit dem erkennen der ausgeblendeten bg-Reiter oder meinem Pseudo-OCR auseinander setzen…

Da ich die ausgeblendeten Reiter allerdings mit hoher Wahrscheinlichkeit über die Kontrolle einzelner Pixel (Schwarz = aktiv, Dunkelgrau = inaktiv) lösen können werde, wird es wohl das Pseudo-OCR werden *g*

[~1h später ] Das war einfacher als erwartet.

Glücklicherweise hat creaothceann eine 5x9px Schrift verwendet deren Position sich (zumindest bei den Ziffern, mehr habe ich mir nicht angesehen..) nicht ändert/anpasst. Die Felder in welchen sich die auszulesenden Pixel befinden ändern sich also nicht.

Ich hatte die Hoffnung das ich in jeder Ziffer einen Pixel finden der sich nur in dieser Ziffer befindet, habe aber keinen gefunden. Dann setzte ich darauf das die Summer der ausgefüllten Pixel in einer Zeile/Spalte Rückschlüße zuliese aber dem war auch nicht so, auch über die Gesamtsumme aller Pixel zu gehen wurde durch „6“ und „9“ zerstört. Sollte ich also nicht noch den ultimativen Geistesblitz haben, muss ich tatsächlich mindestens die Hälfte aller Pixel … warte mal…

0 = X…X   1 = ..X..   2 = …X.   3 = ..XX.   4 = .X.X. 5 = X…X  6 = XXXX.   7 = ..X..  8 = .XXX.  9 = .XXXX

Youp, die mittlere Zeile ist bei jeder Ziffer einzigartig. Damit habe ich 45 zu kontrollierende Pixel auf 5 reduziert. \o/ Wohoo!

_SCREENCLICK

Gestern habe ich beim gelangweilten stöbern im QB64.org-Forum ein Projekt gefunden das (zumindest in der Anfangsphase, weiter habe ich nicht gelesen) wie der SpriteXtractor nur vorgegebene Maus- und Tastatureingaben abspult. Dazu verwendet es den Befehl _SCREENCLICK.

Ich wußte doch das so ein Befehl exisitert.

Mit _SCREENPRINT hätte ich in dem selben Wikieintrag auch die Tatstatureingabe entdeckt.
http://qb64.org/wiki/SCREENCLICK
http://qb64.org/wiki/SCREENPRINT

THEORETISCH müßte ich jetzt die von mir angewandte umständliche Version aus meinem Code gegen die neu gefundenen bereits in QB64 implementierten Versionen austauschen. So wegen potentiellen Geschwindigkeitsgewinn, Umständlichkeit und bla.

Da meine Probleme aber ganz wo anders liegen, lasse ich das aber jetzt so. Es funktioniert und ist in der verbleibenden Anwendung nicht komplizierter als die interne Version.

Der Eintrag zu _SCREENPRINT hat indes einen interessantes Beispiel:
DEFLNG A-Z
SCREEN _NEWIMAGE(640, 480, 32)
PRINT "OPENing and MAXIMIZING Notepad in 5 seconds..."; : _DELAY 5
SHELL _DONTWAIT "START /MAX NotePad.exe" 'opens Notepad file "untitled.txt"
'detect notepad open and maximized 'condition: 80% or more of the screen is white
DO 'read the desktop screen image for maximized window
s = _SCREENIMAGE
_SOURCE s
z = 0
FOR y = 0 TO _HEIGHT(s) - 1 'scan for large white area
FOR x = 0 TO _WIDTH(s) - 1
c = POINT(x, y)
IF c = _RGB32(255, 255, 255) THEN z = z + 1
NEXT
NEXT
IF z / (_HEIGHT(s) * _WIDTH(s)) > 0.8 THEN EXIT DO 'when 80% of screen is white
_FREEIMAGE s 'free desktop image
_LIMIT 1 'scans 1 loop per second
PRINT ".";
LOOP
PRINT
PRINT "NOTEPAD detected as OPEN and MAXIMIZED"


_SCREENPRINT "HELLO WORLD"
SLEEP 2
_SCREENPRINT CHR$(8) + CHR$(8) + CHR$(8) + CHR$(8) + CHR$(8) 'backspace 5 characters
SLEEP 3
_SCREENPRINT "QB64!"
SLEEP 2
_SCREENPRINT CHR$(1) 'CTRL + A select all
SLEEP 2
_SCREENPRINT CHR$(3) 'CTRL + C copy to clipboard
SLEEP 2
PRINT _CLIPBOARD$
_CLIPBOARD$ = "QB64 ROCKS!"
SLEEP 2
_SCREENPRINT CHR$(22) 'CTRL + V paste from clipboard
END

Der Teil der mich so besonders anspricht ist der in dem kontrolliert wird ob 80% des Bildschirms weiß sind. In meinem SpriteXtractor prüfe ich ja nur einzelne Pixel, aber hier wird ein ganzer Bereich herangenommen.

Der Code geht Höhe x Breite alle Pixel ab und zählt alle deren _RGB32-Wert weiß ist zusammen. Dann wird diese Summe (z) durch die Gesamtanzahl der geprüften Pixel (=Höhe*Breite) dividiert und sollte das Ergebnis größer als 0.80 sein gilt dies als Indiz das Notepad geöffnet ist.

Klar ist das es nur funktioniert wenn der Textbereich von Notepad vom Start weg tatsächlich mehr als 80% des Bildschirmes einnimmt. Mehrere Monitore oder manuell skalierte Fenster killen diesen Ansatz schnell.

Da ich beim vSNES afaik alle Fensterpositionen & größen in der .ini definiert habe sollte aktuell für das „erkennen“ ausgeblendeter Buttons eigentlich die Abfrage einzelner Pixel genügen, allerdings habe ich beim testen auch „leere“ Layer entdeckt. Dafür könnte ich die diese Routine adaptieren.

Des Rätsels Lösung…

In aller Kürze: Ich habe herausgefunden was es mit der 1536*864pixel G’schicht auf sich hat! Windows ist schuld. Um genau zu sein die vorgeschlagene Skalierung auf 125%.

Kaum habe ich wieder auf 100% umgestellt, hat wieder alles einwandfrei funktioniert. QB64 kann den vollen Bildschirm kopieren und alle Koordinatenangaben stimmen wieder überei.

Einzig die Schrift ist jetzt wieder fuzzelig und zwingt mich meine Brille aufzusetzen.

Vorerst ist die Welt also wieder gerettet. Problematisch wird’s erst wenn ein QB64-Programm auf einem 125%-skaliertem Desktop in den Breich geschoben wird der sich außerhalb des erfassbaren Bereichs befindet. Aber das ist ein QB64-Problem. (Das ich eventuell mal im Forum zur Sprache bringen sollte…)

Habe btw. Robert von meinen Ideen und Vorstellungen für SR:SNES:DRMM erzählt und nur positives Feedback bekommen. Er teilte vor allem meine Bedenken was die Verstaubtheit des Kampfsystems betrifft und meine Überlegungen von wegen Mouselock & Co. (Darauf gehe ich bei Zeiten mal näher ein…) gefielen ihm recht gut.

Ansonsten kann ich nur sagen das ich gerade mörderisch paranoid bin da im SceneView-Fenster bei unterschiedlichen Savestats unterschiedliche Buttons ausgeblendet sind. Ich werde also für jeden Schritt eine Kontrolle einbauen müssen ob dieser Schritt überhaupt ausführbar ist.

Außerdem bin ich draufgekommen das die Scenes unterschiedlich groß sein können. Das wird Auswirkungen auf das Exportieren der einzelnen Tiles haben da sich die Abstände zwischen den Mauskoordinaten ändern und waaahrg.

Aktuell hoffe ich das die Scene automatisch im Fenster skaliert wird und ich anhand der Größe der exportierten Scene auf die Abstände rückschließen kann.


Frohe Y-nachten…

Gestern habe ich eine halbe Ewigkeit daran herumgebastelt wie ich kontrollieren könnte ob das Scene-View und Memory-View Fenster aktiviert sind nur um dann zu entdecken das sich in der vSNES.ini die Zeile WindowList=Form_Scene,Form_Memory befindet.

Während dieser Versuche habe ich entdeckt das vSNES manchmal länger lädt als die Pausen die ich dafür eingeplant habe andauern. (Warum auch immer, ein System dahinter konnte ich nicht finden.). Also habe ich die oben erwähnte Kontrolle wieder aus der Versenkung geholt und adaptiert.

Im Prinzip wird der Teil des Bildschirms in dem zu diesem Zeitpunkt der noch blauen Scene-View Fensters sein sollte in den Speicher gelegt und dann auf das Vorhandensein der blauen Farbe kontrolliert.

In der Praxis habe ich entdecken dürfen das QB64 nicht nur die 1536*864px-G’schicht sondern auch ein prinzipielles Problem mit der Bildschirmausgabe hat. Ich kann den Vergrößerungsfaktor (noch) nicht genau bestimmen, aber im Ausschnitt links ist die 1 um drei Pixel und die 2 und vier Pixel vergrößert dargestellt!?!

(Im Beispiel links liegt Notepad mit den Ziffern im Hintergrund, darüber liegt der von QB64 erstellte Screenshot dieser Spalte. Rechts daneben ist kA mehr warum ein Teil vom QB64-Fenster.)

Da sich alle Koordinaten weder auf diesen Maßstab noch den der originalen Bildschirmauflösung beziehen, ist es nach vor nur per Trial & Error möglich auf funktionierende Werte zu kommen. Erschwerend kommt auch noch dazu das auch die per POINT ausgelesenen RGB-Daten nicht den in Photoshop angezeigten entsprechen. Und soll ich auch noch erwähnen das _putimage; gerade auch nicht wirklich so funktioniert wie ich gewohnt bin? Nämlich gar nicht.

Ich hoffe ich finde demnächst heraus wo mein Fehler liegt, denn irgendwie möchte ich nicht wahrhaben das QB64 dermaßen kaputt ist.

Heute,  so als eine Art Weihnachtswunder, habe ich es endlich geschafft die Kontrolle umzusetzen. Eigentlich sollte ich jetzt weiter am exportieren basteln… aber ich habe keine Lust. Vielleicht später…

Update: Habe 137 Backup-vSNES.ini’s gelöscht und beschlossen das dies automatisch nach dem Beenden passieren sollte. Gesagt getan, vor Ende des Programms wird jetzt die ursprünglich vorhandene vSNES.ini wiederhergestellt. (alias zurück umbenannt). Weil ich die temporäre RGB-Wert-Anzeige noch nicht deaktiviert hatte, habe ich mich in diesem Zug auch gleich mit der „Status:“-Angabe gespielt.

Von einem der auszog um Sprites zu rippen…

Nachdem ich heute @wrk ein bisschen Luft habe, versuche ich einen Teil meines seit Beginn des Projektes SR:SNES:DRMM gesammelten Wissens zu tei… niederzuschreiben damit ich [Schwarzsehermodus:ON] wenn ich nachdem ich das Projekt demnächst auf Eis gelegt habe und Jahre später wieder ausgrabe mir nicht wieder alles aus den Fingern saugen muss.[Schwarzsehermodus:OFF]

And the big black dog bit me again, wohoo…

Egal. So ziemliche alle Beiträge zum Thema „SNES Sprites rippen“ berufen sich auf die mit den Emulatoren einzeln einblendbaren Layer:

Hier die Version in der die verschiedenen Layer einzeln ausgeblendet werden:



Und hier die etwas sinnvollere in der alle anderen Layer ausgeblendet sind:



Laut zSNES gibt es also vier Backround- und einen Sprite-Layer. Das stimmt mit der auf Wikipedia befindelichen Info überein:

Auflösung:224 (NTSC) bzw. 239 (PAL) Bildzeilen im Progressive-Modus, 448 bzw. 478 Zeilen im Interlaced-Modus. 256 Pixel pro Zeile im Standardmodus, 512 im „High-Res“ Modus.
Farbtiefe:15-Bit-Farbpalette, davon theoretisch alle Farben gleichzeitig darstellbar (unter Ausnutzung des Color Add/Subtract-Modus). Ansonsten bis zu 256 Farben aus 4096 (Mode 7).
Hintergründe:Bis zu 4 unabhängig scrollbare Tilemap-Grafikebenen (Playfields), Colour Add/Subtract Mode (zum Kombinieren der Grafikebenen für Transparenzeffekte zwischen den einzelnen Ebenen und Sprites), 8 Grafikmodi mit unterschiedlichen Farbtiefen und Anzahl der Ebenen, Mode 7 erlaubt das dreidimensionale Zoomen, Drehen und Verzerren einer Grafikebene. Das Kippen der Grafik war von Hause aus nicht möglich, wurde aber durch helfende DSP-Chips in manchen Modulen gewährt.
Sprites:128 Hardware-Sprites, 16 Farben pro Sprite (eine davon transparent), Spritegrößen von 8 × 8 bis 64 × 64 Pixel, max. 32 Sprites pro Zeile, max. 34 Sprite-Tiles (8 × 8 Pixel) pro Zeile.
Die meisten Beiträge schlagen vor das Spiel zu pausieren, alles bis auf die zu extrahierenden Layer auszublenden, einen Screenshot zu erstellen und dann im Bildbearbeitungsprogramm der Wahl loszulegen. Zielt man die einzelnen Phasen einer Animation ab, wünscht einem die Mehrheit ironisch viel Spaß während die Handvoll ernstzunehmender Autoren vorschlägt die gewünschte Animation mit ausgeblendeten Layern als Video aufzuzeichnen, die Frames zu extrahieren, jene mit den einzelnen Phasen auszusortieren und diese dann wie gewohnt im Bildbearbeitungsprogramm der Wahl zu verarbeiten.

Das geht einen Millimeter einfacher. zSNES verfügt nämlich über die Fähigkeit pro Tastendruck nur ein Frame weiterzurechnen. Die Betonung liegt auf weiterrechnen, denn es geht um die auf den SNES-Takt bezogen erstellten Frames, nicht jene in denen sich etwas am Bildschirm verändert. Man muß also immer noch x mal weiter drücken bis man das nächste Sprite angezeigt bekommt, aber das geht immer noch um Jahrhunderte schneller als die Version mit dem Video.

Die Vorarbeit hierzu liegt in den „SPEED OPTIONS“ (CONFIG„->“SPEED). Hier befindet sich unter „PAUSE GAME“ der standardmäßig inaktive Punkt „INCR FRAME„. Hier einmal reinklicken und die Wunschtaste (Bei mir hat sich ohne triftigen Grund „Ü“ eingebürgert…) eintragen.



Nicht vergessen unter „MISC„->“MISC KEYS“ auch eine Taste für die zSNES-eigenen „SNAPSHOTS“ (alias Screenshots) anzugeben. 😉



Dann geht’s auch schon los. Vor dem Animationsbeginn „PAUSE GAME“ (im Normalfall „P“) betätigen, dann solange die bei „INCR FRAME“ eingetragene Taste drücken bis das erste Wunschsprite angezeigt wird und mit der „SNAPSHOT„-Taste aufnehmen. Dann mit „INCR FRAME“ solange weiter bis das nächste angezeigt wird, „SNAPSHOT„, und so weiter und so weiter.

Man kann btw. während dem Drücken von „INCR FRAME“ auch weitere Befehle eingeben. Wenn man zB. eine Richtungstaste drückt um die Spielfigur zu bewegen so wird diese Aktion von zSNES registriert und angewandt. Man könnte also, genug Geduld vorausgesetzt, während dem „INCR FRAME„-„SNAPSHOT„-Prozedere das Spiel auch tatsächlich spielen.

Soviel zum Thema Screenshots.

An die Grenzen von Screenshots im Allgemeinen stößt der geneigte Ripper sobald sich Sprites überlappen (Da es nur einen Sprite-Layer gibt ist die Chance das es dazu kommt extrem hoch.) oder Tilesets in Angriff genommen werden. Je besser der urprüngliche Künstler sein Handwerk verstanden hat desto aufwendiger wird es die Pixel korrekt zuzuordnen.

In der Regel wird einem erklärt das SNES-Sprites und Tiles zwischen 16×16 und 32×32 Pixel groß sind. Weil das bei den meisten klassischen JRPGs hervorragend passt, kann man (in diesem Fall ich) auch davon ausgehen das es sich bei isometrischen Spielen wie Shadowrun genauso verhält.



Tut es aber nicht. Vor allem der fixe Trugschluss das Tiles auf 16×16 Pixel dimensioniert sind zeigt sich anhand der links übrig bleibenden einzelnen Zeile. Dank vSNES weiß ich mittlerweile das die Wahrheit bei ganz normalen rechtwinkeligen 8×8 Pixel-Quadraten liegt.



Der ganze Bildschirm ist (zumindest bei Shadowrun) in 8×8 Pixel-Quadrate aufgeteilt. Jedes Sprite, jedes Tile, jede Font, etc. ist in 8×8 Pixel Quadrate im Speicher hinterlegt. vSNES arbeitet mit den .zst-Dateien, den Savestates von zSNES. Savestates beinhalten alles was sich in dem Moment als es erstellt wurde im Speicher befand.



Das einige Farben nicht passen liegt daran das jedem Sprite eine Palette aus 16 Farben (eine davon transparent, siehe Wikipediaausschnitt) zugeordnet ist und der MemViewer immer nur eine davon anzeigt. Warum? Keine Ahnung, ist aber für meine Zwecke auch egal. Um ehrlich zu sein habe ich den MemViewer mittlerweile gar nicht mehr aktiviert, denn der SceneViewer kann in der „layers“-Anzeige nämlich diese 8×8-Pixel Quadrate einzeln anzeigen.



In dem Screenshot sieht man leider nicht das der Mauscursor sich gerade über Jakes Oberkörper befindet und dieser deswegen im „tile info“-Feld vergrößert dargestellt wird. Wirklich deutlich wird die Mächtigkeit des Viewers aber erst wenn Sprites überlappt dargestellt werden, wie zB die Tür über dem einem Morgue-Angestellten. Fährt man mit dem Cursor über das Maxerl, wird es im „tile info“-Feld vollständig, ohne Türe dargestellt.

Ich habe keine Ahnung wie es sich bei scrollenden Darstellungen verhält, so weit bin ich mit dem Auseinandernehmen (und analysieren was da gerade abgeht…) noch nicht. Die Vermutung das sich das Bild dennoch im Speicher aus 8×8 Pixel Quadraten aufbaut und am Bildschirm versetzt dargestellt wird liegt aber verdammt nahe. Zumindest funktioniert das Skyline-Hochhaus-Intro von Shadowrun auf diese Weise.



Der Tag nähert sich dem Ende und ich kann sagen das ich hier das Grundprinzip meines aktuellen Versuches die Sprites zu extrahieren meiner Meinung nach gut erklärt habe. Warum das aber noch nicht das Ende der Fahnenstange ist und warum ich extra meinen „SpriteXtractor“ (Ja, ich mag den Angebernamen immer noch *g*) bastle um die dabei anfallende Arbeit wenigsten ein bisschen auslagern zu können erkläre ich demnächst.

Sieht gar nicht so tot aus.

Hat jetzt ein paar Tage gedauert bis ich wieder motiviert war… bis ich genug motiviert war um um zu starten.

Während ich also meinen Hintern in die Höhe hievte blieb ich wieder bei der 1536*864px-G’schicht picken. Bei  meinen Recherchen stellte sich heraus das SPI_GETWORKAREA verwendet wird und „Work Area“ ein dehnbarer Begriff ist:

Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen not obscured by the system taskbar or by application desktop toolbars. The pvParam parameter must point to a RECT structure that receives the coordinates of the work area, expressed in physical pixel size. Any DPI virtualization mode of the caller has no effect on this output. To get the work area of a monitor other than the primary display monitor, call the GetMonitorInfo function.
(Source)

Nachdem ich mich eine Runde gefreut und vergeblich eine Alternative gesucht habe, fiel mir ein das _SCREENIMAGE auch schon herumzickt und ich keine Ahnung habe wie ich das repariere/umgehe. Außerdem war da ja noch die Windows+PrtScrn-Kombination.

(Trotzdem: Die geschwundene Breite konnte ich auf das Startmenü rückführen, die geschwundene Höhe auf die Vorschaufenster der minimierten Tasks. Sollte ich Lust haben könnte ich mal versuchen die Aero-Oberfläche zu deaktivieren… irgendwann mal. 😉)

Zurück in QB64 habe ich mich mit der Verzeichnisstruktur beschäftigt. Der SpriteXtractor (Was für ein Angebername… *g*) kontrolliert beim Start ob es einen Eigene Bilder\Screenshot-Folder gibt (Inkl. kernel32-bezogenem auslesen des Pfades…), wo sich die gerade ausgeführte .exe befindet, wie es mit den IN/OUT-Foldern steht (Falls keine existieren werden sie erstellt.) und ob vSNES im gleichnamigen Unterordner geparkt ist.

Dann habe ich eine Subroutine eingefügt die eine Liste aller .zst im IN-Folder erstellt. (Ziemlich umständlich umgesetzt aber effektiv…)

Aus einer Laune heraus habe ich mich mit der vSNES.ini gespielt und dabei entdeckt das sich in dieser die Fensterpositionen, die letzten Pfäde und sonst noch viel Sinnvolles befindet. Ich kann mir als die angedachte Positions-Kontrolle dadurch ersparen.

Der nächste Schritt bestand also daraus diese vSNES.ini zu bearbeiten. Eines Tages lerne ich ganz sicher wie ich Textdateien editiere und nicht jedes mal neu erstellen muss.. egal. Kurzfassung, ja es funktioniert. (Genauso umständlich umgesezt wie die .zst-Liste… aber hey, solange es funktioniert.)

Als nächstes stand dann schon vSNES an. Dank ich die .zst fortlaufend umbenannt habe (0001_Shadowrun.zst, 0002_Shadowrun.zst, 0003_Shadowrun.zst, …) ist es nicht notwendig das Rom zu laden da dies genauso heißen müsste wie die .zst und ich also für jedes .zst eine Kopie des Roms haben müsste. Bis jetzt habe ich es afair aber noch nie benötigt. Sollte ich herausfinden das es doch notwendig ist, kann ich mich immer noch mit dem Thema befassen. (Und wenn es nur eine Routine ist die Kopien erstellt und umbenennt…)

Das Laden des .zst funktioniert dank der Tastatursteuerung des Programms einwandfrei. (Vor allem das man der Listenummer des aktuellen Files entsprechend „Cursor Runter“ drücken muss ist eine unheimliche Erleichterung.). Das Scene-Fenster öffnen und die relevanten Reiter aktivieren ebenfalls.

Jetzt käme das Speichern der einzelnen Layer an die Reihe. Dazu brauche ich aber noch ein sinnvolles Ablagesystem und muss kontrollieren ob die ausgeblendeten Reiter ausgeblendet bleiben. Sollten sie das nicht tun, muss ich einen Plan entwickeln wie ich herausfinde ob ein Reiter ausgeblendet ist.

Das die für SetCursorPos notwendigen x/y-Koordinaten nicht mit denen die ich im Photoshop aus dem Screenshot auslese übereinstimmen wundert/wurmt mich btw. auch noch.

Pläne

Halbzeit meines Urlaubes und ich habe keine einzigen Tag lang gezockt sondern mir nur den Kopf über SR:SNES:DRMM (und natürlich auch das nahende Weihnachten) zerbrochen. Naja, heute habe ich außerdem keine einzige Zeile Code geschrieben und gedenke es auch dabei zu belassen, denn Recherchen und Kopfarbeit habe ich sehr wohl geleistet.

Zu der 1536*864px-G’schicht finde ich weder Lösungen noch Indizien das es bei jemand Anderem auch vorkommt. Auch habe ich den QB64 build gewechselt aber das Problem bleibt weiterhin bestehen. Die einzige Lösung wäre (Außer, wie mir gerade einfällt, dem Test auf einem anderen System… hmmm.) das ich im Forum um Hilfe frage aber… die für mich und die vorgesehenen Zwecke relevanten Fenster befinden sich alle noch im kopierten Ausschnitt.

Nach langem Herumgehadere mit mir bleibe ich dem >jetzt für mich<-Ansatz (siehe „Kontrolle erlangen„, Mo.10/12/18) treu und beschliesse das ich jetzt keine elfundrölfzig Jahre herumbastle bis ich _SCREENIMAGE korrekt zu laufen bekomme sondern mit dem weitermache was ich habe.

UPDATE:
Während ich diesen Text geschrieben habe, habe ich nebenbei nach einer .dll-basierenden Lösung gesucht. Dabei bin ich über folgende, mir bis dato unbekannte, Information gestolpert:

„Windows 8 and Windows 10 users have an additional trick that is a little faster. Tap the Windows key + PrtScn and your display will „blink“ as if the shutter of a camera just closed and opened. That indicates that a screenshot has been taken. This time, however, you don’t have to paste it into another program. Instead, the shot is automatically saved in Pictures > Screenshots.“
(Quelle: www.lifewire.com)

Uhm. Ob der Screenshot jetzt im Programm-Folder oder im Screenshot-Folder abgelegt wird, ist egal. Dateien verschieben ist wahrlich keine Hexerei.

Apropos Folder. Eine meiner heute ausgearbeiteten Ideen dreht sich um eben diese:
Bei einer Schulung in der Arbyte wurde mir der HOTFOLDER-Gebrauch näher gebracht, und die Idee mit dem IN bzw OUT Folder kla… adaptiere ich gerne für mein Projekt. Alle auszuarbeiteten .zst kommen in den IN-Folder, das Programm erstellt dann anhand dieser die Liste nach der sie verarbeitet werden. Die finalen Dateien landen dann im OUT-Folder in einem eignem mit Datum und Uhrzeit versehenen Folder. (Inkl. der .zst-Dateien. Das verarbeitete Quelldateien „verschwinden“ stößt mir aufgrund regelmäßig sauer auf. Das liegt aber daran das je nach Workplace, Uhrzeit und Wasserstand der Donau (alias keinem für mich nachvollziehbarem System) Dateien beim drag&drop verschoben oder nur kopiert werden…)

Wichtig wäre btw. auch noch eine Justierungsmöglichkeit.
Es war vor einer halben Ewigkeit als ich am laufenden Band Screenshots für ein geschriebenes Let’s’Play (Final Fantasy I, das fertig zu stellen steht auch noch irgendwo (gaaanz weit unten) auf meiner ToDo-List…) erstellte nur um regelmäßig die Hilfslinien im Photoshop an die Fensterposition anzupassen bis ich eines Tages entdeckte das sich die Fenstergröße verändert hatte. Dies betraf „auch“ den Zoomfaktor mit der die 256×224 Pixel dargestellt wurden. Beim Versuch die ursprüngliche Fenstergröße wiederherzustellen wurde mir bewußt das diese nie einer normalen (x2, x3, x4,…) Zoomstufe entsprach sondern lange vor dem Start des Let’s’Play’s frei mit der Hand gezogen skaliert worden war…

Wo war ich beziehungsweise wo wollte ich hin? Zur Justierungsmöglichkeit.

Damals habe ich auf die harte Tour gelernt das man sich auf keinerlei Werte oder Positionen von Fenstern verlassen darf. Da dieses Extrahierungsprogramm auf fixe Koordinaten setzt wäre es imho grob fahrlässig darauf zu setzen das diese sich nicht verändern.

Meine Idee wäre es die Position der Menüleiste, die des Ansichtsfensters sowie des Speicherdialogs zu kontrollieren. Sollten diese nicht korrekt sein, wird ein Screenshot erstellt und der Benutzer aufgefordert bestimmte Orientierungspunkte neu zu setzen. Da die meisten Schritte per Shortcuts ausführbar sind, sind glaube ich sogar noch weniger Orientierungspunkte notwenig als ich zu Beginn dieses Absatzes annahm. Genaueres werde ich sehen wenn es dann soweit ist. Wichtig war mir nur diesen Brainfart zu verewigen.

Wozu PostIts oder Notizzettel? Ich habe einen Blog… xD

Auflösung

Habe den ganzen Tag damit ver…bracht mein Log vom .txt ins WordPress zu übertragen. Unglaublich wie viel Layout in einfaches ASCII hineininterpretiert werden kann und wie viele Fehler dabei gemacht werden können.

Genug gesudert, zurück zum Programm.
Der Beispielcode in _SCREENIMAGE ist folgender:

desktop& = _SCREENIMAGE
MaxScreenX& = _WIDTH(desktop&)
MaxScreenY& = _HEIGHT(desktop&)
_FREEIMAGE desktop& 'free image after measuring screen(it is not displayed)
SCREEN _NEWIMAGE(MaxScreenX&, MaxScreenY&, 256) 'program window is sized to fit
_SCREENMOVE _MIDDLE

In der ersten Zeile wird von dem Desktop ein Screenshot erstellt und in desktop& gespeichert. Aus diesem dektop& werden Breite (MaxScreenX&) und Höhe (MaxScreenY&) ausgelesen. Daraufhin wird desktop& wieder geleert und das QB64-Programmfenster auf die Desktopgröße vergrößert. In der letzten Zeile wird das QB64-Programmfenster zentriert.
Das totale Gegenteil von Borderless Fullscreen…

Würde _SCREENIMAGE des Beispiels wegen nicht verwendet werden müssen, würde man die Desktopdimensionen über die QB64-Befehle _DESKTOPWIDTH und _DESKTOPHEIGHT einlesen können.
Das aber nur so am Rande bemerkt, da ich ja eigentlich nur wegen _SCREENIMAGE da bin.

Die einfachste Version die Funktionalität des Programms zu testen wäre es desktop& nicht zu leeren und stattdessen per _COPYIMAGE anzuzeigen. Da ich aber sowieso die Screenshots speichern möchte, wende ich mich dem Begriff… es gibt keinen Speicher-Bild-als-Datei-Befehl!?!

Aber es gibt eine von Galleon erstellte SAVEIMAGE SUB die .bmp erstellen kann. Irgendwie bin ich von dieser Lösung nicht wirklich begeistert, aber ein Testlauf sollte zeigen ob durch das .bmp ein Qualitätsverlust entsteht.

Es funktioniert. Das .bmp ist zwar fast 4Mb groß aber Farben und Qualität passen. Allerdings ist es statt den erwarteten 1920×1080 nur 1536*864 groß?
Links oben passt, aber rechts und unten fehlen Daten.

Einen kurzen Test mit dem weiter oben erwähnten _DESKTOPWIDTH und _DESKTOPHEIGHT ergibt das QB64 meint das mein Desktop diese Dimension hat.

Dafür habe ich entdeckt das _SCREENIMAGE(column1, row1, column2, row2) einstellbarer ist. Meine Vermutung das der Bildausschnitt zu groß war wird leider zerschmettert, denn der Koordinatengesteuerte Versuch provoziert einen Fehler. Mit QB64 ist also tatsächlich kein kompletter Screenshot möglich…

Kontrolle erlangen

SEND KEYS“ funktioniert wahnsinnig gut. In „MOUSE AREA“ befindet sich die Anleitung wie man den Mouse Cursor plaziert, das Einzige was mir jetzt noch fehlt ist das Senden von Mouse clicks.

Advanced Brainfart:
Wenn es möglich wäre den in „SEND KEYS“ erzeugten Screenshot aus dem Zwischenspeicher in QB64 zu importieren wäre es (mit kA, wahrscheinlich typisch unendlich hohem Aufwand) möglich diesen auszuwerten um die Sprites sinnvoll abzulegen (Siehe Sonntag 09/12/2018 Punkt#4: „Bild, x-pos, y-pos und Tilenummer“). Sich komplett auf Variabeln zu verlassen ist nicht möglich da die Tilenummer nicht eruierbar ist.

Back to topic:
Die Zeile die ich wahrscheinlich kopieren und modifizieren muss ist diese:


SUB SENDKEYS ALIAS keybd_event (BYVAL bVk AS LONG, BYVAL bScan AS LONG, BYVAL dwFlags AS LONG, BYVAL dwExtraInfo AS LONG)

Zuerst sollte ich mal schauen was zu „keybd_event“ in der „user32.dll“ steht und ob ein Mouse-equivalent existiert. (…) Oder ich finde sofort die „mouse_event function“ und im selben Zug das diese als auch die „Keybd_event function“ durch die „SendInput function“ ersetzt wurden.

Und da wären wir in meinem moralischen Dilemma: Soll dieses Programm >jetzt für mich< funktionieren oder >möglichst immer für möglichst alle<?

Nachdem sich das Projekt „SR:SNES:DRMM“ jetzt schon seit Jahren (Afair Mittwoch 05.10.2016) zieht und keine tatsächlichen Ergebnisse sondern „nur“ Ideen, Brainfarts, Erkenntnisse und ähnliches hervorgebracht hat, bin ich eine Runde egoistisch und wähle >jetzt für mich<.

Sollte es so funktionieren wie ich es gerne hätte und sollte ich es anwenden können und sollte ich Zeit, Lust und Laune haben kann ich es ja immer noch verallgemeinern.
Wie ich bei meinen Abstechern in VBA gelernt habe ist es aber notwendig alle verwendeten Ressourcen zu notieren und zu speichern.
(Der Link zu besten Quelle ever nutzt nichts wenn diese nicht mehr online ist… und deswegen speichere ich ab nun alle relevanten Seiten als .pdf und füge sie am Ende der Beiträge an.)

Okay, zerlegen wir SEND KEYS in die Angaben auf der Microsoft-Seite

void keybd_event(
  BYTE      bVk,	-> A virtual-key code. The code must be a value in the range 1 to 254.
  BYTE      bScan,	-> A hardware scan code for the key.
  DWORD     dwFlags,	-> Controls various aspects of function operation. This parameter can be one or more of the following values.
			   Value 			Meaning
			   KEYEVENTF_EXTENDEDKEY	If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224).
			   0x0001
			   KEYEVENTF_KEYUP		If specified, the key is being released. If not specified, the key is being depressed. 
			   0x0002
  ULONG_PTR dwExtraInfo	-> An additional value associated with the key stroke.
);
DECLARE DYNAMIC LIBRARY "user32"
    SUB SENDKEYS ALIAS keybd_event (BYVAL bVk AS LONG, BYVAL bScan AS LONG, BYVAL dwFlags AS LONG, BYVAL dwExtraInfo AS LONG)
END DECLARE

CONST KEYEVENTF_KEYUP = &H2
CONST VK_SNAPSHOT = &H2C 'PrtScn key
CONST VK_MENU = &H12 'Alt key
CONST VK_SHIFT = &H10 'Shift key
CONST VK_LWIN = &H5B

PRINT ""
PRINT "SENDKEYS clone example.  Press ENTER to begin..."
PRINT

WHILE INKEY$ <> CHR$(13): WEND

'=== Capture Active window to Clipoard as image (Like Alt+PrtSc)
SENDKEYS VK_MENU, 0, 0, 0					-> drückt Alt key
SENDKEYS VK_SNAPSHOT, 0, 0, 0					-> drückt PrtScn key
SENDKEYS VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0			-> läßt PrtScn key los
SENDKEYS VK_MENU, 0, KEYEVENTF_KEYUP, 0				-> läßt Alt key los

Auf der Suche was „VK_LWIN = &H5B“ ist (Antwort: Die Windows-Taste) bei den Virtual-Key-Codes entdeckt das auch die Maustasten vorhanden sind:
https://docs.microsoft.com/de-at/windows/desktop/inputdev/virtual-key-codes

Constant/value    Description
VK_LBUTTON        Left mouse button
0x01
VK_RBUTTON        Right mouse button
0x02

Eine Runde wirres gegoogle weiter darf ich mitteilen das 0x01 anscheinend dem Code &H1 bzw &H01 entspricht. (0x02 ist &H2 bzw &H02).
Was genau das darstellt erchließt sich mir hingegen nicht.
0x01 = bit set / &H1 = decimal 1.???

Okay, theoretisch sollte es also möglich sein, VK_LMB = &H1 und VK_RMB = & &H2 in das Beispiel einzubauen, den Mousecursor mittels SetCursorPos x, y (Eine SUB die in MOUSE AREA erstellt wird.) zu einen bestimmten Punkt zu führen und dann per SENDKEYS VK_LMB, 0, 0, 0 zu drücken?

Sollte funktionieren. Dann basteln wir den Hybrid mal…

Uhm… „CONST KEYEVENTF_KEYUP = &H2“ ? Ich dachte &H2 ist gleichbedeutend mit &H02 alias Right Mouse Button?
Bei den „dwflags“ oben findet sich „0x0002„… hm, vielleicht funktioniert das Ganze doch nicht gaaanz so easy wie ich annahm…

Okay, funktioniert scheinbar ohne „mouse_event“ doch nicht…

Es funktioniert!!! Whoop whoop!

'============================
'KEYBOARD & MOUSE SCRIPT TEST
'-----------[kemoscte.bas]---
'
'Basierend auf SENDKEY.BAS (Coded for QB64 by Dav, JAN/2013) und dem
'"Mouse Area"-Beispiel gefunden auf https://qb64.org/wiki/Windows_Libraries
'
'Sinn und Zweck ist es den Mouse Cursor auszerhalb des Programmfensters auf dem
'Bildschirm zu bewegen, zu klicken und Text einzugeben.
'
'All (d) by -=[d.s.R.]=- 10.Dec.2018

DECLARE DYNAMIC LIBRARY "user32"
    SUB SetCursorPos (BYVAL x AS LONG, BYVAL y AS LONG) 'move cursor position
    SUB SENDKEYS ALIAS keybd_event (BYVAL bVk AS LONG, BYVAL bScan AS LONG, BYVAL dwFlags AS LONG, BYVAL dwExtraInfo AS LONG)
    SUB SENDMOUSE ALIAS mouse_event (BYVAL dwFlags AS LONG, BYVAL dx AS LONG, BYVAL dy AS LONG, BYVAL dwData AS LONG, BYVAL dwExtraInfo AS LONG)
END DECLARE

CONST KEYEVENTF_KEYUP = &H2
CONST VK_SNAPSHOT = &H2C 'PrtScn key
CONST VK_MENU = &H12 'Alt key
CONST VK_SHIFT = &H10 'Shift key
CONST VK_LWIN = &H5B

CONST MOUSEEVENTF_LEFTDOWN = &H2 'The left button is up.
CONST MOUSEEVENTF_LEFTUP = &H4 'The left button is down.
CONST MOUSEEVENTF_RIGHTDOWN = &H8 'The right button is up.
CONST MOUSEEVENTF_RIGHTUP = &H10 'The right button is down.

SHELL _DONTWAIT "notepad.exe"

SLEEP 1 'give time to make notepad the active window...

'=== Say Hi...
SENDKEYS &H48, 0, 0, 0: SENDKEYS &H48, 0, KEYEVENTF_KEYUP, 0 'H
SENDKEYS &H49, 0, 0, 0: SENDKEYS &H49, 0, KEYEVENTF_KEYUP, 0 'i

SetCursorPos 120, 60 '<- Mousecursor direkt ueber "Datei"
SENDMOUSE MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
SENDMOUSE MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
SetCursorPos 120, 120 '<- Mousecursor direkt ueber "Speichern"
SLEEP 1
SENDMOUSE MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
SENDMOUSE MOUSEEVENTF_LEFTUP, 0, 0, 0, 0

END

Link für morgen: SCREENIMAGE
„The _SCREENIMAGE function stores the current desktop image or a portion of it and returns an image handle.“
Ghihihi…

.pdf-Dateien:
001_QB64wiki_windows_libraries_10_12_2018.pdf (702kb)
002_WinDevCenter_keybd_event_function_10_12_2018.pdf (462kb)
003_WinDevCenter_mouse_event_function_10_12_2018.pdf (458kb)
004_WinDevCenter_virtual-key_codes_10_12_2018.pdf (419kb)