_UNDOCUMENTED CORNER_ edited by Andrew Schulman "The Windows .RES File Format" by Alex G. Fedorov and Dmitry M. Rogatkin [LISTING ONE] // TEST.RC -- simple input file // rc -r test.rc, then dump test.res (see Figure 1) #include BARFOO ICON some.ico FOOBAR DIALOG 10,20,30,40 CAPTION "FOOBAR" CLASS "FOOBAR" STYLE WS_BORDER | WS_CAPTION { CTEXT "This is a test", -1, 1, 2, 3, 4 DEFPUSHBUTTON "OK", IDOK, 5, 6, 7, 8 } BARBAR MENU { POPUP "&Test" { MENUITEM "Cmd &1", 101 MENUITEM "Cmd &2", 102 } POPUP "&Another test" { MENUITEM "Cmd &11", 111 MENUITEM "Cmd &12", 112 } MENUITEM "&Help", 200 } FOOFOO ACCELERATORS { VK_F1, 200, VIRTKEY } [LISTING TWO] // RESLIST.CPP -- Reads Windows .RES files from "Undocumented Corner," DDJ, // XXXX 1993 -- Alex G. Fedorov and Dmitry M. Rogatkin // alex@computerpress.msk.su & datasc@adonis@ias.msk.su #include #include #include #include #include #define LastResNum 16 char *StandardResName[] = { /* 1 */ "Cursor", /* 2 */ "Bitmap", /* 3 */ "Icon", /* 4 */ "Menu", /* 5 */ "Dialog box", /* 6 */ "String table", /* 7 */ "Font directory", /* 8 */ "Font", /* 9 */ "Accelerator table", /* 10 */ "RCData (user-defined)", /* 11 */ "Not alowed", /* 12 */ "Group cursor", /* 13 */ "Not alowed", /* 14 */ "Group icon", /* 15 */ "Name table (obsolete in 3.1)", /* 16 */ "Version info" }; // read an ordinal number into aId, or an ASCIIZ string into aStr int RdHeadItem(char *aStr, int &aId, int hFile) { char ch; if (_read(hFile, &aStr[0], 1) != 1) return 1; if (aStr[0] == '\xFF') // ordinal { aStr[0] = 0; // number, not name _read(hFile, &aId, sizeof(int)); } else // name { if (aStr[0] == 0) // invalid magic { cout << "This is a Win32 .RES file\n"; exit(1); } aId = 0; // name, not number signed long pos = lseek(hFile, 0, SEEK_CUR); // where are we? _read(hFile, aStr+1, 126); // probably read too much lseek(hFile, pos + strlen(aStr), SEEK_SET); // back up } return 0; } void main(int argc, char *argv[]) { int hResFile, RId, Id; long ResLen; unsigned int Flgs; char st[128], sn[128]; if (argc != 2) { cout << "Usage: Reslist resfile\n"; exit(1); } if ((hResFile = _open(argv[1], O_RDONLY))) { // get type: ordinal or string while (! RdHeadItem(st, RId, hResFile)) { if (st[0] == 0) // ordinal number, not name { if (RId <= LastResNum) strcpy(st, StandardResName[RId-1]); else { itoa(RId, st, 10); strcat(st, " (user defined)"); } } else ; // already have type string in st // get ID: ordinal or string RdHeadItem(sn, Id, hResFile); if (sn[0] == 0) // ordinal number, not string { sn[0] = '#'; itoa(Id, &sn[1], 10); } // get memory flags _read(hResFile, &Flgs, sizeof(unsigned int)); // get length in bytes of following resource data _read(hResFile, &ResLen, sizeof(long)); // where are we in the file? // so we can output file offset of actual res data long pos = lseek(hResFile, 0, SEEK_CUR); cout << st << ": " << sn << " (" << ResLen << " bytes at ofs " << hex << pos << dec << "H)\n"; // in a genuine program, we would read in resource data // and switch on resource type in RId lseek(hResFile, ResLen, SEEK_CUR); }; _close(hResFile); } } [LISTING THREE] HGLOBAL LoadRESResource(LPCSTR ResFName, LPCSTR lpszName, LPCSTR lpszType) { char st[128], sn[128]; void huge *AddrTemp; long ResLen; HFILE hResFile; HGLOBAL hMem=0; WORD Flags, RId, Id, fndtyp = 0; if (HIWORD(lpszType) == 0) fndtyp = LOWORD(lpszType); if ((hResFile = _lopen(ResFName, OF_READ)) == HFILE_ERROR) return 0; while(! RdHeadItem(st, RId, hResFile)) { // get type RdHeadItem(sn, Id, hResFile); // get name or ID _lread(hResFile, &Flags, sizeof(unsigned int)); // get flags _lread(hResFile, &ResLen, sizeof(long)); // get length if (fndtyp != 0 && RId == fndtyp || fndtyp == 0 && strcmp(lpszType, st) == 0) { // match type if (HIWORD(lpszName) != 0 && strcmp(lpszName, sn) == 0 || Id == LOWORD(lpszName)) { // match name if (! (hMem = GlobalAlloc(GMEM_FIXED, ResLen))) return 0; if (! (AddrTemp = GlobalLock(hMem))) { GlobalFree(hMem); return 0; } long count = 0; unsigned portion; long len = ResLen - count; while (len) { portion = (len <= 0xFFFF) ? len : 0xFFFF; if (_lread(hResFile, (char huge *)AddrTemp+count, portion) != portion) { GlobalUnlock(hMem); GlobalFree(hMem); hMem = 0; } count+=portion; } GlobalUnlock(hMem); break; // we found it! done! } } _llseek(hResFile, ResLen, SEEK_CUR); // to next resource entry } _lclose(hResFile); return hMem; } [LISTING FOUR] // LoadRESMenu() -- excerpted from READRES.CPP HMENU LoadRESMenu(LPCSTR ResFileName, LPCSTR lpszMenuName) { HGLOBAL hTempMem; void far* AddrTemp; HMENU retMnu; if ((hTempMem = LoadRESResource(ResFileName, lpszMenuName, MAKEINTRESOURCE(RT_MENU))) != 0) { if ((AddrTemp = GlobalLock(hTempMem)) != 0) { retMnu = LoadMenuIndirect(AddrTemp); GlobalUnlock(hTempMem); } GlobalFree(hTempMem); } return retMnu; } Figure 1: Hex dump of TEST.RES 0000 | FF 03 00 FF 01 00 30 10 E8 02 00 00 28 00 00 00 | ......0.....(... 0010 | 20 00 00 00 40 00 00 00 01 00 04 00 00 00 00 00 | ...@........... ...... 02f0 | 00 00 00 00 FF 0E 00 42 41 52 46 4F 4F 00 30 10 | .......BARFOO.0. 0300 | 14 00 00 00 00 00 01 00 01 00 20 20 10 00 01 00 | .......... .... 0310 | 04 00 E8 02 00 00 01 00 FF 05 00 46 4F 4F 42 41 | ...........FOOBA 0320 | 52 00 30 10 4E 00 00 00 00 00 C0 00 02 0A 00 14 | R.0.N........... 0330 | 00 1E 00 28 00 00 46 4F 4F 42 41 52 00 46 4F 4F | ...(..FOOBAR.FOO 0340 | 42 41 52 00 01 00 02 00 03 00 04 00 FF FF 01 00 | BAR............. 0350 | 02 50 82 54 68 69 73 20 69 73 20 61 20 74 65 73 | .P.This is a tes 0360 | 74 00 00 05 00 06 00 07 00 08 00 01 00 01 00 01 | t............... 0370 | 50 80 4F 4B 00 00 FF 04 00 42 41 52 42 41 52 00 | P.OK.....BARBAR. 0380 | 30 10 54 00 00 00 00 00 00 00 10 00 26 54 65 73 | 0.T.........&Tes 0390 | 74 00 00 00 65 00 43 6D 64 20 26 31 00 80 00 66 | t...e.Cmd &1...f 03a0 | 00 43 6D 64 20 26 32 00 10 00 26 41 6E 6F 74 68 | .Cmd &2...&Anoth 03b0 | 65 72 20 74 65 73 74 00 00 00 6F 00 43 6D 64 20 | er test...o.Cmd 03c0 | 26 31 31 00 80 00 70 00 43 6D 64 20 26 31 32 00 | &11...p.Cmd &12. 03d0 | 80 00 C8 00 26 48 65 6C 70 00 FF 09 00 46 4F 4F | ....&Help....FOO 03e0 | 46 4F 4F 00 30 00 05 00 00 00 81 70 00 C8 00 FF | FOO.0......p.... 03f0 | 0F 00 FF 01 00 30 10 3A 00 00 00 0E 00 0E 00 01 | .....0.:........ 0400 | 80 00 42 41 52 46 4F 4F 00 0E 00 04 00 01 80 00 | ..BARFOO........ 0410 | 42 41 52 42 41 52 00 0E 00 05 00 01 80 00 46 4F | BARBAR........FO 0420 | 4F 42 41 52 00 0E 00 09 00 01 80 00 46 4F 4F 46 | OBAR........FOOF 0430 | 4F 4F 00 00 00 | OO... Figure 2: Analysis of Hex Dump of TEST.RES +------------------------------------------- Tag1 | +-------------------------------------- Res Type (3 = RT_ICON) | | +---------------------------------- Tag2 | | | +----------------------------- Resource ID (#1) | | | | +----------------------- Resource Flags | +---+ | +---+ +---+ +------------------- Resource Length (0x02E8) | | | | | | | | | | /////////// Start of ICON data 0000 | FF 03 00 FF 01 00 30 10 E8 02 00 00 28 00 00 00 | ......0.....(... 0010 | 20 00 00 00 40 00 00 00 01 00 04 00 00 00 00 00 | ...@........... ........ skip this hdr size (3+3+2+4=0x0C) + res length (0x02e8) = next res at 0x02f4 ........ +------------------------------- Tag1 | +-------------------------- Res Type (14 = | | RT_GROUP_ICON) | | +------------- Res Name ("BARFOO") | | | | | | Resource Flags | +---+ +-------------------+ +---+ | | | | | | | 02f0 | 00 00 00 00 FF 0E 00 42 41 52 46 4F 4F 00 30 10 | .......BARFOO.0. 0300 | 14 00 00 00 00 00 01 00 01 00 20 20 10 00 01 00 | .......... .... | | /////////////////////////////////// +---------+ +------------------------------- Resource data +-------------------------------------- Resource Length (0x14) +------------------------------------------- More resource data | | +------------------- Tag1 | | +---+------------ Res Type (5 = RT_DIALOG) | | | | +---------- Res Name ("FOOBAR") /////////////////////// | | | | 0310 | 04 00 E8 02 00 00 01 00 FF 05 00 46 4F 4F 42 41 | ...........FOOBA 0320 | 52 00 30 10 4E 00 00 00 00 00 C0 00 02 0A 00 14 | R.0.N........... | | | | | | //////////////////// start of binary dlg data | | | | +---------+--------------------- Res Length (0x4E) | | +---+--------------------------------- Res Flags +---+--------------------------------------- rest of Res Name Figure 3: C Pseudocode for .RES File Structure typedef struct { BYTE ff; // 0xFF WORD id; } ID; typedef struct { union { ID id; // 1 = RT_CURSOR, 2 = RT_BITMAP, etc. char name[variable_length]; // first byte *not* 0xFF } type; union { ID id; // ordinal number of resource char name[variable_length]; // first byte *not* 0xFF } name_or_id; WORD mem_flags; // 0x10=MOVEABLE, 0x20=PURE, 0x40=PRELOAD, // 0x1000=DISCARDABLE DWORD size; // *not* including this header BYTE res_data[size]; // the resource data: see SDK v. 4, ch. 7 } RES_FILE_ENTRY; // NOT VALID C! // RES file is just an accumulation of these entries RES_FILE_ENTRY RES_FILE[num_resources]; Figure 4: RESLIST Output for TEST.RES C:\DDJ>reslist test.res Icon: #1 (744 bytes at ofs cH) Group icon: BARFOO (20 bytes at ofs 304H) Dialog box: FOOBAR (78 bytes at ofs 328H) Menu: BARBAR (84 bytes at ofs 386H) Accelerator table: FOOFOO (5 bytes at ofs 3eaH) Name table (obsolete in 3.1): #1 (58 bytes at ofs 3fbH)