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.