Quake Champions

Extraction and unpacking of game archives and compression, encryption, obfuscation, decoding of unknown files
buk0wski
Posts: 8
Joined: Sat Jun 16, 2018 10:53 pm

Re: Quake Champions

Post by buk0wski »

Thanks for your help mate, highly appreciated. But even after successfully removing the steam encryption w/ steamless (nice hint btw, didn't know this exists), the mentioned constants are nowhere to find :( Well, except for a heckton of 0x15 and 0x23 matches that didn't really get me any further

If they changed all those values, I guess I would have to find the algorithm only by recognizing the corresponding assembly op codes. Which (besides sounding crazy) I doubt is even possible since they depend on the used compiler and its settings, if I am not mistaken?

aluigi wrote:Worst case scenario dump the memory and do it the raw way :)

Will do this.
I think there could be an additional reverse engineering protection because the disassembled steamless output still has some "random byte" areas that shouldn't be there.

Can you recommend a decent memdump debugging tool? I didn't really like the few I "worked" with so far and the windows-implemented techniques are not that practical either

-----------------------------------------------------

EDIT: Windows task manager DMP file of the process didn't include the values either.... something strange is going on here
buk0wski
Posts: 8
Joined: Sat Jun 16, 2018 10:53 pm

Re: Quake Champions

Post by buk0wski »

Found it in the ram! Finally.

This is the actual nextUInt64() func:
(The comments are refering to the the code in http://aluigi.org/bms/quake_champions.bms)

Code: Select all

mov r8,rcx                  
mov rcx,27BB2EE687B0B0FD { -2018463491 }   
mov rax,[r8]      // ulong U
imul rax,rcx         // U *= 2862933555777941757ULL   
mov rcx,61C8864680B583BF    
add rax,rcx         // U += 7046029254386353087ULL
mov [r8],rax      // save U                  
mov rdx,[r8+08]      // RDX = ulong V
shr rdx,11          // V >> 17
xor rdx,[r8+08]      // ^= V
mov rcx,rdx               
shl rcx,1F         // V << 31   
xor rcx,rdx         // ^= V
mov edx,FFFFDA61    // 4294957665U   
mov rax,rcx               
shr rax,08 { 8 }      // V >> 8
xor rax,rcx         // ^= V
mov [r8+08],rax      // save V
mov rcx,[r8+10]      // RCX = ulong W
mov eax,ecx
imul rdx,rax         // ???!!!!
shr rcx,20 { 32 }      // W >> 32   
add rdx,rcx         // W + ?!
mov [r8+10],rdx      // save W
mov rcx,[r8]      // RCX = ulong X (= U)                  
shl rcx,15 { 21 }      // U << 21               
xor rcx,[r8]         // ^ U = X
mov rax,rcx         // RAX = X               
shr rax,23 { 35 }      // X >> 35
xor rcx,rax         // ^= X
mov rax,rcx         // RAX = RCX   
shl rax,04 { 4 }      // X << 4
xor rax,rcx         // ^= X
add rax,[r8+08]      // (x + v)
xor rax,rdx         // ^ w
ret

I tried to analyze it as good as I possibly could. I am lacking a lot of practice tho, as you may see

If I am not mistaken, they changed the line

Code: Select all

w = 4294957665U * (w & 0xffffffff) + (w >> 32);

to something like

Code: Select all

v ^= v >> 17; 
ulong a = v;      // rdx?
v ^= v << 31;
v ^= v >> 8;
ulong b = v;       // rax?
w = (a * b) + (w >> 32);   // a * b -> imul rdx, rax??

But what is happing to the 4294957665U constant?

This is a bit over my head actually, can someone back me up here please?

EDIT: This is the NrRandom() function, which contains iterations of NextUInt64() since NrRandom() calls it a few times:

Code: Select all

mov rax,38ECAC5FB3251641 { -1289415103 }
mov r8,rcx
mov [rcx+08],rax
mov qword ptr [rcx+10],00000001 { 1 }
mov rax,[QuakeChampions.NvOptimusEnablement+4A77E0] { [148F2BB98F0] }
test rax,rax
je QuakeChampions.apProcessExceptionDllCall+1D99A8
xor rdx,[rax]
mov rax,[rcx+08]
mov r11,27BB2EE687B0B0FD { -2018463491 }
xor rax,rdx
mov r10,61C8864680B583BF { -2135587905 }
imul rax,r11
mov r9d,FFFFDA61 { -9631 }
add rax,r10
mov [rcx],rax
mov rcx,[rcx+08]
shr rcx,11 { 17 }
xor rcx,[r8+08]
mov rdx,rcx
shl rdx,1F { 31 }
xor rdx,rcx
mov rax,rdx
shr rax,08 { 8 }
xor rax,rdx
mov [r8+08],rax
mov rcx,[r8+10]
mov eax,ecx
imul rax,r9
shr rcx,20 { 32 }
add rax,rcx
mov [r8+10],rax
mov rax,[r8]
mov [r8+08],rax
mov rax,[r8]
imul rax,r11
add rax,r10
mov [r8],rax
mov rcx,[r8+08]
shr rcx,11 { 17 }
xor rcx,[r8+08]
mov rdx,rcx
shl rdx,1F { 31 }
xor rdx,rcx
mov rax,rdx
shr rax,08 { 8 }
xor rax,rdx
mov [r8+08],rax
mov rcx,[r8+10]
mov eax,ecx
imul rax,r9
shr rcx,20 { 32 }
add rax,rcx
mov [r8+10],rax
mov rax,[r8+08]
mov [r8+10],rax
mov rax,[r8]
imul rax,r11
add rax,r10
mov [r8],rax
mov rcx,[r8+08]
shr rcx,11 { 17 }
xor rcx,[r8+08]
mov rdx,rcx
shl rdx,1F { 31 }
xor rdx,rcx
mov rax,rdx
shr rax,08 { 8 }
xor rax,rdx
mov [r8+08],rax
mov rcx,[r8+10]
mov eax,ecx
imul rax,r9
shr rcx,20 { 32 }
add rax,rcx
mov [r8+10],rax
ret

I've recently learned that r9 = r9d, so 4294957665U is in fact multiplied with W?

EDIT2: Here's everything I found in the memory (each function seperated, still messy): https://pastebin.com/5TMBEQWS
throwaway6346435
Posts: 1
Joined: Sun Jul 08, 2018 12:24 pm

Re: Quake Champions

Post by throwaway6346435 »

Encryption pseudocode:

Code: Select all

signed __int64 __fastcall sub_141562250(__int64 a1, __int64 a2)
{
  __int64 v2; // r8@1
  __int64 v3; // rcx@3
  signed __int64 result; // rax@3

  v2 = a1;
  *(_QWORD *)(a1 + 8) = 4101842887655102017i64;
  *(_QWORD *)(a1 + 16) = 1i64;
  if ( qword_142F99BE8 )
    a2 ^= *(_QWORD *)qword_142F99BE8;
  *(_QWORD *)a1 = 2862933555777941757i64 * (a2 ^ *(_QWORD *)(a1 + 8)) + 7046029254386353087i64;
  v3 = *(_QWORD *)(a1 + 8) ^ (*(_QWORD *)(a1 + 8) >> 17);
  *(_QWORD *)(v2 + 8) = v3 ^ (v3 << 31) ^ ((v3 ^ (unsigned __int64)(v3 << 31)) >> 8);
  *(_QWORD *)(v2 + 16) = (*(_QWORD *)(v2 + 16) >> 32) + 4294957665i64 * (unsigned int)*(_QWORD *)(v2 + 16);
  *(_QWORD *)(v2 + 8) = *(_QWORD *)v2;
  *(_QWORD *)v2 = 2862933555777941757i64 * *(_QWORD *)v2 + 7046029254386353087i64;
  *(_QWORD *)(v2 + 8) ^= (*(_QWORD *)(v2 + 8) >> 17) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17)) << 31) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17)) << 31)) >> 8);
  *(_QWORD *)(v2 + 16) = (*(_QWORD *)(v2 + 16) >> 32) + 4294957665i64 * (unsigned int)*(_QWORD *)(v2 + 16);
  *(_QWORD *)(v2 + 16) = *(_QWORD *)(v2 + 8);
  *(_QWORD *)v2 = 2862933555777941757i64 * *(_QWORD *)v2 + 7046029254386353087i64;
  *(_QWORD *)(v2 + 8) ^= (*(_QWORD *)(v2 + 8) >> 17) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17)) << 31) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17) ^ ((*(_QWORD *)(v2 + 8) ^ (*(_QWORD *)(v2 + 8) >> 17)) << 31)) >> 8);
  result = (*(_QWORD *)(v2 + 16) >> 32) + 4294957665i64 * (unsigned int)*(_QWORD *)(v2 + 16);
  *(_QWORD *)(v2 + 16) = result;
  return result;
}
int __fastcall sub_141561CB0(__int64 a1, signed __int64 a2)
{
  unsigned __int64 v2; // rdi@1
  __int64 v3; // rbx@1
  __int64 v4; // rax@2
  unsigned __int64 v5; // r10@2
  __int64 v6; // r8@2
  unsigned __int64 v7; // rdx@2
  signed __int64 v8; // r9@2
  unsigned __int64 v9; // rdx@3
  __int128 v10; // xmm1@6
  __int64 v11; // rdx@6
  __int64 v13; // [sp+0h] [bp-48h]@6
  __int64 v14; // [sp+20h] [bp-28h]@2
  unsigned __int64 v15; // [sp+28h] [bp-20h]@2
  unsigned __int64 v16; // [sp+30h] [bp-18h]@2
  __int64 v17; // [sp+38h] [bp-10h]@6

  v2 = 0i64;
  v3 = a1;
  *(_DWORD *)(a1 + 184) = 0;
  if ( a2 )
  {
    v6 = a1 + 112;
    *(_OWORD *)v6 = *(_OWORD *)a2;
    *(_OWORD *)(v6 + 16) = *(_OWORD *)(a2 + 16);
  }
  else
  {
    LODWORD(v4) = time64(0i64);
    sub_141562250((__int64)&v14, v4);
    v5 = v16;
    v6 = v3 + 112;
    v7 = v15;
    v8 = v14;
    do
    {
      v5 = 4294957665i64 * (unsigned int)v5 + (v5 >> 32);
      v9 = (((v7 >> 17) ^ v7) << 31) ^ (v7 >> 17) ^ v7;
      v7 = (v9 >> 8) ^ v9;
      v8 = 2862933555777941757i64 * v8 + 7046029254386353087i64;
      *(_BYTE *)(v6 + v2++) = v5 ^ (v7
                                  + (((v8 ^ (unsigned __int64)(v8 << 21)) >> 35) ^ v8 ^ 16
                                                                                      * (((v8 ^ (unsigned __int64)(v8 << 21)) >> 35) ^ v8)));
    }
    while ( v2 < 0x20 );
    a2 = v3 + 112;
  }
  *(_OWORD *)(v3 + 144) = *(_OWORD *)a2;
  v10 = *(_OWORD *)(a2 + 16);
  v11 = *(_QWORD *)v6;
  *(_QWORD *)(v3 + 176) = *(_QWORD *)v6;
  *(_OWORD *)(v3 + 160) = v10;
  sub_141562250(v3 + 192, v11);
  return sub_14211B3B0((unsigned __int64)&v13 ^ v17);
}


Tried cleaning it a little bit, still a long way to go, got bored, may be it will help someone:

Code: Select all

signed __int64 __fastcall sub_141562250(__int64 a1, __int64 a2)
{
  __int64 u; // r8@1
  __int64 v3; // rcx@3
  signed __int64 result; // rax@3

  u = a1;
  v = 4101842887655102017i64;
  w = 1i64;
  if ( qword_142F99BE8 )
    a2 ^= qword_142F99BE8;
  u = 2862933555777941757i64 * (a2 ^ 4101842887655102017i64) + 7046029254386353087i64;
  v3 = 4101842887655102017i64 ^ (4101842887655102017i64 >> 17);
  v = v3 ^ (v3 << 31) ^ ((v3 ^ (v3 << 31)) >> 8);
  w = (w >> 32) + 4294957665i64 * (w & 0xffffffff);
  v = u;
  u = 2862933555777941757i64 * u + 7046029254386353087i64;
  v ^= (v >> 17) ^ ((v ^ (v >> 17)) << 31) ^ ((v ^ (v >> 17) ^ ((v ^ (v >> 17)) << 31)) >> 8);
  w = (w >> 32) + 4294957665i64 * (w & 0xffffffff);
  w = v;
  u = 2862933555777941757i64 * u + 7046029254386353087i64;
  v ^= (v >> 17) ^ ((v ^ (v >> 17)) << 31) ^ ((v ^ (v >> 17) ^ ((v ^ (v >> 17)) << 31)) >> 8);
  result = (w >> 32) + 4294957665i64 * (w & 0xffffffff);
  w = result;
  return result;
}
int __fastcall sub_141561CB0(__int64 a1, signed __int64 a2)
{
  unsigned __int64 v2; // rdi@1
  __int64 v3; // rbx@1
  __int64 now; // rax@2
  unsigned __int64 v5; // r10@2
  __int64 v6; // r8@2
  unsigned __int64 v7; // rdx@2
  signed __int64 v8; // r9@2
  unsigned __int64 v9; // rdx@3
  __int128 v10; // xmm1@6
  __int64 v11; // rdx@6
  __int64 v13; // [sp+0h] [bp-48h]@6
  __int64 v14; // [sp+20h] [bp-28h]@2
  unsigned __int64 v15; // [sp+28h] [bp-20h]@2
  unsigned __int64 v16; // [sp+30h] [bp-18h]@2
  __int64 v17; // [sp+38h] [bp-10h]@6

  v2 = 0i64;
  v3 = a1;
  *(_DWORD *)(a1 + 184) = 0;
  if ( a2 )
  {
    v6 = a1 + 112;
    *(_OWORD *)v6 = *(_OWORD *)a2;
    *(_OWORD *)(v6 + 16) = *(_OWORD *)(a2 + 16);
  }
  else
  {
    LODWORD(now) = time64(0i64);
    sub_141562250((__int64)&v14, now);
    w = v16;
    v6 = v3 + 112;
    v = v15;
    u = v14;
    do
    {
      w = 4294957665i64 * (w & 0xffffffff) + (w >> 32);
      v9 = (((v >> 17) ^ v) << 31) ^ (v >> 17) ^ v;
      v = (v9 >> 8) ^ v9;
      u = 2862933555777941757i64 * u + 7046029254386353087i64;
      *(_BYTE *)(v6 + v2++) = w ^ (v + (((u ^ (u << 21)) >> 35) ^ u ^ 16 * (((u ^ (u << 21)) >> 35) ^ u)));
    }
    while ( v2 < 0x20 );
    a2 = v3 + 112;
  }
  *(_OWORD *)(v3 + 144) = *(_OWORD *)a2;
  v10 = *(_OWORD *)(a2 + 16);
  v11 = *(_QWORD *)v6;
  *(_QWORD *)(v3 + 176) = *(_QWORD *)v6;
  *(_OWORD *)(v3 + 160) = v10;
  sub_141562250(v3 + 192, v11);
  return sub_14211B3B0((unsigned __int64)&v13 ^ v17);
}
buk0wski
Posts: 8
Joined: Sat Jun 16, 2018 10:53 pm

Re: Quake Champions

Post by buk0wski »

I already went through all of this. Regarding the constants and the u,v,w variables, everything is exactly the same, but they added a function call in the decryption code:
if(++qc_seed_idx == 8) {
qc_seed = NextUInt64();
qc_seed_idx = 0;
}
//somewhere here they added....

E8 B699BB00 - call QuakeChampions.Scaleform::Render::Matrix4x4<double>::Transpose+3D4490 // this

and maybe some other Init Vector stuff (I guess) beforehand, including a time function and "security cookie" calls (which are not included in my pastebin since I found them later). The 4x4Matrix appears to be a shifting substitution box of some kind, obviously returning a double.

There are in fact several variations of this RNG where constant floats/doubles are multiplied to the NextUInt64() output, but I found none that includes a 4x4 matrix and thus a variable double value.

However it COULD be possible to reverse engineer the happenings in the box as well of course, but that's where my motivation, spare time and also capabilities reached its limits.

-> Probably easier to hook the running exe and go from there, reading out buffers or memcpy seeds, whatever. Maybe even call the matrix function from the outside. I am not that experienced in this field, but you get my point I guess

ah and those pointer dereferences to NvOptimusEnablement are also questionable. I dont even remember where it led to, as some weeks has passed
QuakeChampions.apProcessExceptionDllCall+1D9999 - 48 8B 05 5843A301 - mov rax,[QuakeChampions.NvOptimusEnablement+4A77E0] { [148F2BB98F0] }


//no guarantee of correctness
speciose
Posts: 1
Joined: Thu Aug 02, 2018 3:59 pm

Re: Quake Champions

Post by speciose »

Have anyone reached encrypted files?
z65536
Posts: 11
Joined: Thu Aug 30, 2018 3:13 pm

Re: Quake Champions

Post by z65536 »

it is decrypted with this code.

Code: Select all

    void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x412e2206; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }
buk0wski
Posts: 8
Joined: Sat Jun 16, 2018 10:53 pm

Re: Quake Champions

Post by buk0wski »

z65536 wrote:it is decrypted with this code.

Code: Select all

    void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x412e2206; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }

Thanks a lot, works now.
Feel kinda stupid tho. How did you find it out?
z65536
Posts: 11
Joined: Thu Aug 30, 2018 3:13 pm

Re: Quake Champions

Post by z65536 »

buk0wski wrote:
z65536 wrote:it is decrypted with this code.

Code: Select all

    void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x412e2206; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }

Thanks a lot, works now.
Feel kinda stupid tho. How did you find it out?

I used a debugger.

these are function NrRandom and quake_decrypt

Code: Select all

signed __int64 __fastcall sub_141666740(_QWORD *a1, __int64 a2)
{
  _QWORD *v2; // r8
  __int64 v3; // rcx
  signed __int64 result; // rax

  v2 = a1;
  a1[1] = 4101842887655102017i64;
  a1[2] = 1i64;
 
  if ( qword_1430FB778 )
    a2 ^= *(_QWORD *)qword_1430FB778;     //As this lines was added, I set a breakpoint here

  *a1 = 2862933555777941757i64 * (a2 ^ a1[1]) + 7046029254386353087i64;
  v3 = a1[1] ^ (a1[1] >> 17);
  v2[1] = v3 ^ (v3 << 31) ^ ((v3 ^ (unsigned __int64)(v3 << 31)) >> 8);
  v2[2] = (v2[2] >> 32) + 4294957665i64 * (unsigned int)v2[2];
  v2[1] = *v2;
  *v2 = 2862933555777941757i64 * *v2 + 7046029254386353087i64;
  v2[1] ^= (v2[1] >> 17) ^ ((v2[1] ^ (v2[1] >> 17)) << 31) ^ ((v2[1] ^ (v2[1] >> 17) ^ ((v2[1] ^ (v2[1] >> 17)) << 31)) >> 8);
  v2[2] = (v2[2] >> 32) + 4294957665i64 * (unsigned int)v2[2];
  v2[2] = v2[1];
  *v2 = 2862933555777941757i64 * *v2 + 7046029254386353087i64;
  v2[1] ^= (v2[1] >> 17) ^ ((v2[1] ^ (v2[1] >> 17)) << 31) ^ ((v2[1] ^ (v2[1] >> 17) ^ ((v2[1] ^ (v2[1] >> 17)) << 31)) >> 8);
  result = (v2[2] >> 32) + 4294957665i64 * (unsigned int)v2[2];
  v2[2] = result;
  return result;
}


Code: Select all

__int16 __fastcall sub_141665F70(__int64 a1, _BYTE *a2, __int64 a3)
{
  __int64 v3; // r11
  _BYTE *v4; // r10
  __int64 i; // r9
  __int64 v6; // rcx
  char v7; // r8
  __int16 v8; // ax
  signed __int64 v9; // rax
  bool v10; // zf
  signed __int64 v11; // rdx

  v3 = a3;
  v4 = a2;
  for ( i = a1; v3; --v3 )
  {
    v6 = *(unsigned __int16 *)(i + 186);
    v7 = *(_BYTE *)(v6 + i + 144);
    *(_BYTE *)(v6 + i + 144) = *v4;
    *v4 ^= v7 ^ *(_BYTE *)(i + 176) & (unsigned __int64)(255i64 << 8 * (unsigned __int8)*(_WORD *)(i + 184));
    v8 = *(_WORD *)(i + 186);
    ++*(_WORD *)(i + 184);
    LOWORD(v9) = ((_BYTE)v8 + 1) & 0x1F;
    v10 = *(_WORD *)(i + 184) == 8;
    *(_WORD *)(i + 186) = v9;
    if ( v10 )
    {
      *(_QWORD *)(i + 192) = 2862933555777941757i64 * *(_QWORD *)(i + 192) + 7046029254386353087i64;
      *(_QWORD *)(i + 200) ^= (*(_QWORD *)(i + 200) >> 17) ^ ((*(_QWORD *)(i + 200) ^ (*(_QWORD *)(i + 200) >> 17)) << 31) ^ ((*(_QWORD *)(i + 200) ^ (*(_QWORD *)(i + 200) >> 17) ^ ((*(_QWORD *)(i + 200) ^ (*(_QWORD *)(i + 200) >> 17)) << 31)) >> 8);
      v11 = (*(_QWORD *)(i + 208) >> 32) + 4294957665i64 * *(unsigned int *)(i + 208);
      *(_QWORD *)(i + 208) = v11;
      v9 = v11 ^ (*(_QWORD *)(i + 200)
                + (((*(_QWORD *)(i + 192) ^ (*(_QWORD *)(i + 192) << 21)) >> 35) ^ *(_QWORD *)(i + 192) ^ (*(_QWORD *)(i + 192) << 21) ^ 16i64 * (((*(_QWORD *)(i + 192) ^ (*(_QWORD *)(i + 192) << 21)) >> 35) ^ *(_QWORD *)(i + 192) ^ (*(_QWORD *)(i + 192) << 21))));
      *(_WORD *)(i + 184) = 0;
      *(_QWORD *)(i + 176) = v9;
    }
    ++v4;
  }
  return v9;
}
buk0wski
Posts: 8
Joined: Sat Jun 16, 2018 10:53 pm

Re: Quake Champions

Post by buk0wski »

Thanks again, I got it.

They changed the constant in the latest patch. New, working code below.

Code: Select all

void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x631A2028; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }
exhawk
Posts: 4
Joined: Sat Sep 08, 2018 3:30 am

Re: Quake Champions

Post by exhawk »

Can someone extract bj's voice lines or share script to do it myself? please
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Quake Champions

Post by aluigi »

I have just updated the script in case someone wants to test it:
http://aluigi.org/bms/quake_champions.bms

The only sample file I have here uses an older constant so I didn't test it.
OXOTHuK
Posts: 2
Joined: Tue Mar 20, 2018 6:14 pm

Re: Quake Champions

Post by OXOTHuK »

I will post my results here.
This is my first time using QuickBMS, so let me know if I'm doing something incorrectly.

I pointed QuickBMS to the shared.pak file (using the Steam version of Quake Champions).
[ steamapps\common\quakechampions\client\preload\paks\shared.pak ]
After selecting the output, my console output shows this:

Code:
insert the constant number for NrRandom initialization, it depends by the version of the game like 0x412e2206 and 0x631A2028

- please insert the content for the variable SEED2:

What do I do?
h3x3r
Posts: 165
Joined: Wed Jun 01, 2016 5:53 pm

Re: Quake Champions

Post by h3x3r »

Seems like they update it again. None of them works.
Rev3n4nt
Posts: 7
Joined: Wed Jun 06, 2018 7:46 pm

Re: Quake Champions

Post by Rev3n4nt »

buk0wski wrote:They changed the constant in the latest patch. New, working code below.

Code: Select all

void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x631A2028; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }

Thanks buk0wski, I wonder if it's possible to get constant for the now version? Can you write a script, or sort of crack\keygen like mini-app that gets that constant? Or just say where to look while debugging - if any of those options is possible, please note :)
z65536
Posts: 11
Joined: Thu Aug 30, 2018 3:13 pm

Re: Quake Champions

Post by z65536 »

My script that can extract shared.pak is here.

Code: Select all

# Quake Champions
# script for QuickBMS http://quickbms.aluigi.org

quickbmsver "0.8.0"
set MEMORY_FILE10 string "
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long long ulong;

    // Numerical Recipes 3rd edition
    static ulong u, v, w;

    ulong NextUInt64()
    {
      u = u * 2862933555777941757ULL + 7046029254386353087ULL;
      v ^= v >> 17; v ^= v << 31; v ^= v >> 8;
      w = 4294957665U * (w & 0xffffffff) + (w >> 32);
      ulong x = u ^ (u << 21); x ^= x >> 35; x ^= x << 4;
      return (x + v) ^ w;
    }

    void NrRandom(ulong seed)
    {
      v = 4101842887655102017ULL;
      w = 1;

      u = v ^ seed ^ 0x631A2028; NextUInt64();
      v = u;                     NextUInt64();
      w = v;                     NextUInt64();
    }

// Quake Champions
static ulong    qc_seed = 0;
static uchar    qc_ivec[32] = {0};
static ushort   qc_seed_idx = 0;
static ushort   qc_ivec_idx = 0;

void quake_decrypt_init(uchar *key) {
   int     i;
   for(i = 0; i < sizeof(qc_ivec); i++) {
      qc_ivec[i] = key[i];
   }
   qc_seed = *(ulong *)key;
   qc_seed_idx = 0;
   qc_ivec_idx = 0;
   NrRandom(qc_seed);
}

int quake_decrypt(unsigned char *data, int size) {
    int     i;
    for(i = 0; i < size; i++) {
        uchar old = qc_ivec[qc_ivec_idx];
        qc_ivec[qc_ivec_idx] = data[i] ;
        data[i] ^= (qc_seed_idx == 0 ? qc_seed :0) ^ old ;
        qc_ivec_idx = (qc_ivec_idx + 1) & 0x1f;
        if(++qc_seed_idx == 8) {
            qc_seed = NextUInt64();
            qc_seed_idx = 0;
        }
    }
    return size;
}
"

goto -40
getdstring KEY 40
calldll MEMORY_FILE10 "quake_decrypt_init" "tcc" RET KEY
findloc OFFSET binary "PK\x06\x06" 0 "" 0
goto OFFSET
idstring "PK\x06\x06"
   get ZERO byte
   get central_entries longlong
   get central_size longlong
   get central_offset longlong
   get DUMMY_offset longlong

goto central_offset
for i = 0 < central_entries

    savepos TMP
    log MEMORY_FILE TMP 0x2e
    calldll MEMORY_FILE10 "quake_decrypt" "tcc" RET MEMORY_FILE 0x2e
    goto 0x2e 0 SEEK_CUR

    goto 0 MEMORY_FILE
    idstring MEMORY_FILE "PK\x01\x02"
        get ver_made        short MEMORY_FILE
        get ver_need        short MEMORY_FILE
        get flag            short MEMORY_FILE
        get method          short MEMORY_FILE
        get modtime         short MEMORY_FILE
        get moddate         short MEMORY_FILE
        get zip_crc         long MEMORY_FILE
        get comp_size       long MEMORY_FILE
        get uncomp_size     long MEMORY_FILE
        get name_len        short MEMORY_FILE
        get extra_len       short MEMORY_FILE
        get comm_len        short MEMORY_FILE
        get disknum         short MEMORY_FILE
        get int_attr        short MEMORY_FILE
        get ext_attr        long MEMORY_FILE
        get rel_offset      long MEMORY_FILE

    savepos TMP
    log MEMORY_FILE TMP name_len
    calldll MEMORY_FILE10 "quake_decrypt" "tcc" RET MEMORY_FILE name_len
    goto name_len 0 SEEK_CUR

    goto 0 MEMORY_FILE
        getdstring name     name_len MEMORY_FILE

        # not encrypted
        getdstring extra    extra_len
        getdstring comment  comm_len

      if extra_len >= 12
         getvarchr extra_id extra 0 short
         if extra_id == 0x0001
            if rel_offset == 0xffffffff
               getvarchr rel_offset extra 4 longlong
            endif
         endif
      endif
      
    math offset = 0x1e
    math offset + rel_offset

        if method == 0
            Log name offset uncomp_size # same as comp_size
            #Log name offset comp_size   # was uncomp_size before AES
        else
            if method == 8
                ComType deflate
            else
                print "unsupported compression method %method%"
                cleanexit
            endif
            CLog name offset comp_size uncomp_size
        endif
next i
Rev3n4nt
Posts: 7
Joined: Wed Jun 06, 2018 7:46 pm

Re: Quake Champions

Post by Rev3n4nt »

z65536 Thanks for the effort, but it's not working with current Quake Champions version on Steam.
It still asks for SEED2, constants: 0x631A2028 OR 0x412e2206 apparently are not working for my latest version on Steam. I assume seed/constant changes vor newer versions, that's why I asked how to get those variables in post above.
z65536
Posts: 11
Joined: Thu Aug 30, 2018 3:13 pm

Re: Quake Champions

Post by z65536 »

Rev3n4nt wrote:z65536 Thanks for the effort, but it's not working with current Quake Champions version on Steam.
It still asks for SEED2, constants: 0x631A2028 OR 0x412e2206 apparently are not working for my latest version on Steam. I assume seed/constant changes vor newer versions, that's why I asked how to get those variables in post above.

Really?
I redownloaded Quake Champions today. this script still working.
peterzhenhh
Posts: 45
Joined: Wed Jan 17, 2018 5:15 pm

Re: Quake Champions

Post by peterzhenhh »

It works! THX!
Rev3n4nt
Posts: 7
Joined: Wed Jun 06, 2018 7:46 pm

Re: Quake Champions

Post by Rev3n4nt »

z65536
Really?
I redownloaded Quake Champions today. this script still working.

My bad. You're right, it works. Something was on my side: I deleted all Quake Champions files and redownloaded - extracts as it should. Thanks :)
Rev3n4nt
Posts: 7
Joined: Wed Jun 06, 2018 7:46 pm

Re: Quake Champions

Post by Rev3n4nt »

I used unpacker script, and got all files out.
Models are seems to be *.TPL files(at least static ones).
Textures are *.PCT
from Xentax forum:
GRiNDERKILLER wrote:
About TPL files they have a string S3DRESOURCE means SABER 3D RESOURCE. MESH maybe?... I tried H2O but no luck. Since i still don't know where starts VTBlock and which size has.

GRiNDERKILLER wrote:
PCT are only textures. TCIP header means PICTURE. Btw textures are easily convertable by Rawtex. You must just set start offset of raw data + sometimes guess compression. But mostly they are BC7 - RGB/sRGB

I used Ninja Ripper for extracting weapon models & textures before - back then UV's were ripped fine, now they're mess. Since size is same(tris&vertex) in new version, I upload old ripped model with textures for comparison and maybe help to recognize new format.
http://www.mediafire.com/file/2fg1bci3hi6dr6m/qc_rge_ripp.zip/file
If anybody figure out how to read\convert models and textures files into common models and textures format with keeping UV's for models - I upload models and textures for that as well.
http://www.mediafire.com/file/t88wuha6ps44qcg/qc_rge_textures.zip/file
http://www.mediafire.com/file/c3e9vfxrs9elc6p/qc_rge_tpl.zip/file