Comments (43)
Sure, I just ripped the funcs directly from the game rather than reimplement them manually:
void __cdecl sub_4D9710(DWORD *a1, signed int a2, int a3)
{
int v3; // ecx
DWORD *v4; // edx
unsigned int v5; // eax
int v6; // esi
unsigned int v7; // edi
unsigned int v8; // ebx
unsigned int v9; // edx
int v10; // esi
int v11; // eax
unsigned int v12; // edx
int v13; // esi
int v14; // eax
bool v15; // zf
DWORD *v16; // edx
int v17; // edi
int v18; // ebx
unsigned int v19; // eax
unsigned int v20; // ecx
int v21; // ebx
int v22; // esi
int v23; // edi
DWORD *v24; // [esp+Ch] [ebp-10h]
DWORD *v25; // [esp+Ch] [ebp-10h]
unsigned int v26; // [esp+10h] [ebp-Ch]
int i; // [esp+10h] [ebp-Ch]
int v28; // [esp+14h] [ebp-8h]
int v29; // [esp+14h] [ebp-8h]
int v30; // [esp+18h] [ebp-4h]
unsigned int v31; // [esp+28h] [ebp+Ch]
int v32; // [esp+28h] [ebp+Ch]
if (a2 <= 1)
{
if (a2 < -1)
{
v16 = a1;
v17 = -a2 - 1;
v29 = -a2 - 1;
v18 = 0x9E3779B9 * (52 / -a2 + 6);
v19 = *a1;
v25 = &a1[-a2 - 1];
v32 = 0x9E3779B9 * (52 / -a2 + 6);
do
{
v20 = v18;
v21 = v17;
for (i = (v20 >> 2) & 3; v21; --v21)
{
v22 = v16[v21 - 1];
v23 = (16 * v22 ^ (v19 >> 3)) + ((v16[v21 - 1] >> 5) ^ 4 * v19);
v16 = a1;
v16[v21] -= ((v32 ^ v19) + (v22 ^ *(DWORD *)(a3 + 4 * (i ^ v21 & 3)))) ^ v23;
v19 = a1[v21];
}
v16 = a1;
*v16 -= ((v32 ^ v19) + (*v25 ^ *(DWORD *)(a3 + 4 * (i ^ v21 & 3)))) ^ ((16 * *v25 ^ (v19 >> 3))
+ ((*v25 >> 5) ^ 4 * v19));
v15 = v32 == 0x9E3779B9;
v18 = v32 + 0x61C88647;
v19 = *a1;
v17 = v29;
v32 += 0x61C88647;
} while (!v15);
}
}
else
{
v3 = 0;
v4 = a1;
v28 = 52 / a2 + 6;
v5 = a1[a2 - 1];
v6 = a2 - 1;
v24 = &a1[a2 - 1];
v31 = a1[a2 - 1];
v26 = v6;
do
{
v7 = 0;
v30 = v3 - 0x61C88647;
v8 = ((unsigned int)(v3 - 0x61C88647) >> 2) & 3;
if (v6)
{
do
{
v9 = v4[v7 + 1];
v10 = (16 * v31 ^ (v9 >> 3)) + ((v5 >> 5) ^ 4 * v9);
v11 = (v30 ^ v9) + (v31 ^ *(DWORD *)(a3 + 4 * (v8 ^ v7 & 3)));
v4 = a1;
v4[v7] += v11 ^ v10;
v5 = a1[v7++];
v31 = v5;
} while (v7 < v26);
}
v12 = *v4;
v13 = (16 * v31 ^ (v12 >> 3)) + ((v5 >> 5) ^ 4 * v12);
v3 -= 0x61C88647;
v14 = (v30 ^ v12) + (v31 ^ *(DWORD *)(a3 + 4 * (v8 ^ v7 & 3)));
v4 = a1;
*v24 += v14 ^ v13;
v15 = v28-- == 1;
v5 = *v24;
v6 = v26;
v31 = *v24;
} while (!v15);
}
}
int32_t __cdecl main(int32_t argc, char* argv[])
{
FILE* f = nullptr;
fopen_s(&f, u8"C:\\Users\\atom0s\\Desktop\\teadecpp\\Debug\\Savegame1.save", u8"rb");
fseek(f, 0, SEEK_END);
const auto size = ftell(f);
fseek(f, 0, SEEK_SET);
std::vector<uint8_t> data(size, u8'\0');
fread(data.data(), 1, size, f);
fclose(f);
const int32_t decSize = size / -4;
const uint8_t key[] = { 0xF3, 0xED, 0xA4, 0xAE, 0x2A, 0x33, 0xF8, 0xAF, 0xB4, 0xDB, 0xA2, 0xB5, 0x22, 0xA0, 0x4B, 0x9B };
sub_4D9710((DWORD*)&data[0], decSize, (int32_t)&key);
fopen_s(&f, u8"C:\\Users\\atom0s\\Desktop\\teadecpp\\Debug\\Savegame1.save.dump", u8"wb");
fwrite(data.data(), 1, data.size(), f);
fclose(f);
return 0;
}
Quick throw together just to test the decryption.
from engge.
from engge.
I have tested the commit: Clean crypto code (7f9cc32) on windows and it works correctly.
I comment it, because I think you should know that it works well :)
from engge.
For this task, any help will be welcome, I need to figure out how savegame is structured.
from engge.
you want the savegames to be compatible with the original implementation?
from engge.
It would be awesome no?
from engge.
There is a general explanation of save games here: https://blog.thimbleweedpark.com/savegame (you probably already know about this, but it's a good idea to compile this information somewhere..). Also, extra saves are here: https://www.gog.com/forum/general/thimbleweed_park_save_game_request
from engge.
Yes I'm aware of this great blog, it's a gold mine. But as you said it's a good idea to gather all information here.
from engge.
If anybody knows someone who can work on this task I will be very pleased.
This subject becomes critical, it's kind of hard now to go further without having savegames implemented.
I contacted on Sep 1st, 2019 mstr- by email, but he didn't answer me :(
from engge.
Hello, have you asked Luigi Auriemma for help, or has anyone in the following forums?
or
Maybe there, someone is able to decrypt the saved games.
Another thing that can be done is to try to follow the steps described by Ron Gilbert and try to record the variables described in the Json file, until a saved game is loaded. And then if at any time someone decrypts the originals compare and adapt to the original.
from engge.
Good idea, thank you, I posted a message on the forum, we will see...
from engge.
Yes, hopefully someone can help.
from engge.
Hey guys, I helped scemino over on Zenhax but figured I'd share the info I reversed here too to keep it with the actual project so it doesn't get lost on a random site elsewhere.
The game encrypts the save files using TEA encryption. The encryption key used is:
const uint8_t key[] = { 0xF3, 0xED, 0xA4, 0xAE, 0x2A, 0x33, 0xF8, 0xAF, 0xB4, 0xDB, 0xA2, 0xB5, 0x22, 0xA0, 0x4B, 0x9B };
The decryption methods are:
void __cdecl sub_4D9710(_DWORD *a1, signed int a2, int a3)
{
int v3; // ecx
unsigned int *v4; // edx
unsigned int v5; // eax
int v6; // esi
unsigned int v7; // edi
unsigned int v8; // ebx
unsigned int v9; // edx
int v10; // esi
int v11; // eax
unsigned int v12; // edx
int v13; // esi
int v14; // eax
bool v15; // zf
_DWORD *v16; // edx
int v17; // edi
int v18; // ebx
unsigned int v19; // eax
unsigned int v20; // ecx
int v21; // ebx
int v22; // esi
int v23; // edi
unsigned int *v24; // [esp+Ch] [ebp-10h]
_DWORD *v25; // [esp+Ch] [ebp-10h]
unsigned int v26; // [esp+10h] [ebp-Ch]
int i; // [esp+10h] [ebp-Ch]
int v28; // [esp+14h] [ebp-8h]
int v29; // [esp+14h] [ebp-8h]
int v30; // [esp+18h] [ebp-4h]
unsigned int v31; // [esp+28h] [ebp+Ch]
int v32; // [esp+28h] [ebp+Ch]
if ( a2 <= 1 )
{
if ( a2 < -1 )
{
v16 = a1;
v17 = -a2 - 1;
v29 = -a2 - 1;
v18 = 0x9E3779B9 * (52 / -a2 + 6);
v19 = *a1;
v25 = &a1[-a2 - 1];
v32 = 0x9E3779B9 * (52 / -a2 + 6);
do
{
v20 = v18;
v21 = v17;
for ( i = (v20 >> 2) & 3; v21; --v21 )
{
v22 = v16[v21 - 1];
v23 = (16 * v22 ^ (v19 >> 3)) + ((v16[v21 - 1] >> 5) ^ 4 * v19);
v16 = a1;
v16[v21] -= ((v32 ^ v19) + (v22 ^ *(_DWORD *)(a3 + 4 * (i ^ v21 & 3)))) ^ v23;
v19 = a1[v21];
}
v16 = a1;
*v16 -= ((v32 ^ v19) + (*v25 ^ *(_DWORD *)(a3 + 4 * (i ^ v21 & 3)))) ^ ((16 * *v25 ^ (v19 >> 3))
+ ((*v25 >> 5) ^ 4 * v19));
v15 = v32 == 0x9E3779B9;
v18 = v32 + 0x61C88647;
v19 = *a1;
v17 = v29;
v32 += 0x61C88647;
}
while ( !v15 );
}
}
else
{
v3 = 0;
v4 = a1;
v28 = 52 / a2 + 6;
v5 = a1[a2 - 1];
v6 = a2 - 1;
v24 = &a1[a2 - 1];
v31 = a1[a2 - 1];
v26 = v6;
do
{
v7 = 0;
v30 = v3 - 0x61C88647;
v8 = ((unsigned int)(v3 - 0x61C88647) >> 2) & 3;
if ( v6 )
{
do
{
v9 = v4[v7 + 1];
v10 = (16 * v31 ^ (v9 >> 3)) + ((v5 >> 5) ^ 4 * v9);
v11 = (v30 ^ v9) + (v31 ^ *(_DWORD *)(a3 + 4 * (v8 ^ v7 & 3)));
v4 = a1;
v4[v7] += v11 ^ v10;
v5 = a1[v7++];
v31 = v5;
}
while ( v7 < v26 );
}
v12 = *v4;
v13 = (16 * v31 ^ (v12 >> 3)) + ((v5 >> 5) ^ 4 * v12);
v3 -= 0x61C88647;
v14 = (v30 ^ v12) + (v31 ^ *(_DWORD *)(a3 + 4 * (v8 ^ v7 & 3)));
v4 = a1;
*v24 += v14 ^ v13;
v15 = v28-- == 1;
v5 = *v24;
v6 = v26;
v31 = *v24;
}
while ( !v15 );
}
}
char __cdecl sub_4D95E0(void *Src, size_t Size, int a3, int a4, int a5)
{
char result; // al
_DWORD *v6; // eax
_DWORD *v7; // esi
unsigned int v8; // eax
signed int v9; // edi
int v10; // edx
int v11; // ebx
int v12; // ecx
int v13; // edi
int v14; // eax
char *Srca; // [esp+10h] [ebp+8h]
size_t Sizea; // [esp+14h] [ebp+Ch]
if ( !Src || !a5 )
return 0;
if ( (signed int)Size % 8 )
return 0;
v6 = malloc(Size);
v7 = v6;
if ( v6 )
memcpy(v6, Src, Size);
sub_4D9710(v7, (signed int)Size / -4, a5);
v8 = *((unsigned __int8 *)v7 + Size - 1);
v9 = Size - v8 - 9;
Sizea = Size - v8 - 9;
if ( v8 > 8 || v9 <= 0 )
goto LABEL_23;
v10 = 0;
Srca = (char *)0x6583463;
v11 = 0;
v12 = 0;
if ( v9 >= 2 )
{
v13 = v9 - 1;
do
{
v10 += *((unsigned __int8 *)v7 + v12);
v14 = *((unsigned __int8 *)v7 + v12 + 1);
v12 += 2;
v11 += v14;
}
while ( v12 < v13 );
v9 = Sizea;
}
if ( v12 < v9 )
Srca = (char *)(*((unsigned __int8 *)v7 + v12) + 106443875);
if ( &Srca[v11 + v10] != (char *)(*((unsigned __int8 *)v7 + v9) | ((*((unsigned __int8 *)v7 + v9 + 1) | (*(unsigned __int16 *)((char *)v7 + v9 + 2) << 8)) << 8)) )
{
LABEL_23:
if ( v7 )
free(v7);
result = 0;
}
else
{
*(_DWORD *)a3 = v7;
*(_DWORD *)a4 = v9;
result = 1;
}
return result;
}
This will decrypt the save file back to a raw 'GGData' object, similar to what you guys have as a GGPack in your code here:
https://github.com/scemino/engge/blob/master/src/Parsers/GGPack.cpp
The file header is checked for a specific pattern 0x01020304:
int __cdecl sub_4C1EB0(int a1, int a2)
{
_BYTE *v3; // ecx
_DWORD *v4; // eax
int v5; // [esp+0h] [ebp-Ch]
int v6; // [esp+4h] [ebp-8h]
int v7; // [esp+8h] [ebp-4h]
if ( !a1 )
return 0;
if ( *(_DWORD *)(a1 + 20) > 4 )
{
v3 = *(_BYTE **)(a1 + 16);
if ( *v3 == 1 && v3[1] == 2 && v3[2] == 3 && v3[3] == 4 )
return sub_4C1F30((_DWORD *)a1, a2);
}
v4 = sub_456A10(a1);
v5 = 0;
v6 = 0;
v7 = 0;
if ( !v4 )
return 0;
return sub_4C1BC0((int)&v5, (int)v4, a2, 0);
}```
Then parsed if matched using:
```cpp
int __cdecl sub_4C1F30(_DWORD *a1, int a2)
{
_DWORD *v2; // esi
int v3; // eax
int v4; // eax
_DWORD *v5; // esi
signed int v6; // ecx
char *v7; // eax
signed int v9; // eax
signed int v10; // eax
signed int v11; // eax
signed int v12; // eax
int v13; // eax
int v14; // ebx
char v15; // cl
int i; // eax
_DWORD *v17; // eax
_DWORD *v18; // edi
int v19; // ecx
int v20; // ST10_4
int v21; // esi
void **v22; // [esp+10h] [ebp-28h]
int v23; // [esp+14h] [ebp-24h]
int v24; // [esp+18h] [ebp-20h]
int v25; // [esp+1Ch] [ebp-1Ch]
int v26; // [esp+20h] [ebp-18h]
int v27; // [esp+24h] [ebp-14h]
int v28; // [esp+28h] [ebp-10h]
int v29; // [esp+34h] [ebp-4h]
v2 = (_DWORD *)dword_6D7440;
if ( dword_6D7440 )
{
v3 = *(_DWORD *)(dword_6D7440 + 8);
if ( v3 != -1000 )
*(_DWORD *)(dword_6D7440 + 8) = v3 - 1;
(*(void (__thiscall **)(_DWORD *))(*v2 + 8))(v2);
v4 = v2[2];
if ( v4 != -1000 && v4 <= 0 )
{
++dword_6E7754;
(*(void (__thiscall **)(_DWORD *, signed int))*v2)(v2, 1);
dword_6E7754 -= 2;
}
}
v5 = a1;
dword_6D7440 = 0;
if ( !a1 )
return 0;
v6 = a1[5];
if ( v6 > 4 )
{
v7 = (char *)a1[4];
if ( *v7 != 1 || v7[1] != 2 || v7[2] != 3 || v7[3] != 4 )
{
sub_4C2120("bad marker: %d,%d,%d,%d", *v7, *v7 + 1, *v7 + 2, *v7 + 3);
return 0;
}
}
v24 = 1;
v25 = 0;
v22 = &GGArray<GGString *>::`vftable';
v26 = 0;
v27 = 0;
v28 = 0;
v23 = 2;
v9 = a1[6];
v29 = 0;
if ( v9 < v6 )
a1[6] = v9 + 1;
v10 = v5[6];
if ( v10 < v6 )
v5[6] = v10 + 1;
v11 = v5[6];
if ( v11 < v6 )
v5[6] = v11 + 1;
v12 = v5[6];
if ( v12 < v6 )
v5[6] = v12 + 1;
sub_4C2870(v5);
v13 = sub_4C2870(v5);
v14 = v5[6];
v5[6] = v13;
if ( v13 >= v5[5] || (v15 = *(_BYTE *)(v13 + v5[4]), v5[6] = v13 + 1, v15 != 7) )
{
v21 = 0;
}
else
{
for ( i = sub_4C2870(v5); i != -1; i = sub_4C2870(v5) )
{
v17 = (_DWORD *)sub_4D00F0((void *)(v5[4] + i));
v18 = v17;
if ( v17 )
{
v19 = v17[2];
if ( v19 != -1000 )
v17[2] = v19 + 1;
(*(void (__thiscall **)(_DWORD *))(*v17 + 4))(v17);
a1 = v18;
sub_443D50(&v26, (unsigned int *)&a1);
}
}
v20 = a2;
v5[6] = v14;
v21 = sub_4C2770(v5, &v22, v20);
}
sub_417F50();
return v21;
}
This is where the GGPack style reading is happening. You can adjust the current GGPack in a separate app to test with by editing:
- The GGPack::readPack function needs to just directly assume the file is already decrypted after the above steps/info.
- The sig is immediately valid as the first 4 bytes due to the above.
- readHash needs to be adjusted for n_pairs == 0, this seems to be a valid case in save files so ignore throwing an exception there.
- readPack then needs to be adjusted to basically just return everything instead of just file entries.
Once done, the current GGPack code can read the save file:
from engge.
That's awesome @atom0s !!
By chance, can you share your decrypt cpp source code ?
Thanks anyway.
from engge.
Very good job atom0s !!! It is impressive how quickly you have solved it.
Thank you very much for helping scemino and for this great project that he is carrying out.
I congratulate you again.
Cheers :)
from engge.
There is a file called "Save.dat", which occupies 216 bytes. This file is created when you exit the program, whether or not there are saved games.
This file cannot be decrypted with the previously posted function.
atom0s, would you mind taking a look at this one too?
Thank you very much
Cheers
from engge.
For the 'Save.dat' file, the key is different. The rest of the functionality is the same though as I outlined above. The decryption key for that file is:
const uint8_t key[] = { 0x93, 0x9D, 0xAB, 0x2A, 0x2A, 0x56, 0xF8, 0xAF, 0xB4, 0xDB, 0xA2, 0xB5, 0x22, 0xA3, 0x4B, 0x2B };
That one decrypts to text without needing to be further processed.
from engge.
@scemino I saw you requested the encryption function the game uses. It's the same as the above stuff but in reverse order. Here is how the client does it. The encryption itself is the same function from above for TEA/XTEA:
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5)
{
int v5; // eax
int v6; // eax
int v7; // ebx
void *v8; // eax
_DWORD **v9; // edi
signed int v10; // ecx
signed int v11; // edx
int v12; // eax
__int16 v13; // cx
int i; // eax
int v15; // ecx
signed int v17; // [esp+0h] [ebp-4h]
if ( !Src || !a5 )
return 0;
if ( byte_6E803C )
{
v5 = dword_6E8040;
}
else
{
byte_6E803C = 1;
v5 = time64(0);
}
dword_6E8040 = v5 + 1;
v6 = (signed int)(Size + 9) % 8;
v7 = 8 - v6;
v17 = 8 - v6 + Size + 9;
v8 = malloc(v17);
v9 = (_DWORD **)a3;
*(_DWORD *)a3 = v8;
if ( v8 )
memcpy(v8, Src, Size);
v10 = 0;
v11 = 0x6583463;
if ( (signed int)Size > 0 )
{
do
{
v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++);
v11 += v12;
}
while ( v10 < (signed int)Size );
v9 = (_DWORD **)a3;
}
*(_WORD *)((char *)*v9 + Size) = v11;
*((_BYTE *)*v9 + Size + 2) = BYTE2(v11);
v13 = dword_6E8040;
*((_BYTE *)*v9 + Size + 3) = HIBYTE(v11);
*(_WORD *)((char *)*v9 + Size + 4) = v13;
*(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040);
*((_BYTE *)*v9 + Size + 8) = v7;
for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 )
v15 = (int)*v9 + i++;
sub_4D9710(*v9, v17 / 4, a5);
*(_DWORD *)a4 = v17;
return 1;
}
from engge.
For the 'Save.dat' file, the key is different. The rest of the functionality is the same though as I outlined above. The decryption key for that file is:
const uint8_t key[] = { 0x93, 0x9D, 0xAB, 0x2A, 0x2A, 0x56, 0xF8, 0xAF, 0xB4, 0xDB, 0xA2, 0xB5, 0x22, 0xA3, 0x4B, 0x2B };That one decrypts to text without needing to be further processed.
Great, thank you @atom0s, it works fine, yes it's a text file including some stats and achievements.
I have a lot of work now before supporting savegames
from engge.
@scemino I saw you requested the encryption function the game uses. It's the same as the above stuff but in reverse order. Here is how the client does it. The encryption itself is the same function from above for TEA/XTEA:
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5) { int v5; // eax int v6; // eax int v7; // ebx void *v8; // eax _DWORD **v9; // edi signed int v10; // ecx signed int v11; // edx int v12; // eax __int16 v13; // cx int i; // eax int v15; // ecx signed int v17; // [esp+0h] [ebp-4h] if ( !Src || !a5 ) return 0; if ( byte_6E803C ) { v5 = dword_6E8040; } else { byte_6E803C = 1; v5 = time64(0); } dword_6E8040 = v5 + 1; v6 = (signed int)(Size + 9) % 8; v7 = 8 - v6; v17 = 8 - v6 + Size + 9; v8 = malloc(v17); v9 = (_DWORD **)a3; *(_DWORD *)a3 = v8; if ( v8 ) memcpy(v8, Src, Size); v10 = 0; v11 = 0x6583463; if ( (signed int)Size > 0 ) { do { v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++); v11 += v12; } while ( v10 < (signed int)Size ); v9 = (_DWORD **)a3; } *(_WORD *)((char *)*v9 + Size) = v11; *((_BYTE *)*v9 + Size + 2) = BYTE2(v11); v13 = dword_6E8040; *((_BYTE *)*v9 + Size + 3) = HIBYTE(v11); *(_WORD *)((char *)*v9 + Size + 4) = v13; *(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040); *((_BYTE *)*v9 + Size + 8) = v7; for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 ) v15 = (int)*v9 + i++; sub_4D9710(*v9, v17 / 4, a5); *(_DWORD *)a4 = v17; return 1; }
Yes I deleted my message, because I figured it out after, if I call the same method with a positive content size (called 'a2') then the method encrypts the data.
from engge.
For the 'Save.dat' file, the key is different. The rest of the functionality is the same though as I outlined above. The decryption key for that file is:
const uint8_t key[] = { 0x93, 0x9D, 0xAB, 0x2A, 0x2A, 0x56, 0xF8, 0xAF, 0xB4, 0xDB, 0xA2, 0xB5, 0x22, 0xA3, 0x4B, 0x2B };That one decrypts to text without needing to be further processed.
Thank you very much!! It works great :)
from engge.
@scemino I saw you requested the encryption function the game uses. It's the same as the above stuff but in reverse order. Here is how the client does it. The encryption itself is the same function from above for TEA/XTEA:
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5) { int v5; // eax int v6; // eax int v7; // ebx void *v8; // eax _DWORD **v9; // edi signed int v10; // ecx signed int v11; // edx int v12; // eax __int16 v13; // cx int i; // eax int v15; // ecx signed int v17; // [esp+0h] [ebp-4h] if ( !Src || !a5 ) return 0; if ( byte_6E803C ) { v5 = dword_6E8040; } else { byte_6E803C = 1; v5 = time64(0); } dword_6E8040 = v5 + 1; v6 = (signed int)(Size + 9) % 8; v7 = 8 - v6; v17 = 8 - v6 + Size + 9; v8 = malloc(v17); v9 = (_DWORD **)a3; *(_DWORD *)a3 = v8; if ( v8 ) memcpy(v8, Src, Size); v10 = 0; v11 = 0x6583463; if ( (signed int)Size > 0 ) { do { v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++); v11 += v12; } while ( v10 < (signed int)Size ); v9 = (_DWORD **)a3; } *(_WORD *)((char *)*v9 + Size) = v11; *((_BYTE *)*v9 + Size + 2) = BYTE2(v11); v13 = dword_6E8040; *((_BYTE *)*v9 + Size + 3) = HIBYTE(v11); *(_WORD *)((char *)*v9 + Size + 4) = v13; *(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040); *((_BYTE *)*v9 + Size + 8) = v7; for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 ) v15 = (int)*v9 + i++; sub_4D9710(*v9, v17 / 4, a5); *(_DWORD *)a4 = v17; return 1; }
Hello atom0s
Although the important thing here is that scemino understands the encryption process. I would also like to understand it, so that I can help scemino in what I can. And I don't know how to do it.
I see this encryption function and there are things I don't understand. And others that perhaps if you have understood them, therefore:
- The parameters that the function receives, I think they would be the following, correct me if I'm wrong:
a3 -> target data
a4 -> target data size
a5 -> encryption key
- I see two variables:
byte_6E803C
dword_6E8040
These are not defined within the body of the function:
char __cdecl sub_4D94A0 (void * Src, size_t Size, int a3, int a4, int a5)
What do they contain or how would they be defined?
Could you pass a real encryption example like the one you passed for decryption?
In any case, thank you very much for your help.
Cheers
from engge.
@scemino I saw you requested the encryption function the game uses. It's the same as the above stuff but in reverse order. Here is how the client does it. The encryption itself is the same function from above for TEA/XTEA:
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5) { int v5; // eax int v6; // eax int v7; // ebx void *v8; // eax _DWORD **v9; // edi signed int v10; // ecx signed int v11; // edx int v12; // eax __int16 v13; // cx int i; // eax int v15; // ecx signed int v17; // [esp+0h] [ebp-4h] if ( !Src || !a5 ) return 0; if ( byte_6E803C ) { v5 = dword_6E8040; } else { byte_6E803C = 1; v5 = time64(0); } dword_6E8040 = v5 + 1; v6 = (signed int)(Size + 9) % 8; v7 = 8 - v6; v17 = 8 - v6 + Size + 9; v8 = malloc(v17); v9 = (_DWORD **)a3; *(_DWORD *)a3 = v8; if ( v8 ) memcpy(v8, Src, Size); v10 = 0; v11 = 0x6583463; if ( (signed int)Size > 0 ) { do { v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++); v11 += v12; } while ( v10 < (signed int)Size ); v9 = (_DWORD **)a3; } *(_WORD *)((char *)*v9 + Size) = v11; *((_BYTE *)*v9 + Size + 2) = BYTE2(v11); v13 = dword_6E8040; *((_BYTE *)*v9 + Size + 3) = HIBYTE(v11); *(_WORD *)((char *)*v9 + Size + 4) = v13; *(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040); *((_BYTE *)*v9 + Size + 8) = v7; for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 ) v15 = (int)*v9 + i++; sub_4D9710(*v9, v17 / 4, a5); *(_DWORD *)a4 = v17; return 1; }Hello atom0s
Although the important thing here is that scemino understands the encryption process. I would also like to understand it, so that I can help scemino in what I can. And I don't know how to do it.
I see this encryption function and there are things I don't understand. And others that perhaps if you have understood them, therefore:
- The parameters that the function receives, I think they would be the following, correct me if I'm wrong:
a3 -> target data a4 -> target data size a5 -> encryption key
- I see two variables:
byte_6E803C dword_6E8040
These are not defined within the body of the function:
char __cdecl sub_4D94A0 (void * Src, size_t Size, int a3, int a4, int a5)
What do they contain or how would they be defined?
Could you pass a real encryption example like the one you passed for decryption?
In any case, thank you very much for your help.
Cheers
No need, the encryption works for me as well.
But thanks anyway.
I have all the information I need for the moment.
from engge.
Hello atom0s
Although the important thing here is that scemino understands the encryption process. I would also like to understand it, so that I can help scemino in what I can. And I don't know how to do it.
I see this encryption function and there are things I don't understand. And others that perhaps if you have understood them, therefore:
- The parameters that the function receives, I think they would be the following, correct me if I'm wrong:
a3 -> target data a4 -> target data size a5 -> encryption key
- I see two variables:
byte_6E803C dword_6E8040
These are not defined within the body of the function:
char __cdecl sub_4D94A0 (void * Src, size_t Size, int a3, int a4, int a5)
What do they contain or how would they be defined?
Could you pass a real encryption example like the one you passed for decryption?
In any case, thank you very much for your help.
Cheers
Sure.
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5)
The parameters here are:
- Src - Is the input data to be processed.
- Size - Is the size of the input data.
- a3 - Output buffer pointer to hold the processed data.
- a4 - Output buffer size.
- a5 - The encryption key pointer.
byte_6E803C is just used as a flag to determine if something has happened yet. In this case, it's used to tell if a 'baseline' timestamp has been created to be used to timestamp file data. This is added to the end of the file data as a 'footer' for the data. The end chunk before the encryption is happening is where that footer is being created:
v11 = 0x6583463;
if ( (signed int)Size > 0 )
{
do
{
v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++);
v11 += v12;
}
while ( v10 < (signed int)Size );
v9 = (_DWORD **)a3;
}
*(_WORD *)((char *)*v9 + Size) = v11;
*((_BYTE *)*v9 + Size + 2) = BYTE2(v11);
v13 = dword_6E8040;
*((_BYTE *)*v9 + Size + 3) = HIBYTE(v11);
*(_WORD *)((char *)*v9 + Size + 4) = v13;
*(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040);
*((_BYTE *)*v9 + Size + 8) = v7;
for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 )
v15 = (int)*v9 + i++;
The first part (the while loop chunk) is creating a 'hash'-like number based on the data. Then that is written to this header as a means to validate the data for being tampered with.
dword_6E8040 is just the timestamp counter used. That's just initialized with time64(0) then incremented each time its used, by 1.
The way the overall encryption handler works is based on the size passed to it.
void __cdecl sub_4D9710(_DWORD *a1, signed int a2, int a3)
a2 handles the size of the data, which the function uses to determine if you are trying to encrypt or decrypt the data.
// Decryption uses negative size values..
sub_4D9710(v7, (signed int)Size / -4, a5);
// Encryption uses positive size values..
sub_4D9710(*v9, v17 / 4, a5);
Using the example decryption code I gave above you can recreate the encryption in reverse as needed. You can calculate the hash using:
int32_t v10 = 0;
int32_t v11 = 0x6583463;
int32_t v12 = 0;
do
{
v12 = *(uint8_t*)&data[v10++];
v11 += v12;
} while (v10 < size - 16);
// v11 now contains the hash that will be put in the footer..
The timestamp part doesn't really matter and is solely used for visuals from the look of it. The decryption function doesn't check it for anything, so it is just used to see the date of the save in-game.
The last 8 bytes are just repeated as a marker from the look of things as well. Decryption doesn't seem to check that either.
for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 )
v15 = (int)*v9 + i++;
v7 here is calculated from the size:
v6 = (signed int)(Size + 9) % 8;
v7 = 8 - v6;
So if the data size (without the footer) is: 0x7A120
Then we get: 8 - ((0x7A120 + 9) % 8)
or 0x07
So then our footer would look like this:
80 31 23 07 29 C1 9A 5A 07 07 07 07 07 07 07 07
80 31 23 07 - The 'hash' of the data.
29 C1 9A 5A - The 'timestamp' of the data.
07 07 07 07 07 07 07 07 - The repeated last byte.
After this is calculated the encryption function is called. For decryption, the reverse is done where the data is decrypted, the footer is validated, then the data is processed. The decryption function only seems to validate the hash though, the timestamp and repeated 8 bytes are ignored from the look of things.
from engge.
Hello atom0s
Although the important thing here is that scemino understands the encryption process. I would also like to understand it, so that I can help scemino in what I can. And I don't know how to do it.
I see this encryption function and there are things I don't understand. And others that perhaps if you have understood them, therefore:
- The parameters that the function receives, I think they would be the following, correct me if I'm wrong:
a3 -> target data a4 -> target data size a5 -> encryption key
- I see two variables:
byte_6E803C dword_6E8040
These are not defined within the body of the function:
char __cdecl sub_4D94A0 (void * Src, size_t Size, int a3, int a4, int a5)
What do they contain or how would they be defined?
Could you pass a real encryption example like the one you passed for decryption?
In any case, thank you very much for your help.
CheersSure.
char __cdecl sub_4D94A0(void *Src, size_t Size, int a3, int a4, int a5)The parameters here are:
* Src - Is the input data to be processed. * Size - Is the size of the input data. * a3 - Output buffer pointer to hold the processed data. * a4 - Output buffer size. * a5 - The encryption key pointer.
byte_6E803C is just used as a flag to determine if something has happened yet. In this case, it's used to tell if a 'baseline' timestamp has been created to be used to timestamp file data. This is added to the end of the file data as a 'footer' for the data. The end chunk before the encryption is happening is where that footer is being created:
v11 = 0x6583463; if ( (signed int)Size > 0 ) { do { v12 = *(unsigned __int8 *)(*(_DWORD *)a3 + v10++); v11 += v12; } while ( v10 < (signed int)Size ); v9 = (_DWORD **)a3; } *(_WORD *)((char *)*v9 + Size) = v11; *((_BYTE *)*v9 + Size + 2) = BYTE2(v11); v13 = dword_6E8040; *((_BYTE *)*v9 + Size + 3) = HIBYTE(v11); *(_WORD *)((char *)*v9 + Size + 4) = v13; *(_WORD *)((char *)*v9 + Size + 6) = HIWORD(dword_6E8040); *((_BYTE *)*v9 + Size + 8) = v7; for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 ) v15 = (int)*v9 + i++;The first part (the while loop chunk) is creating a 'hash'-like number based on the data. Then that is written to this header as a means to validate the data for being tampered with.
dword_6E8040 is just the timestamp counter used. That's just initialized with time64(0) then incremented each time its used, by 1.
The way the overall encryption handler works is based on the size passed to it.
void __cdecl sub_4D9710(_DWORD *a1, signed int a2, int a3)a2 handles the size of the data, which the function uses to determine if you are trying to encrypt or decrypt the data.
// Decryption uses negative size values.. sub_4D9710(v7, (signed int)Size / -4, a5); // Encryption uses positive size values.. sub_4D9710(*v9, v17 / 4, a5);Using the example decryption code I gave above you can recreate the encryption in reverse as needed. You can calculate the hash using:
int32_t v10 = 0; int32_t v11 = 0x6583463; int32_t v12 = 0; do { v12 = *(uint8_t*)&data[v10++]; v11 += v12; } while (v10 < size - 16); // v11 now contains the hash that will be put in the footer..The timestamp part doesn't really matter and is solely used for visuals from the look of it. The decryption function doesn't check it for anything, so it is just used to see the date of the save in-game.
The last 8 bytes are just repeated as a marker from the look of things as well. Decryption doesn't seem to check that either.
for ( i = 0; i < v7; *(_BYTE *)(v15 + Size + 9) = v7 ) v15 = (int)*v9 + i++;v7 here is calculated from the size:
v6 = (signed int)(Size + 9) % 8; v7 = 8 - v6;
So if the data size (without the footer) is: 0x7A120
Then we get:
8 - ((0x7A120 + 9) % 8)
or 0x07So then our footer would look like this:
80 31 23 07 29 C1 9A 5A 07 07 07 07 07 07 07 07
80 31 23 07 - The 'hash' of the data.
29 C1 9A 5A - The 'timestamp' of the data.
07 07 07 07 07 07 07 07 - The repeated last byte.After this is calculated the encryption function is called. For decryption, the reverse is done where the data is decrypted, the footer is validated, then the data is processed. The decryption function only seems to validate the hash though, the timestamp and repeated 8 bytes are ignored from the look of things.
Now it has become clear to me ... it has been a magnificent explanation :)
I'm going to put it into practice.
Thank you very much atom0s !!!
from engge.
Oh ok. Sorry @Mac1512 you were right, this is something very interesting.
Good work @atom0s
from engge.
Oh ok. Sorry @Mac1512 you were right, this is something very interesting.
Good work @atom0s
No problem @scemino :)
The truth is that the information that atomos is providing is very useful and very interesting.
In fact, I had seen those data when decrypting the Saves.dat file and I have observed later, after the clarification given by atom0s, that it is also found in the saved games.
At first I didn't know what that data was. Now I see that they are a few extra bytes, to include information, like a footer :)
the timestamp saved here is the same as the one saved in the "savetime" field of the decrypted json file (of the saved games), adding a 1.
Encryption still doesn't work for me, when I compiled it threw me an exception, for lack of time I haven't been able to test more. I will make some modifications to make it work, as much as possible
again thank you very much @atom0s for your clarifications.
Cheers
Edited:
I have had to make a small modification since there are no certain macros, such as BYTE2 ... Now I can say that it works perfect :)
from engge.
Wow, is this feature ready for testing?
from engge.
I didn't get there in time to let you know before you released release 0.5.0.
At least in windows, there is a problem with saves:
[2020-04-28 08: 29: 47.101] [log] [warning] Invalid savegame: Savegame1.save
[2020-04-28 08: 29: 47.101] [log] [error] Sorry, an error occurred: This is not an hashtable
Although I have not been able to test it, I am convinced that it does not save the hash well, that happened to me, when I compiled the function that I provide atom0s; the calculated hash value is correct, but the saving was incorrect when storing the int value, making it byte by byte. Besides, you have to discount the space that this hash occupies plus the saving time and repeated characters ... as explained by atom0s. Since saved games take up more than they should.
Saves Original = 500016 bytes
Saves engge = 502056 bytes
I see there is a difference of 2040 bytes.
So it makes me think that there is a mistake when coding.
Due to lack of time, I cannot yet decode the saves and see what is specifically being stored and thus be able to give you more accurate data.
If I can I will prove it today in the afternoon.
Cheers
from engge.
Ok I removed the release.
Can you tell me what did you do exactly ? And can you send me your savegame ?
This is odd, I saved a game and I have exactly 500 016 bytes with engge, and I can load it again (tested on macOS).
Thank you
from engge.
Hello,
You will see three folders, one of them is called:
With 0.5.0 github
In this are the save, from the version you uploaded, you will see that it has a size of 501922 bytes.
There are two other folders called 1 and 2, these are created from commit 0f09c4f, which corresponds to version release 0.5.0
the saves occupy in this case: 501997 bytes and 501924 bytes, respectively.
From what I see every time the saved value changes ...
The saved game that I commented on the issue, I overwritten it by mistake, but from what I see it does not matter, since the data varies in all.
The proof of this I do as follows:
I start the program without the engge.nut file, so I start a game from scratch, and the first time the game is auto-saved, I quit the game (by the way the icons on the upper right do not appear to be able to quit the game, what I look for the calculation and the menu to exit appears ...), when starting again engge in the part of verifying the game, it closes and the error message appears in the log (the start screen is not seen).
Today I don't have time to try anything else ... I hope this can help you.
I pass the link with the saves
http://www.mediafire.com/file/e8dle3bh98uygq4/0.5.0_save_fail.7z/file
from engge.
Thank @Mac1512 I got the same errors with your given savegames.
I did the exact same things, I started a new game, I have the same issue: it is not possible from that point to go to the settings menu (gear icon), but the savegame is correct and I can load it if I restart engge.
I compared your json savegame to mine, and it's almost the same (except some values due to the random number generator and the language english vs spanish).
So I started once again in spanish, but It still succeeds, and the json savegames are almost identical.
I suppose it comes from the encode/decode method which must have a different behavior on Windows.
So here is my proposition, I try to change this method to make it more cross-platform, I create a commit and you will tell me if it works for you @Mac1512
Anyway thank you for your feedback, it's really helpful.
You are a great Windows and spanish tester ;)
from engge.
Thank you for your comments :)
I do it to learn and I would like this project to come to fruition xD
I have tried the last commit (fa36c40) and same error.
I pass you a link to mediafire, inside there are two compressed files, in one are the logs, one of them is from the beginning when the game is auto saved and another log of the failure. I send you the saved game of this test too.
In the other file, the source code (main.cpp) of the modification I made, of the functions of atom0s, goes so that the encoding in windows would work. with this come the following files:
Save.dat -> original Save.dat (216 bytes)
Save.json -> Decoded Save.dat (216 bytes)
Savepre.json -> Save.json without time index, hash ... (203 bytes)
Save.enc -> Result of encoding Savepre.json with the main.cpp that I passed you, is exactly the same as Save.dat
For convenience, I pass you the adapted main.cpp to generate a file equal to Save.dat (216 bytes). But the function is exactly the same as for saved games, only that they are in the ggpack format, and I use an intermediate program to pass them from json to ggpack.
This main.cpp can be improved, but I made it functional and left it like that xD
I hope it works for you and here I am to try anything on windows, within the possibilities of time
The link is as follows:
http://www.mediafire.com/file/z7eg9lep0iwvhjw/save_plus_encode_windows.7z/file
Cheers
from engge.
I have been doing some tests and I think that the failure may be in the order in which linux or mac store the data, perhaps it is big-endian and windows on the contrary little endian ...
Although I have not looked at the functions you use in engge to save saved games, when I tried to decode the saves generated by engge, with my decoding function for windows, even though they take up more than normal, that is not why it is due, the data I get is illegible, that is, I don't get the decoded ggpack file that I should get.
With the original saves, using my decoding function for windows, it does it right, I get the decoded ggpack file.
I pass you my original save, without decoding and decoding, are the files:
Savegame_orig.save -> without decoding
Savegame_orig.dec -> decoded
and the files:
Savegame1.save -> without decoding, commit (fa36c40)
Savegame1.dec -> Decoded (Unreadable Data)
Savegame2.save -> without decoding, commit (0f09c4f)
Savegame2.dec -> Decoded (Unreadable Data)
Download link:
http://www.mediafire.com/file/7jctn9vajhz5wfm/twp_saves_decode_fail.7z/file
from engge.
I can't read Savegame1.save and Savegame2.save, I'm able to read Savegame_orig.save.
I have to check on Windows, it will take me more time :(
from engge.
OK I found it, I changed my commit, it should be good now with the the commit aa672e2
Can you confirm this @Mac1512 please ?
It it's ok for you I will release the version v0.5.0-alpha
from engge.
Wow, now it works great :)
Well, I have been testing and the saved games are going well, in fact I have tried original and saved games directly by engge.
About the 0.5.0 release, because of the saved games if you can launch it ... although I see a series of errors that you should raise whether to solve them before releasing the 0.5.0 release or later for a 0.6.0 release.
I detail some of them, and depending on the decision you make, they put themselves in new issues:
- The icons on the right side do not appear ... (although this does not happen in all parts)
- Reyes appears with a hat if the game is loaded from the beginning, although this hat disappears as he speaks.
-3. In the dialogue between Ray and Reyes, if Ray is the one who talks to Reyes, the options for dialogue do not appear, but if it is Reyes who talks to Ray if ...
I suppose there are more ... but in what little I have tried, I have at least seen these.
from engge.
Reyes appears with a hat if the game is loaded from the beginning, although this hat disappears as he speaks.
Why the hat?
from engge.
I think it must be an error when storing any of the variables of the saved game. I guess the costume is not setting up correctly.
from engge.
from engge.
It seems perfect :)
from engge.
-3. In the dialogue between Ray and Reyes, if Ray is the one who talks to Reyes, the options for dialogue do not appear, but if it is Reyes who talks to Ray if ...
What do you mean exactly ? if I select Ray, I can't talk to Reyes, right ?
from engge.
Yes, exactly
In fact the action gets to be executed, but the dialogue texts do not appear if it is Ray who speaks with Reyes, in part 2 (The Body). I have tried to click equally on certain parts of the screen but nothing has happened. On the other hand, if it is Reyes, who talks to Ray, it works normally.
There is still a bug in the script, I had opened this issue: (#123), I think that now the two script bugs do not occur, but I have to do more tests about
from engge.
Related Issues (20)
- Tutorial system still buggy
- Implement Help menus
- Savegame corrupted if game is saved in Ransome trailer
- In close up screens we should not see verb's text
- StartScreen does not look like TWP
- Replace SFML audio
- Save game is slow
- Don't overwrite autosave
- Some texts are not positioned correctly
- Crash when Ransome is trying to remove his makeup
- Impossible to give tube WC-67
- When loading a game, some object should be here
- Notebook has incorrect name
- Ransome can't use trampoline
- Missing animation in Trailer alley
- Weird camera movement
- Support Big Endian HOT 1
- Read ggpack in memory and avoid use of seekg HOT 1
- Pigeon brothers wheels are invisible HOT 8
- Question: since TP is not open source HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from engge.