flowlib in QCRACK

looking at the file structure

Posted by eric on March 23, 2017

I wasn’t getting much farther after my last effort to reverse the QCRACK binary, so I decided to look at the other files that QCRACK needs; flowlib.dir and flowlib.lib.

Loading flowlib.lib into a hex editor shows it begins with what looks like the game names, ones used in the -g option of QCRACK. After that are matching full names of the games. There are 24 each.

Games:

"doom2"
"ultdm95"
"master"
"hexen"
"heretic"
"wolf3d"
"heretc13"
"death"
"hexen11"
"doom2_95"
"doom_se"
"finaldos"
"final"
"quake"
"quake1"
"quake2"
"quake3"
"quake4"
"quake5"
"quake6"
"quake7"
"quake8"
"quake9"
"fake"

Full names:

"id Software: Doom II"
"id Software: The Ultimate Doom for Windows 95"
"id Software: Master Levels"
"id Software: HEXEN"
"id Software: Heretic"
"id Software: Wolfenstein 3d"
"id Software: Heretic: Shadow of the Serpent Rider"
"id Software: HEXEN: Deathkings of the Dark Citadel"
"id Software: HEXEN 1.1"
"id Software: Doom II for Windows 95"
"id Software: Doom Special Edition - The Ultimate Doom"
"id Software: Final Doom for DOS"
"id Software: Final Doom for Windows 95"
"id Software: Quake 1.01 - 0"
"id Software: Quake 1.01 - 1"
"id Software: Quake 1.01 - 2"
"id Software: Quake 1.01 - 3"
"id Software: Quake 1.01 - 4"
"id Software: Quake 1.01 - 5"
"id Software: Quake 1.01 - 6"
"id Software: Quake 1.01 - 7"
"id Software: Quake 1.01 - 8"
"id Software: Quake 1.01 - 9"
"fake"

After that is binary data that I left alone for the moment.

Looking at flowlib.dir, it’s a smaller file that just seems to list file names.

file names in hex editor showing the cascade file names in hex editor showing the cascade

I noticed the file names cascade suggesting there’s a constant size for each. There’s 24 bytes between the beginning of each file name, and an extra four bytes at the start of the file. At the end of each entry are non-printable-ASCII values, with non-zero at the left of 4-byte sections, I’m guessing they are little-endian integers. There can be three of these ints, leaving 12 bytes for the file name.

Looking at the first two entries:

45 58 45 2e 31 37 00 00 00 00 00 00 00 a1 fe c1 00 00 00 00 f3 00 00 00
50 52 4f 44 55 43 54 53 2e 31 37 00 00 e8 c8 f4 f3 00 00 00 24 03 00 00

The first entry ends with f3, and in the second entry the second to last int is the same value of f3. This would fit if the last int was file size and second to last int was offset, which also fits with the first entry having offset of zero.

I wrote code to extract these file entries.

#include <stdint.h>

struct flowlibdir_entry {
    char file_name[13];
    int32_t something;
    int32_t offset;
    int32_t size;
};

#include <stdio.h>
#include <string.h>

void flowlibdir(void) {
    FILE* fp;
    struct flowlibdir_entry entries[1000];
    int num_entries = 0;

    if((fp = fopen("flowlib.dir", "rb")) == NULL) {
        printf("can't open flowlib.dir\n");
        return;
    }

    int num_objs_read;

    int thing;

    num_objs_read = fread(&thing, 4, 1, fp);
    char c;
    while((c=getc(fp)) != EOF && num_entries <= 1000) {
        ungetc(c, fp);
        num_objs_read = fread(&entries[num_entries], 12, 1, fp);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for file name %s as expected:%d\n",
                   entries[num_entries].file_name,
                   num_objs_read);
        }
        entries[num_entries].file_name[12] = '\0';

        num_objs_read = fread(&entries[num_entries].something, 4, 1, fp);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for something as expected:%d\n", num_objs_read);
        }

        num_objs_read = fread(&entries[num_entries].offset, 4, 1, fp);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for offset as expected:%d\n", num_objs_read);
        }

        num_objs_read = fread(&entries[num_entries].size, 4, 1, fp);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for size as expected:%d\n", num_objs_read);
        }

        num_entries++;
    }

    if(num_entries >= 1000) {
        printf("reached max entries\n");
    }

    printf("num_entries=%d\n", num_entries);
    printf("thing=%d\n", thing);
    int i;
    for(i = 0; i < num_entries; ++i) {
        printf("%s %d %d %d\n",
               entries[i].file_name,
               entries[i].something,
               entries[i].offset,
               entries[i].size);

    }
}

This gives the following output:

num_entries=270
thing=270
EXE.17 -1040277248 0 243
PRODUCTS.17 -188160000 243 804
FLOW.TOB -629997568 1047 2096
FLOW.TOK 6744064 3143 25917
FLOW.TXT -956268032 29060 26196
FLOWWORK.TXT 2005530368 55256 29211
BACK_DN.BMP -114617088 84467 3198
BACK_GR.BMP -107151360 87665 2824
BACK_HI.BMP -1961135104 90489 3198
BACK_LO.BMP -1549108480 93687 2824
BACKUP.BMP 1381061376 96511 59150
BIGQ.BMP -853934080 155661 118836
BK_DN.BMP -1575764480 274497 26166
BK_HI.BMP 331343872 300663 26166
BK_LO.BMP -1610058752 326829 25282
BK1_DN.BMP -2085966592 352111 20002
BK1_HI.BMP -24436480 372113 20002
BK1_LO.BMP 1229324800 392115 20042
BK2_DN.BMP -352321536 412157 19256
BK2_HI.BMP 331284480 431413 19256
BK2_LO.BMP -2144978176 450669 19218
BK3_DN.BMP 38111232 469887 20896
BK3_HI.BMP 66687232 490783 20896
BK3_LO.BMP -259069952 511679 20890
BOUGHT.BMP -1231008000 532569 45794
BQ_HI.BMP 1583308032 578363 16602
BQ_LO.BMP -1017598720 594965 16434
BT1_HI.BMP -1338573056 611399 14314
BT1_LO.BMP -320143104 625713 12406
BT1BK.BMP 179156480 638119 32642
BT2_HI.BMP -1375402496 670761 14080
BT2_LO.BMP 1465012480 684841 12794
BT2BK.BMP 1089110016 697635 18402
BTEXT.BMP -973034240 716037 15444
BTEXT2.BMP 230272256 731481 17444
C16.BMP 1899921408 748925 32862
CALL.BMP 1587347456 781787 13238
CALL2.BMP -1106907392 795025 13090
CALL3.BMP -1492617984 808115 13770
CALL4.BMP -1207702784 821885 13272
CALL5.BMP 1354981120 835157 13514
CONGRAT.BMP 1081344000 848671 38922
DEAHOLS.BMP -234835968 887593 40898
DMHOLS.BMP 27750656 928491 44504
DMSEHOLS.BMP 1482381568 972995 38326
ERROR.BMP -1958448640 1011321 34176
EXIT_DN.BMP 1235729152 1045497 2156
EXIT_GR.BMP -1993943552 1047653 2016
EXIT_HI.BMP -1660753152 1049669 2156
EXIT_LO.BMP 16352000 1051825 2016
EXITBOX.BMP 179118336 1053841 19792
FAKE.BMP 1973284864 1073633 1112
FINDOS.BMP 1525589760 1074745 44514
FINHOLS.BMP -1528710656 1119259 44316
FSK_DN.BMP -391470592 1163575 30100
FSK_HI.BMP -1509222912 1193675 30100
FSK_LO.BMP 132683520 1223775 30406
GREAT.BMP 330611712 1254181 12278
HERHOLS.BMP -387564288 1266459 37022
HEXHOLS.BMP 1958804992 1303481 39034
HOW_DN.BMP -1662467328 1342515 4186
HOW_HI.BMP -223352576 1346701 4186
HOW_LO.BMP 521011968 1350887 3900
HOWTO.BMP 851648768 1354787 12266
IDLOGO1.BMP 1977289216 1367053 26162
IDSF_HI.BMP 130488832 1393215 4908
IDSF_LO.BMP 16351232 1398123 5900
IDSTPOP.BMP 1946762240 1404023 87530
INTER_DN.BMP 15616 1491553 3692
INTER_HI.BMP -1352923648 1495245 3692
INTER_LO.BMP -1739605504 1498937 3286
IORDERS.BMP -1612146688 1502223 62398
ITEXT.BMP -1029583360 1564621 136332
LEGAL.BMP 852527616 1700953 12116
MASHOLS.BMP -1559190784 1713069 43074
MGREAT.BMP -1500798464 1756143 9360
MGTEXT.BMP -1488411904 1765503 52124
MSHOP.BMP -1518231040 1817627 10152
MSTEXT.BMP -1488805120 1827779 38502
NEWBACK.BMP -1500732672 1866281 279802
NEWBACK2.BMP 614639104 2146083 286246
NOSPACE.BMP -1985550848 2432329 44532
NULL.BMP 29881856 2476861 1120
OK_DN.BMP -146800640 2477981 3240
OK_HI.BMP 13467904 2481221 3240
OK_LO.BMP -847182336 2484461 2846
P1S_DN.BMP 74711040 2487307 12884
P1S_HI.BMP -1660259328 2500191 12884
P1S_LO.BMP -33262336 2513075 12826
P2S_DN.BMP -1507521024 2525901 12878
P2S_HI.BMP 343212288 2538779 12878
P2S_LO.BMP -1566095872 2551657 12830
P3S_DN.BMP 43453184 2564487 12938
P3S_HI.BMP -273684224 2577425 12938
P3S_LO.BMP 113748736 2590363 12888
P4S_DN.BMP 943370240 2603251 12400
P4S_HI.BMP 1031799296 2615651 12400
P4S_LO.BMP 168683520 2628051 12408
P5S_DN.BMP -1205927168 2640459 12926
P5S_HI.BMP 772404480 2653385 12926
P5S_LO.BMP -402375168 2666311 12896
P6S_DN.BMP 401151488 2679207 12816
P6S_HI.BMP 113704960 2692023 12816
P6S_LO.BMP -1070396672 2704839 12722
P7S_DN.BMP 915081216 2717561 12414
P7S_HI.BMP -391715328 2729975 12414
P7S_LO.BMP 125108480 2742389 12358
P8S_DN.BMP 1962949632 2754747 12718
P8S_HI.BMP -1677625600 2767465 12718
P8S_LO.BMP 1790902272 2780183 12528
PL1_HI.BMP 1547599872 2792711 2512
PL1_LO.BMP 1048581632 2795223 2222
PL2_HI.BMP -1559609856 2797445 2832
PL2_LO.BMP 770393600 2800277 2396
PL3_HI.BMP -639042048 2802673 2668
PL3_LO.BMP 4096000 2805341 2330
PL4_HI.BMP -2147432448 2807671 2624
PL4_LO.BMP -401837056 2810295 2260
PLAY_DN.BMP -13702912 2812555 3284
PLAY_GR.BMP 915139840 2815839 2920
PLAY_HI.BMP -385983232 2818759 3284
PLAY_LO.BMP -2147070464 2822043 3014
PLAYPOP.BMP 178438144 2825057 89362
PLAYQ_HI.BMP -369237760 2914419 5466
PLAYQ_LO.BMP -1168629504 2919885 6078
PRNT_DN.BMP 12058624 2925963 3544
PRNT_GR.BMP 1459664896 2929507 3290
PRNT_HI.BMP -962163712 2932797 3544
PRNT_LO.BMP 57933824 2936341 3290
QBACK1.BMP 225748480 2939631 275738
QKBACK.BMP 269912832 3215369 265940
QKBACK2.BMP -1561518592 3481309 275958
QUAKE.BMP 1946199808 3757267 70172
QUNLOCK.BMP 1101200896 3827439 61734
QUNLOCK2.BMP 1144946688 3889173 11994
RTFWHIT.BMP -1495065088 3901167 8892
SIGN_HI.BMP -1559224832 3910059 4812
SIGN_LO.BMP -962392064 3914871 6062
SIGNPOP.BMP -2083097344 3920933 87336
SK_DN.BMP 1061060608 4008269 22332
SK_HI.BMP -1269707264 4030601 22332
SK_LO.BMP 13991936 4052933 23066
SMLOGO.BMP -896806400 4075999 10282
SOF_DN.BMP 753401856 4086281 2784
SOF_GR.BMP 185007616 4089065 2784
SOF_HI.BMP -1507214848 4091849 3030
SOF_LO.BMP 243836416 4094879 2784
SON_DN.BMP 1048625920 4097663 3010
SON_GR.BMP -960294400 4100673 2754
SON_HI.BMP 1362529536 4103427 3010
SON_LO.BMP 138724864 4106437 2754
SP1_HI.BMP -4849664 4109191 16542
SP1_LO.BMP -796207104 4125733 14700
SP2_HI.BMP -1965525760 4140433 20688
SP2_LO.BMP -536427008 4161121 19460
SP3_HI.BMP -1157353984 4180581 16152
SP3_LO.BMP 175403776 4196733 14692
SP4_HI.BMP -1910070784 4211425 19740
SP4_LO.BMP 92939776 4231165 18430
SRQUAKE.BMP 1946157568 4249595 40566
STAND.BMP 868481280 4290161 38650
STEXT.BMP 1504637440 4328811 15430
STITLE.BMP -1488714240 4344241 12836
TDLOGO.BMP 10937600 4357077 8606
TDLOGO2.BMP -1100218368 4365683 5694
TOK_DN.BMP 292757760 4371377 1660
TOK_GR.BMP -1559624192 4373037 1674
TOK_HI.BMP 158665728 4374711 1674
TOK_LO.BMP -2011117056 4376385 1674
TS_DN.BMP -1488962048 4378059 29754
TS_HI.BMP 934940928 4407813 29754
TS_LO.BMP 269387520 4437567 27818
TS1_DN.BMP -851397632 4465385 20546
TS1_HI.BMP -2113473024 4485931 20546
TS1_LO.BMP -1194227968 4506477 20454
TS1B.BMP 1601315072 4526931 43970
TS2_DN.BMP -1501553152 4570901 24102
TS2_HI.BMP 567099904 4595003 24102
TS2_LO.BMP -851528704 4619105 24122
TS2B.BMP -369582592 4643227 38948
TS3_DN.BMP 1168156416 4682175 24418
TS3_HI.BMP 853705216 4706593 24418
TS3_LO.BMP 567093504 4731011 24334
TS3B.BMP 1158580736 4755345 48044
TS4_DN.BMP -1002827008 4803389 20806
TS4_HI.BMP 183029760 4824195 20806
TS4_LO.BMP -1488648704 4845001 20624
TS4B.BMP -224203520 4865625 34854
UCAN_DN.BMP -490822400 4900479 1808
UCAN_GR.BMP -1309548032 4902287 1830
UCAN_HI.BMP -498882048 4904117 1828
UCAN_LO.BMP 855654400 4905945 1828
UEXIT_DN.BMP 510918656 4907773 1694
UEXIT_GR.BMP 1400209408 4909467 1708
UEXIT_HI.BMP 550141952 4911175 1708
UEXIT_LO.BMP -411040768 4912883 1708
UNLK_HI.BMP 1187382528 4914591 5294
UNLK_LO.BMP 1914817792 4919885 6094
UNLKPOP.BMP 1207006208 4925979 88078
UNLOCK.BMP 1187424768 5014057 57182
UNLOCK1.BMP -966757120 5071239 6270
UOK_DN.BMP -1286900736 5077509 3060
UOK_HI.BMP -383391744 5080569 3060
DEATH.CAH -346138368 5083629 28
DOOM_SE.CAH -371680000 5083657 32
DOOM2.CAH -2035629056 5083689 28
DOOM2_95.CAH 1891606528 5083717 34
FINAL.CAH -2000080128 5083751 28
FINALDOS.CAH 1958742528 5083779 34
HERETC13.CAH 1020586752 5083813 34
HERETIC.CAH 255754240 5083847 32
HEXEN.CAH -385833216 5083879 28
HEXEN11.CAH 1343780096 5083907 32
MASTER.CAH -402610432 5083939 30
QUAKE.CAH 158674432 5083969 28
ULTDM95.CAH -2147299584 5083997 32
WOLF3D.CAH 708816896 5084029 30
COPYRGHT.DAT 1977686784 5084059 38
T-DRIVE.DAT -1486813696 5084097 261
DEATH.DOC 976516096 5084358 512
DOOM_SE.DOC -402295552 5084870 512
DOOM2.DOC 1599005440 5085382 512
DOOM2_95.DOC 1172835584 5085894 512
FINAL.DOC -823443456 5086406 512
FINALDOS.DOC 1974399488 5086918 512
HERETC13.DOC 1101710080 5087430 512
HERETIC.DOC -1993433600 5087942 512
HEXEN.DOC 771751936 5088454 512
HEXEN11.DOC 252102400 5088966 512
MASTER.DOC 975576320 5089478 512
QUAKE.DOC -164717056 5089990 512
ULTDM95.DOC -164751360 5090502 512
WOLF3D.DOC -2010244352 5091014 512
ENCRYPT.EXE -1091878144 5091526 14894
UOK_LO.BMP -2144428544 5106420 2686
WARN.BMP -466484992 5109106 44260
COPYRGHT.TXT -352286720 5153366 40
EXE.TXT 260711936 5153406 235
WIN31.BMP 113716736 5153641 57852
WOLHOLS.BMP -523115520 5211493 37630
LIMITS.TXT 1124823808 5249123 281
PRODUCTS.TXT -1959879168 5249404 804
SKU.TXT 91553792 5250208 1706
WRNGDIR.BMP -352321024 5251914 57814
WT1_HI.BMP 1334453760 5309728 17698
WT1_LO.BMP 772205312 5327426 16230
WT1BK.BMP 1192132608 5343656 41366
YOK_DN.BMP 1208353024 5385022 1580
YOK_HI.BMP 990299904 5386602 1562
QUAKE1.CAH 43924224 5388164 30
QUAKE2.CAH 1472460800 5388194 30
QUAKE3.CAH 1166616064 5388224 30
QUAKE4.CAH 72714496 5388254 30
QUAKE5.CAH 259326976 5388284 30
QUAKE6.CAH 1342590208 5388314 30
QUAKE7.CAH 1476465408 5388344 30
QUAKE8.CAH -1073019136 5388374 30
QUAKE9.CAH 1482381824 5388404 30
QUAKE1.DOC 1963564544 5388434 512
QUAKE2.DOC 41224448 5388946 512
QUAKE3.DOC 993952512 5389458 512
QUAKE4.DOC -1640284672 5389970 512
QUAKE5.DOC 1048784384 5390482 512
QUAKE6.DOC 993952512 5390994 512
QUAKE7.DOC -1626063360 5391506 512
QUAKE8.DOC -1976674816 5392018 512
QUAKE9.DOC -421000960 5392530 512
YOK_LO.BMP -1073083392 5393042 1562
SETUP.CAH 1918974976 5394604 8187
FLOWWORK.BAK 20876544 5402791 27176

I saw that the first four bytes of the flowlib.dir file are actually the number of entries.

Thinking what that offset could be for, I wonder if flowlib.lib is where all these files are, as it’s a much larger file and also needed by QCRACK. If so, that would mean the first file in flowlib.lib would be that first file entry EXE.17.

Sure enough, the length of those game names is the same as what it says is the length for EXE.17.

The modified code to extract the files:

#include <stdint.h>

struct flowlibdir_entry {
    char file_name[13];
    int32_t something;
    int32_t offset;
    int32_t size;
};

#include <stdio.h>
#include <string.h>

void flowlibdir(void) {
    FILE* flowlibdir;
    FILE* flowliblib;
    char file_buff[1024*1024*6];//6MB file buffer, bigger than whole flowlib.lib file
    struct flowlibdir_entry entries[1000];
    int num_entries = 0;

    //does it matter if I pass b for binary to fopen?
    if((flowlibdir = fopen("flowlib.dir", "rb")) == NULL) {
        printf("can't open flowlib.dir\n");
        return;
    }

    if((flowliblib = fopen("flowlib.lib", "rb")) == NULL) {
        printf("can't open flowlib.lib\n");
    }

    int num_objs_read;

    int thing;

    num_objs_read = fread(&thing, 4, 1, flowlibdir);
    char c;
    while((c=getc(flowlibdir)) != EOF && num_entries <= 1000) {
        ungetc(c, flowlibdir);
        num_objs_read = fread(&entries[num_entries], 12, 1, flowlibdir);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for file name %s as expected:%d\n",
                   entries[num_entries].file_name,
                   num_objs_read);
        }
        entries[num_entries].file_name[12] = '\0';

        num_objs_read = fread(&entries[num_entries].something, 4, 1, flowlibdir);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for something as expected:%d\n", num_objs_read);
        }

        num_objs_read = fread(&entries[num_entries].offset, 4, 1, flowlibdir);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for offset as expected:%d\n", num_objs_read);
        }

        num_objs_read = fread(&entries[num_entries].size, 4, 1, flowlibdir);
        if(num_objs_read != 1) {
            printf("didn't get 1 object for size as expected:%d\n", num_objs_read);
        }

        if(flowliblib) {
            FILE* extracted_file;

            if((extracted_file = fopen(entries[num_entries].file_name, "wb")) != NULL) {
                //get the file out of flowlib.lib
                if(fread(file_buff, entries[num_entries].size, 1, flowliblib) != 1) {
                    printf("didn't get 1 object extracted from flowlib.lib for %s\n",
                           entries[num_entries].file_name);
                }

                //write to the file
                if(fwrite(file_buff, entries[num_entries].size, 1, extracted_file) != 1) {
                    printf("didn't write 1 object of extracted file %s\n",
                           entries[num_entries].file_name);
                }
            }
            else {
                printf("could not open file %s to write to\n", entries[num_entries].file_name);
            }

            fclose(extracted_file);
        }

        num_entries++;
    }

    if(num_entries >= 1000) {
        printf("reached max entries\n");
    }

    printf("num_entries=%d\n", num_entries);
    printf("thing=%d\n", thing);
    int i;
    for(i = 0; i < num_entries; ++i) {
        printf("%s %d %d %d\n",
               entries[i].file_name,
               entries[i].something,
               entries[i].offset,
               entries[i].size);

    }
}

It’s mostly bitmaps. With those, and what looks like config files, it seems the Quake CD ran a general program like this that you customize the UI with your own bitmaps and config files saying what to do.

There are also those DOC files that the QCRACK program mentions, will try to see how those are used next. Also a SKU.TXT file in plain text that is the same size as the original SKU.17, will also see if that’s an encrypted form of the plain text one and how it’s encrypted. Will also be checking out that ENCRYPT.EXE program.