Tutorial How to Reverse Engineer Save Game Files - Titan Quest Cheats

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
1580236668687.png


Game: Titan Quest Anniversary Edition
Engine: Path Engine
Studio: Iron Lore
Version: Steam
Buy: Steam

Introduction:

Funnily enough this was the most often requested thing on my website. I'm still not sure why this game is still so damn popular but I'm guessing it has something to do with the fact it's cheap, has no anticheat and is a pretty decent Diablo clone. Source is attached and link to the repo is here.

This is less of a tutorial and more of a break down of all the information I reversed on the save files if you're curious read on below.

So awhile back I released a small utility for screwing around with TQ’s Save Files. While it worked pretty well and I was able to achieve my end goal of skipping to playing higher difficulties without grinding through normal; what I didn’t expect was the number of people asking for updates and fixes for it.

I mean at this point Titan Quest is over thirteen years old. But who am I to judge I regularly play Red Alert 2 (Tour of Egypt, Team Alliance, Crates ON). So after thinking about it for awhile I decided on a few things:
  1. I didn’t want to spend the rest of my life maintaining a utility for an ancient game.
  2. I no longer wanted to receive messages, comments and requests asking for updates.
So with that in mind I’ve decided to release everything I have relating to Titan Quest, all my source code and everything I learned while reverse engineering the save file.

This article will cover as much information as I was able to glean from reversing the Save File and give people an insight into the rough structure of the file so they may write their own tools.

Digging in

Before we start you need to make sure you have a decent hex editor like HxD, and you might as well fire up Visual Studio because we’re going to be doing some light coding.

Starting with the header of TQ we can begin to analyse the structure:

tq-header.jpg

Amazing ms paint job.

Looking in our hex window we can see that the first four bytes are a signed int32. The value of which specifies the length of the proceeding string which in this case is headerVersion (without a null byte). Immediately following this string is the value of that field in this case 03 00 00 00 (int32).

So the version of this save file is version 3 or the Ragnarok patch.

If you continue to look through the file you’ll see for the most part it follows a similar structure:

C++:
int32 stringLength;
char fieldName[stringLength];
int32 value;
So with this information we can create a simple parsing utility to scrape the file for information:

C++:
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <windows.h>
#include <inttypes.h>

typedef unsigned char uchar_t;

void ReadHeader(uchar_t* pBuf)
{
    // Pointer to data buffer
    char* ptrBuf = (char*)(pBuf);

    // Set pointer to fieldLength data
    int32_t* fieldLength = (int32_t*)(ptrBuf);

    // Allocate and zero a buffer 1 char bigger for the null string
    char* fieldName = (char*)calloc(1, *fieldLength + 1);
   
    // Create a pointer to the start of the string
    char* ptrFieldName = (char*)(ptrBuf + sizeof(int32_t));

    // Copy the text to our buffer
    memcpy(fieldName, ptrFieldName, *fieldLength);

    // Calculate the offset of the header version (start of the buffer, length of the value, and the value itself)
    int32_t* fieldValue = (int32_t*)(ptrBuf + sizeof(int32_t) + *fieldLength);

    // Dump the info
    printf("Field Name:\t%s\nName Length:\t%d\nValue:\t\t%d\n\n", fieldName, *fieldLength, *fieldValue);

    // Free mem
    free(fieldName);
}

int main(void)
{
    const char saveFile[] = "Player.chr";

    FILE* fp = fopen(saveFile, "rb");
    if (!fp)
        return -1;

    fseek(fp, 0, SEEK_END);
    long fileSize = ftell(fp);
    rewind(fp);

    uchar_t* pBuf = (uchar_t*)calloc(1, fileSize);

    fread(pBuf, fileSize, 1, fp);
    fclose(fp);

    // Read TQ's save header information
    ReadHeader(pBuf);

    free(pBuf);
}
This should dump the header of the save file to the screen, easy? EASY.

And then something bad happened…
If we look at offset 0x2D (45) in the save file we see something annoying. Our assumption was incorrect because the playerCharacterClass field has a string value instead of a plain int32. Mind you it’s stored in the same format as before int32, char.

It is however at a fixed offset in the file so we can easily update our code to parse this section easily.

C++:
void ReadPlayerClass(uchar_t* pBuf)
{
    // Move pointer to the start of playerclass
    char* ptrBuf = (char*)(pBuf + 0x15);

    int32_t* nameLength = (int32_t*)(ptrBuf);

    // Same as before move the pointer to the start of the string
    char* ptrFieldName = (char*)(ptrBuf + sizeof(int32_t));
    char *fieldName = (char*)calloc(1, *nameLength + 1);
    memcpy(fieldName, ptrFieldName, *nameLength);

    // Dump the info
    printf("Field Name:\t%s\nName Length:\t%d\n\n", fieldName, *nameLength);
    free(fieldName);

    // Now we need to extract the value
    // Move the pointer to start of the data
    ptrBuf = (char*)(ptrFieldName + *nameLength);

    nameLength = (int32_t*)(ptrBuf);
    fieldName = (char*)calloc(1, *nameLength + 1);
    ptrFieldName = (char*)(ptrBuf + sizeof(int32_t));

    memcpy(fieldName, ptrFieldName, *nameLength);
    printf("Field Name:\t%s\nName Length:\t%d\n\n", fieldName, *nameLength);

    free(fieldName);
}
Now we’re getting somewhere! If your code is working correctly you should see the following information on screen now:


DpxBznf.png

Lookin' snazzy.

The next two fields are uniqueId and streamData. To my knowledge uniqueId is always 0x10 bytes long and streamData is usually either 0 or 0x14. You can either read these fields or skip over them it wont make any difference.

Once you get to playerClassTag you’ll run into your first manipulable variable: playerLevel. However something to remember with RPG style games is the player’s level is usually intertwined with the player’s experience.

It’s never a good idea to alter one without updating the other. If you want to see what I mean try setting the player’s level 60 with zero experience and see what happens (on a new save file!).

That’s pretty much it:

That’s pretty much all there is to the save data. Most fields control what they describe themselves as. To figure out what other fields do you can either install random values and load the game or do comparisons between different save files.
 

Attachments

Last edited by a moderator:
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods League of Legends Accounts