Aufgrund des grossen Feadbacks zu meinem ersten kurzen Abriss über das Speicherformat von Restricted Area habe ich mich entschieden, etwas tiefer auf die anderen Werte in der Savegame-Datei einzugehen.
Ziel soll es sein, das Savegame weitestgehend zu untersuchen und alle wichtigen Offsets zu erkennen, sowie einen Struct zu entwerfen, der von einem Programm benutzt werden kann um den Spielstand zu bearbeiten.
Einzelschritte
Besorg euch einen Hexeditor. Zum Beispiel y0das 16Edit FX ( unter Code Snippets ), der ist kostenlos und funktioniert wunderbar.
Startet das Spiel und schreibt euch alle Eigenschaften eures Charakters auf.
Startet den Windows Taschenrechner calc.exe ( wissenschaftliche Ansicht ) und rechnet die Werte in Hexadezimal um. Beachtet die reversen Byteorder der Daten! ( Siehe dazu auch das erste Tutorial. )
Legt eine Sicherheitskopie von \Restricted Area\Save\CHARAKTER\CHARAKTER.sav an.
Öffnet \Restricted Area\Save\CHARAKTER\CHARAKTER.sav mit dem Hexeditor eurer Wahl und sucht nach den aufgeschriebenen Werten.
Auswertung
Mit etwas suchen und wiederholtem Ändern der Werte kommen wir zu untenstehendem Schema.
0x0000 DWORD Das aktuelles Level des Charakters.
0x020c DWORD Die aktuellen Erfahrungspunkte.
0x0210 DWORD Die benötigte Erfahrung bis zum nächsten Level.
0x0214 DOWRD Unbekannter Wert.
Die folgenden Werte sind zwar als DWORD gespeichert, können aber nur bis maximal 0xFF ( 255 ) gesteigert
werden. Eine weitere Steigerung ist nur über Items im Spiel möglich.
0x0218 DWORD Stärke
0x021C DWORD Geschick
0x0220 DWORD Konstitution
0x0220 DWORD Intelligenz
0x0228 DWORD Reaktion
0x022C DWORD Willenskraft
0x0230 CHAR[] Der Name des Charakters, er darf maximal 17 Zeichen lang sein und ist mit 0x0D 0x0A terminiert.
Relativ zu der Länge des Namens sind die Folgenden Werte gespeichert. Ist unser Spielername Mantis, 5 Zeichen Länge, so berechnet sich die Start-Offset des folgenden Blocks wie folgt:
Sei
b=0x0002 ( die Länge des Terminators )
c=0x0006 ( Länge des Charakternamens "Mantis" )
Geld Offset 0x00000230 + b + c
Offset in Zahlen 0x00000230 + 0x02 + 0x06 = 0x00000238
0xXX DWORD Ausdauer
0xXX DWORD Leben
0xXX DWORD Toleranz
0xXX DWORD Energie
0xXX DWORD Belastbarkeit
0xXX DWORD Schlag
0xXX DWORD Toxin
0xXX DWORD Geld
0xXX DWORD Welcher Charakter ( 04:Jessica, 03:Victoria, 02:Kenji, 01:Johnson )
0x0730 DWORD Anzahl der zu vergebenden Skillpunkte.
0x0734 DWORD Anzahl der zu vergebenden Eigenschaftspunkte.
Code
Nun wollen wir die gefundenen Informationen in einen Struct zusammenfassen, den man benutzen kann um die Informationen strukturiert in ein Programm einzulesen. Hierbei befassen wir uns zunächst mit den Offsets 0x020C bis 0x0230.
typedef struct __restricted_area_savegame_part1 {
/// 0x020C
DWORD dwCurrentExperience; /// Die aktuellen Erfahrungspunkte.
DWORD dwNeededExperience; /// Die benötigten Erfahrungspunkte bis zum nächten Level.
DOWRD dwUnknowen; /// unbekannt
DWORD dwStrength; /// Stärke
DWORD dwGeschick; /// Geschick
DWORD dwCostitution; /// Konstitution
DWORD dwIntelligence; /// Intelligenz
DWORD dwReaction; /// Reaktion
DWORD dwWill; /// Willenskraft
}
Nun folgt der Spielername, diesen können wir leider nicht in einen Struct lesen, da er aus unerfindlichen gründen von der Eingabe her zwar auf 17 Zeichen beschränkt ist, dass Feld in der Savegame-Datei jedoch leider dynamisch wächst. Unschön aber lösbar.
Wir lesen einfach solange Bytes in eine Variable, bis wir auf den Terminator treffen.
Nach diesem etwas unschönen Intermetzo können wir wieder einen ordentlichen Struct definieren.
typedef struct __restricted_area_savegame_part2 {
DWORD dwConstituion; /// Ausdauer
DWORD dwLife; /// Leben
DWORD dwToleranz; /// Toleranz
DWORD dwErnergie; /// Energie
DWORD dwBelastbarkeit; /// Belastbarkeit
DWORD dwSchlag; /// Schlage
DWORD dwToxin; /// Toxin
DWORD dwMoney; /// Geld
DWORD dwCharactertype; /// Welcher Charakter ( 04:Jessica, 03:Victoria, 02:Kenji, 01:Johnson )
}
Diese beiden Struct definieren die Grundwerte des Charakters. Diese kann man mit dem folgenden kleinen Dumper ansehen aber nicht ändern. Der Code bildet eine gute Grundlage um einen Editor zu erstellen und sollte mit den beiden Artikeln selbsterklärend sein.
///
/// Dateiname: restricted-area-savegame-dumper.cpp
///
/// Diese Datei gehört zu dem Artikel:
/// - http://www.naden.de/public/articles/restricted-area-savegame-analyse.php
///
/// Dieser Code ist (c) 2004 naden.de - Der Code darf frei gelesen, empfohlen, verlinkt, compiliert und
/// teilweise in anderen Programmen - nichtkommerzieller Art - verwendet werden, solange die Quelle angegeben
/// wird. Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung
/// des Autors. Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen.
///
#include
#include
#include
/// def., denn dann müssen wir nicht windows.h importieren
#define DWORD long
#define UINT unsigned int
/// Import muss nach den #defines kommen
#include "restricted_area_types.h"
///
/// Kleine Hilfsfunktion, die die geg. Werte auf neg. testet.
/// RA schreibt, wenn ein Wert nicht gesetzt ist, auch nicht 0 ist, 0xffffffff
///
void m_printf( char *szDesc, DWORD dwValue )
{
printf( "\n%s: ", szDesc );
if( dwValue == 0xffffffff )
{
printf( "-" );
}
else
{
printf( "%u", dwValue );
}
}
int main( int argc, char ** argv )
{
printf( "\nResticted Area - Savegame-Dumper (c)2004 www.naden.de - v0.1\n" );
int hFile;
/// Datei zum lesen öffnen
if ( ( hFile = open( argv[ 1 ], O_RDONLY ) ) < 0 )
{
printf( "Syntax: %s path\filename.sav\n", argv[ 0 ] );
return( 1 );
}
/// Gehe zu Offset 0x020C
lseek( hFile, 0x020C, SEEK_SET );
/// Lese den ersten Struct ein
_read( hFile, &ra_savegame_p1, sizeof( ra_savegame_p1 ) );
/// Lese den Charaktername ein
char szCharactername[ 19 ];
UINT uIndex = 0U;
while( _read( hFile, ((char*)szCharactername+uIndex), 1 ) > 0 )
{
if( szCharactername[ uIndex ] == '\n' )
{
break;
}
uIndex ++;
}
szCharactername[ uIndex ] = 0;
/// Lese den zweiten Struct ein
read( hFile, &ra_savegame_p2, sizeof( ra_savegame_p2 ) );
/// und den dritten
read( hFile, &ra_savegame_p3, sizeof( ra_savegame_p3 ) );
close( hFile );
/// gebe die Daten auf dem Schirm aus
printf( "\nCharaktername: %s", szCharactername );
printf( "\nCharaktertype: %u ( %s )",
ra_savegame_p2.dwCharactertype,
__restricted_area_charakters[ ra_savegame_p2.dwCharactertype-1 ] );
printf( "\nAktuelle Erfahrungspunkte: %u", ra_savegame_p1.dwCurrentExperience );
printf( "\nBenötigte Erfahrungspunkte bis zum nächten Level: %u", ra_savegame_p1.dwNeededExperience );
m_printf( "Unbekannt", ra_savegame_p1.dwUnknowen );
m_printf( "Stärke", ra_savegame_p1.dwStrength );
m_printf( "Geschick", ra_savegame_p1.dwSkill );
m_printf( "Konstitution", ra_savegame_p1.dwCostitution );
m_printf( "Intelligenz", ra_savegame_p1.dwIntelligence );
m_printf( "Reaktion", ra_savegame_p1.dwReaction );
m_printf( "Willenskraft", ra_savegame_p1.dwWill );
m_printf( "Ausweichen", ra_savegame_p2.dwDodge );
m_printf( "Widerstand", ra_savegame_p2.dwResistence );
m_printf( "Leben", ra_savegame_p2.dwLife );
m_printf( "Toleranz", ra_savegame_p2.dwToleranz );
m_printf( "Energie", ra_savegame_p2.dwErnergie );
m_printf( "Balistik", ra_savegame_p2.dwBalistik );
m_printf( "Schlag", ra_savegame_p2.dwHit );
m_printf( "Toxinresistenz", ra_savegame_p2.dwToxinResistence );
m_printf( "Geld", ra_savegame_p2.dwMoney );
/// Gehe zu Offset 0x020C
lseek( hFile, 0x0730, SEEK_SET );
m_printf( "Zu vergebenden Entwicklungspunkte", ra_savegame_p3.dwDevelopmentpointsToUse );
m_printf( "Zu vergebenden Grundpunkte", ra_savegame_p3.dwCommonPointsToUse );
return( 0 );
}
///
/// Dateiname: restricted-area-savegame-dumper.cpp
///
/// Diese Datei gehört zu dem Artikel:
/// - http://www.naden.de/public/articles/restricted-area-savegame-analyse.php
///
/// Dieser Code ist (c) 2004 naden.de - Der Code darf frei gelesen, empfohlen, verlinkt, compiliert und
/// teilweise in anderen Programmen - nichtkommerzieller Art - verwendet werden, solange die Quelle angegeben
/// wird. Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung
/// des Autors. Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen.
///
typedef struct __restricted_area_savegame_part1 {
/// 0x020C
DWORD dwCurrentExperience; /// Die aktuellen Erfahrungspunkte.
DWORD dwNeededExperience; /// Die benötigten Erfahrung bis zum nächten Level.
DWORD dwUnknowen; /// unbekannt
DWORD dwStrength; /// Stärke
DWORD dwSkill; /// Geschick
DWORD dwCostitution; /// Konstitution
DWORD dwIntelligence; /// Intelligenz
DWORD dwReaction; /// Reaktion
DWORD dwWill; /// Willenskraft
} __restricted_area_savegame_part1_type;
typedef struct __restricted_area_savegame_part2 {
DWORD dwDodge; /// Ausweichen
DWORD dwLife; /// Leben
DWORD dwToleranz; /// Toleranz
DWORD dwErnergie; /// Energie
DWORD dwBalistik; /// Balistik
DWORD dwResistence; /// Widerstand
DWORD dwHit; /// Schlage
DWORD dwToxinResistence; /// Toxinwiderstand
DWORD dwMoney; /// Geld
DWORD dwCharactertype; /// Welcher Charakter ( 04:Jessica, 03:Victoria, 02:Kenji, 01:Johnson )
} __restricted_area_savegame_part2_type;
typedef struct __restricted_area_savegame_part3 {
/// 0x0730
DWORD dwDevelopmentpointsToUse; ///Anzahl der zu vergebenden Skillpunkte.
/// 0x0734
DWORD dwCommonPointsToUse; /// Anzahl der zu vergebenden Eigenschaftspunkte.
} __restricted_area_savegame_part3_type;
char __restricted_area_charakters[ 4 ][ 10 ] = {
{ "Johnson\0" }, { "Kenji\0" }, { "Victoria\0" }, { "Jessica\0" }
};
__restricted_area_savegame_part1_type ra_savegame_p1;
__restricted_area_savegame_part2_type ra_savegame_p2;
__restricted_area_savegame_part3_type ra_savegame_p3;
Zusammenfassung
Bei der durchsicht der Savegame-Datei sind mir noch viele andere Werte aufgefallen. Da ich aber nicht alle immer zu 100% zuordnen konnte habe ich erst einmal alles weggelassen, was nicht immer eindeutig zu bestimmen war. Je nachdem, ob dieser Artikel auf so viel Interesse wie der erste stösst, reichte ich gerne neue Informationen nach.
Begriffserkärungen
DWORD : Datentyp, ein D(OUBLE)WORD, belegt 4 Bytes. Der Wertebereich geht von -2.147.483.648 <= X <= 2.147.483.64 bzw. 0 <= X <= 4.294.967.295 wenn unsigned.
Offset : Position innerhalb einer Datei.
0x: Eine Zahl die mit 0xbeginnt, bezeichnet eine hexadezimale Zahl, eine Zahl zur Basis 16 mit den Ziffern 0123456789abcdef.
Struct: Begriff aus der Programmierung, ein Struct bezeichnet eine selbst definierte Variable, die mehrere Einzelwerte enthalten kann.
Danke allen Lesern, von denen ich so viel positives Feadback bekommen habe. Danke auch an ROA für die Beisteuerung einiger Offsets.
Anregungen, konstruktive Verbesserungsvorschläge und allgemeines Feadback sind wie immer willkommen. Bitte keine Anfragen zu gepatchten Savegames. Ich werde definitv keine Binaries veröffentlichen oder weitergeben. Jeder sollte mit den zwei Artikeln selber in der Lage sein, seinen Spielstand zu verbessern.
Der direkte Link zu diesem artikel lautet: http://www.naden.de/public/articles/restricted-area-savegame-analyse.php
Alle Angaben ohne Anspruch auf Richtigkeit oder Vollständigkeit. Die Manipulation der Dateien erfolgt auf eigene Verantwortung. Es ist ausgeschlossen, dass der Autor für etwaige Schäden am System oder den Programmen zur Rechenschaft gezogen werden kann.
Alle Markenrechte liegen bei den jeweiligen Eigentümern. Der Autor ist in keiner Weise mit den Herstellern oder dem Distributor des Spiels verbunden.