Quake Champions

Extraction and unpacking of game archives and compression, encryption, obfuscation, decoding of unknown files
theli
Posts: 3
Joined: Sat Dec 29, 2018 7:57 pm

Re: Quake Champions

Post by theli »

I wrote a simple app that bruteforces a seed:

Code: Select all

#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <cstring>
#include <omp.h>

const static uint32_t magic = 0x06064b50;
const static uint32_t entry_magic = 0x02014b50;

#pragma pack(push, 1)
typedef struct {
    /*
    findloc OFFSET binary "PK\x06\x06" 0 "" LIMIT
    math OFFSET + 4
    goto OFFSET
    get ZERO byte
    get central_entries longlong
    get central_size longlong
    get central_offset longlong
    get DUMMY_offset longlong
    */
    uint32_t magic;
    uint8_t unk;
    uint64_t central_entries;
    uint64_t central_size;
    uint64_t central_offset;
    uint64_t dummy_offset;
} main_header_t;

typedef struct {
    /*
        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
            */
    uint32_t magic;
    uint16_t ver_made;
    uint16_t ver_need;
    uint16_t flag;
    uint16_t method;
    uint16_t modtime;
    uint16_t moddate;
    uint32_t zip_crc;
    uint32_t comp_size;
    uint32_t uncomp_size;
    uint16_t name_len;
    uint16_t extra_len;
    uint16_t comm_len;
    uint16_t disknum;
    uint16_t int_attr;
    uint32_t ext_attr;
    uint32_t rel_offset;
} entry_header_t;
#pragma pack(pop)

class QCDecrypt {
    private:
    uint64_t u, v, w;

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

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

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

    uint64_t    qc_seed;
    uint8_t    qc_ivec[32];
    uint16_t   qc_seed_idx;
    uint16_t   qc_ivec_idx;

    public:
    void quake_decrypt_init(uint8_t *key, uint64_t seed2) {
        unsigned int i;
        for(i = 0; i < sizeof(qc_ivec); i++) {
            qc_ivec[i] = key[i];
        }
        qc_seed = *(uint64_t *)(key);
        qc_seed_idx = 0;
        qc_ivec_idx = 0;
        NrRandom(qc_seed ^ seed2);
    }

    QCDecrypt(uint8_t *key, uint seed2) {
        quake_decrypt_init(key, seed2);
    }
    int quake_decrypt(uint8_t *data, uint8_t* output, int size) {
        for(int i = 0; i < size; i++) {
            uint8_t c = data[i];
            uint8_t old = qc_ivec[qc_ivec_idx];
            qc_ivec[qc_ivec_idx] = c;
            output[i] = (qc_seed_idx ? 0 : qc_seed) ^ c ^ old;
            qc_ivec_idx = (qc_ivec_idx + 1) & 0x1f;
            if(++qc_seed_idx == 8) {
                qc_seed = NextUInt64();
                qc_seed_idx = 0;
            }
        }
        return size;
    }
};

using namespace std;
std::string string_to_hex(const std::string& input)
{
    static const char* const lut = "0123456789ABCDEF";
    size_t len = input.length();

    std::string output;
    output.reserve(2 * len);
    for (size_t i = 0; i < len; ++i)
    {
        const unsigned char c = input[i];
        output.push_back(lut[c >> 4]);
        output.push_back(lut[c & 15]);
    }
    return output;
}
int main(int argc, char *argv[]) {
    (void)argc;
    char* fname = argv[1];
    cout << "Opening file:" << fname << endl;
    ifstream ifile(fname, ios::in | ios::binary);
    ifile.seekg(-40, ios_base::end);
    array<uint8_t, 40> key;
    ifile.read((char*)key.data(), key.size());
    cout << "key:" << string_to_hex((char*)key.data()) << endl;

    array<uint8_t, 0x4000> haystack;
    ifile.seekg(-0x4000, ios_base::end);
    ifile.read((char*)haystack.data(), haystack.size());
    array<uint8_t, 4> needle = {'P', 'K', 0x06, 0x06};

    auto it = std::search (haystack.begin(), haystack.end(), needle.begin(), needle.end());

    main_header_t header;
    memcpy((char*)&header, it, sizeof(header));

    cout << "sizeof entry header " << hex << sizeof(entry_header_t) << dec << endl;
    cout << "Header has " << header.central_entries << " entries"<<endl;

    array<uint8_t, 0x400> buffer;
    ifile.seekg(header.central_offset, ios_base::beg);
    ifile.read((char*)buffer.data(), buffer.size());



    std::atomic_uint32_t progress;
    std::atomic_bool found;

#pragma omp parallel
    {
       
        unsigned int percent = 0;
        int nthreads, tid;
        uint32_t seed;
        tid = omp_get_thread_num();
        nthreads = omp_get_num_threads();
        uint32_t per_thread = 0xFFFFFFFF / nthreads;
        uint32_t from = per_thread * tid;
        //from = 0x412e2206;
        //from = 0x631A2028;
        //from = 0x0F10C856F;
        //from = 0x0;
        //from = 8041504;
        //
        //from = 0x6F01BCCC;
        //from = 0x6EA00000;
        uint32_t to = (tid == nthreads - 1 ? 0xFFFFFFFF : per_thread * (tid+1));

#pragma omp critical
                {
                    cout << "Thread " << dec << tid;
                    cout << " From   " << hex << from;
                    cout << " To   " << hex << to << dec << endl;
                }

        QCDecrypt qc(key.data(), 0);
        array<char, 0x4000> tmp;
        for (seed = from; !found && seed < to; ++seed) {
            qc.quake_decrypt_init(key.data(), seed);

            bool all_match = true;
            unsigned int index = 0;
            for (int i = 0; i < 3; ++i) {
                entry_header_t *encrypted_entry;
                entry_header_t entry;
                encrypted_entry = (entry_header_t*)(buffer.data() + index);
                index += sizeof(entry_header_t);
                qc.quake_decrypt((uint8_t*)encrypted_entry, (uint8_t*)&entry, sizeof(entry_header_t));
                if (entry.name_len + entry.extra_len + entry.comm_len + index > buffer.size()) {
                    all_match = false;
                    break;
                }
                char *name = (char*)(buffer.data() + index);
                qc.quake_decrypt((uint8_t*)name, (uint8_t*)tmp.data(), entry.name_len);
                if (!all_of(tmp.data(), tmp.data()+entry.name_len, [] (char c) -> bool {return isprint(c);})) {
                    all_match = false;
                    break;
                }
                index += entry.name_len;
                index += entry.extra_len;
                index += entry.comm_len;
#if 0
                name = (char*)tmp.data();
#pragma omp critical
                {
                cout <<"entry:"<<  i << endl;
                cout <<"name:"<< string_to_hex(name) << endl;
                cout <<"name:"<< name << endl;
                cout <<"name_len:"<<  entry.name_len << endl;
                cout <<"magic:"<<  hex << entry.magic << dec << endl;
                }
#endif
                if (entry.magic != entry_magic) {
                    all_match = false;
                    break;
                }
            }

            if (all_match) {
                found = true;
#pragma omp critical
                {
                    cout << "++++++++++++++++++++++++++++++++" << endl;
                    cout << "Found seed: " << hex << seed<< dec << endl;
                    cout << "++++++++++++++++++++++++++++++++" << endl;
                }
                break;
            }
            ++progress;
            if ( tid == 0 && percent < progress / (0xFFFFFFFF / 100)) {
#pragma omp critical
                cout << "Progress: " << dec << progress / (0xFFFFFFFF / 100) << "%" << endl;
                percent ++;
            }
        }
    }

}


It takes about 4-5 minutes on my i7 to find seed for current steam version: 0xbfe7184d
I've verified it by unpacking some files with quickbms and this seed
Its stupid, but hey, you don't have to babysit debugger and risk a ban to find it.
h3x3r
Posts: 165
Joined: Wed Jun 01, 2016 5:53 pm

Re: Quake Champions

Post by h3x3r »

You can be banned for debugging? Really? Maybe only if there is a VAC function. Anyway can you please compile that app a post it here? And thanks for seed!
theli
Posts: 3
Joined: Sat Dec 29, 2018 7:57 pm

Re: Quake Champions

Post by theli »

h3x3r wrote:You can be banned for debugging? Really? Maybe only if there is a VAC function.

I haven't heard of those happening, but you never know, there is always a possibility.

h3x3r wrote:Anyway can you please compile that app a post it here? And thanks for seed!

Sure, attached windows binary.
Though I haven't tested it on a real windows machine. But it did work in wine.
Linux users can just compile it with

Code: Select all

g++ --std=c++11 find_seed.cpp -fopenmp -O3 -o find_seed



Btw, my original intention of unpacking paks was to find shaders the game uses. Though I haven't been able to find nor source code nor precompiled shaders anywhere. I assume now that they are packed somewhere inside those files that are packed inside paks ... (matreshka, huh?). Does anyone know where does the game store them?
henriksen
Posts: 1
Joined: Thu Jan 17, 2019 1:31 pm

Re: Quake Champions

Post by henriksen »

Is anyone having audio from the game? I'm looking for Hulshult music after summer update. Maybe there's a way to extract it?
SunPraiser
Posts: 1
Joined: Thu Apr 04, 2019 9:15 am

Re: Quake Champions

Post by SunPraiser »

theli wrote:Sure, attached windows binary.


Tried the app and got this instantly:

Code: Select all

find_seed.exe main_menu.pak

Opening file:main_menu.pak
key:920D35E467C22A4D652BB63102E0A6FA58F675E68C192A2D94129A449680295131500644064B5331C8AF4D
sizeof entry header 2e
Header has 133 entries
Thread 0 From   0 To   3fffffff
Thread 2 From   7ffffffe To   bffffffd
Thread 1 From   3fffffff To   7ffffffe
Thread 3 From   bffffffd To   ffffffff
++++++++++++++++++++++++++++++++
Found seed: ceed
++++++++++++++++++++++++++++++++
Thread 0 Done
Thread 1 Done
Thread 3 Done
Thread 2 Done


What did I do wrong?
Jwells
Posts: 1
Joined: Sun May 10, 2020 4:08 pm

Re: Quake Champions

Post by Jwells »

Can anyone give an update on how to extract the shared.pak folder? The brute force app is not working and neither does the bms script. I want to be able to access the music files in the game, does anyone have an existing download of the ripped files or a way to access the pak files as of 2020?
bbrk
Posts: 2
Joined: Fri Aug 14, 2020 9:51 am

Re: Quake Champions

Post by bbrk »

SunPraiser wrote:
theli wrote:Sure, attached windows binary.


Tried the app and got this instantly:

Code: Select all

find_seed.exe main_menu.pak

Opening file:main_menu.pak
key:920D35E467C22A4D652BB63102E0A6FA58F675E68C192A2D94129A449680295131500644064B5331C8AF4D
sizeof entry header 2e
Header has 133 entries
Thread 0 From   0 To   3fffffff
Thread 2 From   7ffffffe To   bffffffd
Thread 1 From   3fffffff To   7ffffffe
Thread 3 From   bffffffd To   ffffffff
++++++++++++++++++++++++++++++++
Found seed: ceed
++++++++++++++++++++++++++++++++
Thread 0 Done
Thread 1 Done
Thread 3 Done
Thread 2 Done


What did I do wrong?


Program works with old inital.pak, you can check by downloading one on previous pages, but not with current
bbrk
Posts: 2
Joined: Fri Aug 14, 2020 9:51 am

Re: Quake Champions

Post by bbrk »

quickbms and find_seed helps to extract files from old paks, but not on new.
I'm interested in champions sounds, want to use 'em as notification sounds on my phone, maybe someone helps?
Shakes
Posts: 5
Joined: Sun Aug 16, 2020 12:50 pm

Re: Quake Champions

Post by Shakes »

bbrk wrote:quickbms and find_seed helps to extract files from old paks, but not on new.
I'm interested in champions sounds, want to use 'em as notification sounds on my phone, maybe someone helps?

They both work for me? The decrypt seed hasn't changed for quite some time now. It's been CEED (not a typo) for over a year now. Heck, I can open the current PTS paks up with the script from earlier with that seed.

Anyways, shameless plug, I have all the champion sounds (and other goodies) ripped already here to OGG format:
https://knockout.chat/thread/9182/1
(models are still mostly ninjarips, sadly)

Unfortunately I could not find where the music itself was. If anyone knows the specific pak and the file within said pak where the game's music is stored, I would be much obliged. All I've been able to find are voices and SFX.

Also, does anyone here know if it's safe to go into a custom match with NinjaRipper? It's safe running just in the menus, but I want to nab some world models and I don't want my account with 1300 hours to get flagged for running it in a match.

Edit: Made an alt about a month ago to rip world models. AFAIK it hasn't been banned yet even after ripping multiple times so it seems that NR is okay to use, but obviously do so at your own risk.
Last edited by Shakes on Thu Mar 04, 2021 8:12 pm, edited 1 time in total.
hakeryk
Posts: 2
Joined: Thu Mar 04, 2021 6:48 pm

Re: Quake Champions

Post by hakeryk »

Hey folks. I am new in this thread but I tried almost everything to get this working on latest QC (2021.03.04) (and QC PTS) version but I failed.

1) find_seed app is not even asking me for file name - it just run for few minutes with 100% cpu usage with "Opening file:" and it quits. Even when started directly cmd with admin privelages.
2) I tested all of the BMS from this thread and tried the "CEED" seed and "ceed" as well - non of them works. Every file that is outputed is just rubish with 300 kb.

Any idea how to open these newest files? :(

Windows 10, 20H2
Shakes
Posts: 5
Joined: Sun Aug 16, 2020 12:50 pm

Re: Quake Champions

Post by Shakes »

hakeryk wrote:Hey folks. I am new in this thread but I tried almost everything to get this working on latest QC (2021.03.04) (and QC PTS) version but I failed.

1) find_seed app is not even asking me for file name - it just run for few minutes with 100% cpu usage with "Opening file:" and it quits. Even when started directly cmd with admin privelages.
2) I tested all of the BMS from this thread and tried the "CEED" seed and "ceed" as well - non of them works. Every file that is outputed is just rubish with 300 kb.

Any idea how to open these newest files? :(

Windows 10, 20H2

The syntax for the seed finder is just find_seed.exe <pak name>.pak, but the seed itself hasn't changed since forever as I've been able to dump the new PTS archives with the new assets. It might be of help to see what version of QuickBMS you're using? The version I've been using (for QC's files, at least) that has worked consistently is 0.10.1 with a build date of "Oct 20 2019 - 14:45:05".

Also, I don't remember exactly which script from previous replies or threads was the one I started with, but this is the one I've been using (not taking credit for it, I just swapped in the most recent seed):

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 ^ 0xCEED; 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;
}
"
# PK.R.2
# 50 4b c4 52 db 32

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
hakeryk
Posts: 2
Joined: Thu Mar 04, 2021 6:48 pm

Re: Quake Champions

Post by hakeryk »

Now it works with your code! :) Thanks to You I extracted initial.pak :) You are the hero.

Yet I still don't know how to open or convert these .pct files with Raw Texture Cooker. Can't find proper settings but I will try.
Shakes
Posts: 5
Joined: Sun Aug 16, 2020 12:50 pm

Re: Quake Champions

Post by Shakes »

hakeryk wrote:Now it works with your code! :) Thanks to You I extracted initial.pak :) You are the hero.

Yet I still don't know how to open or convert these .pct files with Raw Texture Cooker. Can't find proper settings but I will try.

Typically, the game's textures follow a pattern.

  • Diffuse/albedo, "dir" (tangent/directional maps) and CC textures (paint masks) are almost always in format BC7.
  • Normals and "spec" textures (really combined metal/roughness) are always BC5U.
  • The "em" textures are emission/glow textures which can be either of two formats: BC7 with AO in the alpha channel, or BC4 when it's just AO, and you can usually tell which it is by the data size of the file (BC4 will be half the size of a BC7 texture with the same resolution).
  • HDETM textures are masks for the small tiled detail textures and are always in BC4.
  • Gradient textures (the ones with _g at the end) are BC7 for some reason despite all being grayscale.
  • I'm fairly certain cubemaps are in BC6 floating-point HDR so there's not a lot of hope of using any of these.
  • There are a few misc effect textures that use non-power-of-2 resolutions or BC1/BC3 compression formats. UI elements are frequently guilty of the former.

To find the exact starting offset, look for the first instance of a six-byte sequence: FF 00 xx xx xx 00; the image data starts at the next byte following this sequence.
A 2048*2048 BC7/BC5 texture typically hovers around 5.6 MB, and the texture portion starts at 0x90. For each step up or down in vertical resolution, the start typically changes by 8 bytes. So for example, a 4096*4096 texture would start at offset 0x98, but a 4096*2048 texture would usually still start at 0x90. So...

...a 4096*4096 BC7 is 22.4 MB and starts at 0x98, so a texture of 11.2 MB would either be 2048*4096, 4096*2048, or 4096*4096 in BC4.
...a 2048*2048 BC7 is 5.6 MB and starts at 0x90
...a 1024*1024 BC7 is 1.4 MB and starts at 0x88
...a 512*512 BC7 is 350 KB and starts at 0x80
...etc., etc.

Make sure to uncheck "autodetect size" as this is usually incorrect anyways.
Kevin04
Posts: 2
Joined: Thu Apr 22, 2021 4:58 pm

Re: Quake Champions

Post by Kevin04 »

Shakes wrote:Unfortunately I could not find where the music itself was. If anyone knows the specific pak and the file within said pak where the game's music is stored, I would be much obliged. All I've been able to find are voices and SFX.

I'm pretty sure you at least suspected as much, but from what I gathered with my really limited skillset in this area, the music seems to be in initial.pak/[sound]/desktop/master_bank.sndbundle (or the desktopsurround equivalent). There are at least some kind of track lists fairly early in there if you search for "music" with a hex editor. I have no idea how to actually use the file though, but maybe this little info helps someone who's more knowledgeable.

EDIT: I also want to mention that someone recently uploaded a gamerip on YouTube, extracted from the paks according to the comments: https://www.reddit.com/r/QuakeChampions ... oundtrack/
Shakes
Posts: 5
Joined: Sun Aug 16, 2020 12:50 pm

Re: Quake Champions

Post by Shakes »

Kevin04 wrote:I'm pretty sure you at least suspected as much, but from what I gathered with my really limited skillset in this area, the music seems to be in initial.pak/[sound]/desktop/master_bank.sndbundle (or the desktopsurround equivalent). There are at least some kind of track lists fairly early in there if you search for "music" with a hex editor. I have no idea how to actually use the file though, but maybe this little info helps someone who's more knowledgeable.

I suspected that from the filesize and seeing RIFF in the file while using a hex editor, but I don't know how people extracted it in the first place. The author of qcpak (iOrange over on Xentax) might know a bit about it since there were old rips with all of the (then current) soundtracks in it which I presume people got with that tool, back before most of the sounds were converted to OGG.

Kevin04 wrote:EDIT: I also want to mention that someone recently uploaded a gamerip on YouTube, extracted from the paks according to the comments: https://www.reddit.com/r/QuakeChampions ... oundtrack/

I did see that. More than likely this is a rip someone did of a much older pak file, seeing as it doesn't have the music from Exile which was added fairly recently.
Kevin04
Posts: 2
Joined: Thu Apr 22, 2021 4:58 pm

Re: Quake Champions

Post by Kevin04 »

Shakes wrote:I suspected that from the filesize and seeing RIFF in the file while using a hex editor, but I don't know how people extracted it in the first place. The author of qcpak (iOrange over on Xentax) might know a bit about it since there were old rips with all of the (then current) soundtracks in it which I presume people got with that tool, back before most of the sounds were converted to OGG.

I've looked into it once more and while I didn't figure out very much myself, you pointed me in the right direction mentioning Xentax. I ultimately got to the following page, the description there still works and the files are online: https://web.archive.org/web/20180104124 ... audio.html (last archived working version)

For my purposes it's enough to run the provided BMS on master_bank.sndbundle (which for that file essentially removes everything before the string FSB5, up to 0x00043A58). The resulting fsb file can be read by vgmstream, just like the sound bank files from the shared pak. That's all I personally need out of it. I can tag, listen or really do pretty much anything else I want directly with my foobar2000 setup now.

Please note that converting to wav using fsb_aud_extr.exe, as described on the archived page, will overwrite some generated files due to duplicate stream titles (the Hulshult/Vrenna variants use the same names). At the moment, there are 52 tracks in total, but an unattended conversion using that method results in only 31. Whatever way you used to process the bank files will probably work fine here as well, though.
Shakes
Posts: 5
Joined: Sun Aug 16, 2020 12:50 pm

Re: Quake Champions

Post by Shakes »

Kevin04 wrote:I've looked into it once more and while I didn't figure out very much myself, you pointed me in the right direction mentioning Xentax. I ultimately got to the following page, the description there still works and the files are online: https://web.archive.org/web/20180104124 ... audio.html (last archived working version)

For my purposes it's enough to run the provided BMS on master_bank.sndbundle (which for that file essentially removes everything before the string FSB5, up to 0x00043A58). The resulting fsb file can be read by vgmstream, just like the sound bank files from the shared pak. That's all I personally need out of it. I can tag, listen or really do pretty much anything else I want directly with my foobar2000 setup now.

Please note that converting to wav using fsb_aud_extr.exe, as described on the archived page, will overwrite some generated files due to duplicate stream titles (the Hulshult/Vrenna variants use the same names). At the moment, there are 52 tracks in total, but an unattended conversion using that method results in only 31. Whatever way you used to process the bank files will probably work fine here as well, though.

That did it. The old method I was using (same BMS script but a python script to get the audio out of it) worked, but files were overwritten as you mentioned. With vgmstream I was able to extract each individual track, though I had to rename them manually which involved a bit of guesswork. I also couldn't find the track for Exile even though it's referenced in the sndbundle file, which seemed odd.