Mercprofilestruct

Kein Durchblick im Quellcode von Ja2? Hier werden sie geholfen.

Moderator: Flashy

Antworten
Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Mercprofilestruct

Beitrag von Snej » 11 Jun 2005, 00:33

Das MERCPROFILESTRUCT befindet sich in "soldier profile type.h"
Es enthält Informationen zum Profil des Söldners.
Ziemlich am Anfang des Programms wird ein globales Array gMercProfiles
mit 170 NPC Profilen geladen.

***

Platzhalter für Links zu den MERCPROFILESTRUCT Variablen und deren
Bedeutung

***

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 11 Jun 2005, 00:34

gMercProfiles wird in "Soldier Profile.h" definiert.
Initialisierung findet in "Soldier Profile.c" durch JA2EncryptedFileRead statt,
welches das verschlüsselte File "BINARYDATA\\Prof.dat" ausliest, entschlüsselt
und ein memcopy auf das globale Array macht !!! böse böse

sizeof(MERCPROFILESTRUCT) ist eigentlich 707 (nachzählen ;))

dazu:
NAME_LENGTH=30 und NICKNAME_LENGTH=10 steht in "soldier profile type.h"
typedef CHAR8 PaletteRepID[ 30 ]; steht in "Overhead Types.h"
typedef unsigned char BOOLEAN; steht in "Types.h" wie auch die
restlichen Typen

Prof.dat hat nun jedoch eine Größe von 121.720 Bytes was zusammen mit
NUM_PROFILES = 170 (steht in "soldier profile type.h")
sizeof(MERCPROFILESTRUCT)=716 ergibt.

Es gibt also 9 Padding Bytes. Das erste ist zwischen 229 bSex (für Barry =00 ---
01 wäre weiblich) und 261 bMedical (Barry hat einen Wert von 20 in Medizin)

Wenn interesse besteht kann ich eine decryptete prof.dat an NItrat schicken,
damit er sie, wenn er so gütig ist, online stellen kann.

Ich hoffe ihr könnt mir helfen die genauen Positionen der Padding Bytes auszumachen.
Ich seh nur den Weg über das Debuggen der Original.exe
da ich z.B. nicht weiß wie ich meinem Compiler beibringe interne Paddings
in structs zu erstellen.

Generell ist im Source darauf zu achten das man memcopy nicht mit structs verwendet.
Jeder Compiler erzeugt andere Paddings und so kann eine selbst compilierte exe
schnell ein Fehlverhalten zeigen.

Daraufhin ist der bestehende Source zu untersuchen. Wahrscheinlich müssen
zusätzliche Ladefunktionen geschrieben werden.

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 11 Jun 2005, 10:12

Auf Wunsch von Realist noch einmal eine kleine Illustration des Problems:

struct {char c;short s;int i;}
Größen 1,2 bzw. 4 Bytes
im Speicher mehere Möglichkeiten:
cssiiii -> 7 Bytes
cpssiiii -> 8 Bytes
csspiiii -> 8 Bytes
cssiiiip -> 8 Bytes
p für PaddingByte(s)

wenn man nun den struct mit memcopy in Variante a) speichert
später neu compiliert und der Compiler Variante c) erzeugt
und dann den Struct mit memcopy einliest, hat man ein Problem.
Denn p kann jeden zufälligen zustand haben.

Besonders gefährlich dabei ist, das die ersten Variablen noch stimmen (hier c).

Das ist auch der grund warum sich structs prinzipiell nicht in C
vergleichen lassen.

Grüße Snej

Ich hoffe eigentlich das in 1.12 die prof.dat keine 121.720 Bytes groß ist und nur
bis 1.05 so groß war. Vielleicht gibt jemand Bescheid.

Das ändert jedoch nichts daran das wir es mit einem schwerwiegenden Problem zu tun haben.

shadow the deat
Alpha-Squad
Beiträge: 1593
Registriert: 01 Feb 2002, 19:22
Kontaktdaten:

Beitrag von shadow the deat » 11 Jun 2005, 10:18

also ich bin an der entschlüsselte prof.dat interressiert.

allgemein an der Technik wie man die ja2 daten entschlüsselt
:lhdevil: :uriel: Führer der SoS :lhdevil: (soldiers of shadow)

:lhdevil: Enominis Satanis :lhdevil:

Die Your God is Dead
Behold Satans Rise :hail:


(Action)Gamer für Gewalt und Terror :k:

Realist
Alpha-Squad
Beiträge: 1573
Registriert: 24 Apr 2003, 11:00
Wohnort: Düsseldorf

Beitrag von Realist » 11 Jun 2005, 10:21

hiho,

den technischen Hintergrund verstehe ich schon, nur wofür zur Hölle ist das wichtig?
Sorry, wenn ich da gerade auf der Leitung stehe. :red:

die goldene prof.dat ist übrigens auch genau 121.720 bytes groß...

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 11 Jun 2005, 11:02

@shadow: dann werd ich mal die decryptete Version in guter Hoffnung an Nitrat heute noch irgendwann abschicken.

@Realist: Kannst du sicherstellen, den gleichen Compiler in der gleichen Version mit den gleichen Einstellungen wie SirTech zu verwenden ?

Als Firma die nur die exe rausgibt ist das kein Problem, aber für SourceModder schon. Da sich die Compiler doch schon unterscheiden. Und das Abbild des structs im Speicher ist Compilersache !!!! Wir wollen aber nur den Sourcecode verändern und keine Compileroptionen etc. vorschreiben.

Die Prof.dat enthält alle Infos des Söldners die von Sir-Tech mitgegeben wurden (außer Bilder und Sounds übersetzte Texte etc.). Sie wurde als memcopy von SirTech !!!! erstellt.

Du erstellst also einen Mod mit dem Source und z.B. vor dem ExpLevel steht ein PaddingByte (von SirTech in prof.dat erzeugt) das zufällig 99 enthält und dein Compiler erzeugt das PaddingByte genau an der Stelle wo das ExpLevel steht - vertauscht also beide Werte.
Bis du den Fehler findest und feststellst das es an Compilereinstellungen liegt,
vergeht schon so einige Zeit.

Bei ExpLevel findet man relativ schnell das es einen Fehler gibt, aber es gibt auch versteckte Variablen wie, z.B. Einstellungen zu den anderen NPCs.
Wenn sich da was verschiebt wundert man sich nur das vor dem mod (um beim Beispiel zu bleiben) zwei Söldner miteinander auskamen und nach dem mod nicht mehr.

Grüße Snej

Immer noch nicht das Problem erkannt? Vielleicht findet jemand anders erklärendere Worte - oder PM an mich (bzgl. worum geht es).

Realist
Alpha-Squad
Beiträge: 1573
Registriert: 24 Apr 2003, 11:00
Wohnort: Düsseldorf

Beitrag von Realist » 11 Jun 2005, 11:23

argh, jetzt versteh worauf das hinausläuft. :)
thx für die erklärung :k:

ich werde sehen, ob ich mithelfen kann, eine lösung zu finden.

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 11 Jun 2005, 13:28

Also ich stell mir das in etwa so vor, das die Ladefunktion je die 716 Bytes in einen buffer (decrypted) kopiert und dann eine Byte für Byte Zuordnung zum struct erfolgt.

Dabei kann man gleich Prozessoren/Compiler berücksichtigen die eine andere Bytezuordnung bei den Integers z.B. haben (Lowest Byte first vs. Highest Byte first) was den Code portabler macht.

Für MercProfileStruct würde ich eine solche Funktion schreiben, wenn die 9 Padding Bytes denn feststehen.

Aber der Code ist auf weitere MemCopy s hin zu überprüfen insbesondere im Zusammenhang mit sizeof(structVar).

Meine (knappe) Analyse bisher:

1 Byte zwischen Sex und Medical
2 Bytes zwschen Medical und Strength
1 Byte zwischen LifeMax und Life
1 Byte zwischen Agility und Mechanical
2 Bytes zwischen Mechanical und Race
2 Bytes nach Attitude

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 11 Jun 2005, 16:06

hmm keine edit funktion hier in diesem Unterforum :dozey:

hab jetzt mal den struct in meine c Umgebung kopiert zusammen mit

typedef unsigned char CHAR8;
typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef unsigned char UINT8;
typedef signed int INT32;
typedef signed short INT16;
typedef signed char INT8;

// Rest
const int NAME_LENGTH=30;
const int NICKNAME_LENGTH=10; //steht in "soldier profile type.h"
typedef CHAR8 PaletteRepID[ 30 ]; //steht in "Overhead Types.h"
typedef unsigned char BOOLEAN;

und am Ende ein

cout << sizeof (MERCPROFILESTRUCT)<<endl;

und siehe da -> 707
Mein Compiler erzeugt also keine Padding Bytes.

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Names and Stats

Beitrag von Snej » 11 Jun 2005, 20:47

Die Variablen:

UINT16 zName[ NAME_LENGTH ];
UINT16 zNickname[ NICKNAME_LENGTH ];

INT8 bLife; // aktuelle Gesundheit
INT8 bLifeMax; // maximale Gesundheit STAT
INT8 bAgility; // Beweglichkeit STAT
INT8 bDexterity; // Geschicklichkeit STAT
INT8 bStrength; // Kraft STAT
INT8 bLeadership; // Führungsqualität ATTR
INT8 bWisdom; // Weisheit STAT
INT8 bMarksmanship; // Treffsicherheit ATTR
INT8 bMechanical; // Technik ATTR
INT8 bExplosive; // Sprengstoffe ATTR
INT8 bMedical; // Medizin ATTR
INT8 bExpLevel; // Erfahrungsstufe

INT16 sExpLevelGain;
INT16 sLifeGain;
INT16 sAgilityGain;
INT16 sDexterityGain;
INT16 sWisdomGain;
INT16 sMarksmanshipGain;
INT16 sMedicalGain;
INT16 sMechanicGain;
INT16 sExplosivesGain;
INT16 sLeadershipGain;
INT16 sStrengthGain;

INT8 bExpLevelDelta;
INT8 bLifeDelta;
INT8 bAgilityDelta;
INT8 bDexterityDelta;
INT8 bWisdomDelta;
INT8 bMarksmanshipDelta;
INT8 bMedicalDelta;
INT8 bMechanicDelta;
INT8 bExplosivesDelta;
INT8 bStrengthDelta;
INT8 bLeadershipDelta;

UINT16 usStatChangeChances[ 12 ];
UINT16 usStatChangeSuccesses[ 12 ];

INT8 bEvolution;

INT8 bScientific; // ein unbenutztes ATTR Relikt - hat keinerlei Bedeutung mehr
// wird aber noch in den SOLDIERTYPE kopiert



Es folgen nun einige Erläuterungen:

a) In "soldier profile type.h" finden wir

#define NAME_LENGTH 30
#define NICKNAME_LENGTH 10

zName und zNickname sind Strings, wobei jedes Zeichen aus zwei Bytes besteht:
ASCII Code und einem 0 Byte.
Die Strings terminieren mit einem doppelten 0 Byte.
zName enhält den ausführlichen Namen.
zNickname enthält den Kurznamen.

b) Bemerkung: Die folgenden Definitionen stammen aus "Campaign.h".

Für bestimmte Aktionen und Ereignisse bekommt der Spieler Chancen (positive wie
auch negative).

// stat change causes
#define FROM_SUCCESS 0
#define FROM_TRAINING 1
#define FROM_FAILURE 2

Die Chancen werden getestet und bei bestandenem Test in subPoints umgewandelt.
Einen großen Einfluß, einen Test zu bestehen, hat die Weisheit des Söldners.
Die subPoints werden in den GainVariablen gespeichert. Überschreiten sie eine gewisse
Grenze

#define SKILLS_SUBPOINTS_TO_IMPROVE 25
#define ATTRIBS_SUBPOINTS_TO_IMPROVE 50
#define LEVEL_SUBPOINTS_TO_IMPROVE 350 // per current level!
// also für Lvl 9 auf Lvl 10: 9*350=3150

wird Gain wieder auf 0 gesetzt und die betreffende Eigenschaft bis zu einem Maximalwert
gesteigert.

#define MAX_STAT_VALUE 100 // for STAT and ATTR
#define MAXEXPLEVEL 10 // maximum merc experience level

Durch Training werden auch Chancen vergeben. Allerdings gibts nur im Einsatz auch
Chancen für die Erfahrung. Außerdem gibt es ein Trainingscap über das hinaus nicht
trainiert werden kann.

// training cap: you can't train any stat/skill beyond this value
#define TRAINING_RATING_CAP 85

Söldner, die sich nicht im Team befinden, trainieren oder sind im Einsatz, wo sie auch
Erfahrung gewinnen können.
In den DeltaVariablen wird die Anzahl der unter der Leitung des Spielers erwirkten
Fortschritte der Söldner gespeichert.

usStatChangeChances zählt positive wie auch negative Chancen.
usStatChangeSuccesses zählt wieviele davon den Test bestanden haben.
Beide Arrays dienen also nur statistischen Zwecken.

#define SALARYAMT 0
#define HEALTHAMT 1
#define AGILAMT 2
#define DEXTAMT 3
#define WISDOMAMT 4
#define MEDICALAMT 5
#define EXPLODEAMT 6
#define MECHANAMT 7
#define MARKAMT 8
#define EXPERAMT 9
#define STRAMT 10
#define LDRAMT 11
#define ASSIGNAMT 12
#define NAMEAMT 13

#define FIRST_CHANGEABLE_STAT HEALTHAMT
#define LAST_CHANGEABLE_STAT LDRAMT
#define CHANGEABLE_STAT_COUNT ( LDRAMT - HEALTHAMT + 1 )

usStatChangeChances[0] wie auch usStatChangeSuccesses[0] bleiben also ungenutzt.

c) In "soldier profile type.h" finden wir die möglichen Werte für bEvolution.

typedef enum{
NORMAL_EVOLUTION =0,
NO_EVOLUTION,
DEVOLVE,
} CharacterEvolution;

NORMAL_EVOLUTION - halt normal, wie oben erklärt.
NO_EVOLUTION bedeutet, das keine Stats erhöht werden z.B. für Fahrzeuge
DEVOLVE - Der Söldner hat seinen Zenit schon überschritten und gewinnt durch
Training nichts mehr hinzu. Auch nehmen seine Stats eher ab als zu.

Snej
Kanonenfutter
Beiträge: 31
Registriert: 03 Jun 2003, 07:13

Beitrag von Snej » 12 Jun 2005, 17:53

Ich denke ich hab jetzt die PaddingBytes ausgemacht und diese stimmen auch mit meinen Vermutungen oben überein (an einer Stelle hatte ich mich geirrt da ich Race und Nationality verwechselt habe).

Ich hab mir ein 716 Bytes Array erstellt und die Variabilität der einzelnen Bytes dazuschreiben lassen (mit 0 ausgenommenen 0 Wert für leere Profile).
Daurch konnte ich Stats wie Medical etc. oder andere Variablen wie WeeklySalary gut ausmachen.

Dann hab ich die struct Variablen der Reihe nach zugeordnet, und zwar so das ByteTypen an jeder Position beginnen dürfen, Shorts nur an geraden und Integer nur an durch 4 teilbaren Positionen.

Das Ergebnis überzeugt, da genau 9 Bytes Leerraum (Padding) entstehen an den vorausgesagten Stellen.

Numeriert man die Bytes von 0-715 sind das folgende Positionen:
241 270 271 309 407 415 539 551 573

Ein Hotfix kann erstmal wie folgt aussehen:

char PadByte p1; // zwischen INT8 bScientific und INT16 sExpLevelGain
char PadByte p2; // zwischen UINT16 usMouthY und UINT32 uiEyeDelay
char PadByte p3; // zwischen UINT16 usMouthY und UINT32 uiEyeDelay
char PadByte p4; // zwischen INT8 bLeadershipDelta und UINT16 usKills
char PadByte p5; // zwischen BOOLEAN fUseProfileInsertionInfo und INT16 sGridNo
char PadByte p6; // zwischen UINT8 ubRoomRangeStart[2] und UINT16 inv[19]
char PadByte p7; // zwischen INT8 bRacist und UINT32 uiWeeklySalary
char PadByte p8; // zwischen INT8 bBaseMorale und UINT16 sMedicalDepositAmount
char PadByte p9; // zwischen INT8 bTownAttachment und UINT16 usOptionalGearCost

Ist jetzt zwar nicht mit einem Debugger überprüft, aber ich bin überzeugt.

Antworten