Task 610: .RES File Format

Task 610: .RES File Format

1. List of all the properties of this file format intrinsic to its file system

The .RES file format is the binary compiled resource file format used in Windows, generated from .RC scripts by the resource compiler. It consists of a sequence of resource entries, each with a header and data, with the first entry being an empty header. The intrinsic properties (fields) in the file structure, as defined in the RESOURCEHEADER and related specifications, are:

  • DataSize: A 4-byte unsigned integer (DWORD) representing the size in bytes of the resource data following the header.
  • HeaderSize: A 4-byte unsigned integer (DWORD) representing the size in bytes of the resource header (including variable-length Type and Name fields).
  • Type: A variable-length field representing the resource type. It can be an integer (prefixed with 0xFFFF followed by a 2-byte ID) or a null-terminated Unicode string.
  • Name: A variable-length field representing the resource name. It can be an integer (prefixed with 0xFFFF followed by a 2-byte ID) or a null-terminated Unicode string.
  • DataVersion: A 4-byte unsigned integer (DWORD) specifying the version of the resource data.
  • MemoryFlags: A 2-byte unsigned integer (WORD) containing attribute flags for the resource (e.g., MOVEABLE, PRELOAD, DISCARDABLE).
  • LanguageId: A 2-byte unsigned integer (WORD) identifying the language of the resource (e.g., for localization).
  • Version: A 4-byte unsigned integer (DWORD) for user-defined version information.
  • Characteristics: A 4-byte unsigned integer (DWORD) for user-defined additional information about the resource.
  • Padding: Variable bytes (0-3) after Type, Name, and data to ensure DWORD (4-byte) alignment in the file.

These properties are common to all resource entries in the file. The file has no overall header beyond the initial empty resource entry, and resources are concatenated with alignment padding.

3. Ghost blog embedded HTML JavaScript for drag and drop .RES file dump

.RES File Dumper
Drag and drop .RES file here

  

4. Python class for .RES file

import struct

class RESFile:
    def __init__(self, filename=None):
        self.resources = []
        if filename:
            self.read(filename)

    def read_unicode_or_id(self, f):
        first = struct.unpack('<H', f.read(2))[0]
        if first == 0xFFFF:
            id = struct.unpack('<H', f.read(2))[0]
            return str(id)
        else:
            f.seek(-2, 1) # Rewind
            str_bytes = b''
            while True:
                char = struct.unpack('<H', f.read(2))[0]
                if char == 0:
                    break
                str_bytes += struct.pack('<H', char)
            return str_bytes.decode('utf-16le')

    def align_to_4(self, f):
        pad = (4 - f.tell() % 4) % 4
        f.read(pad)

    def read(self, filename):
        with open(filename, 'rb') as f:
            while True:
                data_size = struct.unpack('<I', f.read(4))[0]
                header_size = struct.unpack('<I', f.read(4))[0]
                type_ = self.read_unicode_or_id(f)
                self.align_to_4(f)
                name = self.read_unicode_or_id(f)
                self.align_to_4(f)
                data_version = struct.unpack('<I', f.read(4))[0]
                memory_flags = struct.unpack('<H', f.read(2))[0]
                language_id = struct.unpack('<H', f.read(2))[0]
                version = struct.unpack('<I', f.read(4))[0]
                characteristics = struct.unpack('<I', f.read(4))[0]
                self.resources.append({
                    'data_size': data_size,
                    'header_size': header_size,
                    'type': type_,
                    'name': name,
                    'data_version': data_version,
                    'memory_flags': memory_flags,
                    'language_id': language_id,
                    'version': version,
                    'characteristics': characteristics,
                    'data': f.read(data_size) if data_size > 0 else b''
                })
                self.align_to_4(f)
                if f.tell() >= os.path.getsize(filename):
                    break

    def print_properties(self):
        for idx, res in enumerate(self.resources):
            print(f"Resource {idx}:")
            for key, value in res.items():
                if key != 'data':
                    print(f"{key}: {value}")
            print()

    def write(self, filename):
        with open(filename, 'wb') as f:
            for res in self.resources:
                start_pos = f.tell()
                f.write(struct.pack('<I', res['data_size']))
                f.write(struct.pack('<I', 0))  # Placeholder for header_size
                self.write_unicode_or_id(f, res['type'])
                self.align_to_4_write(f)
                self.write_unicode_or_id(f, res['name'])
                self.align_to_4_write(f)
                f.write(struct.pack('<I', res['data_version']))
                f.write(struct.pack('<H', res['memory_flags']))
                f.write(struct.pack('<H', res['language_id']))
                f.write(struct.pack('<I', res['version']))
                f.write(struct.pack('<I', res['characteristics']))
                header_end = f.tell()
                header_size = header_end - start_pos
                f.seek(start_pos + 4)
                f.write(struct.pack('<I', header_size))
                f.seek(header_end)
                f.write(res['data'])
                self.align_to_4_write(f)

    def write_unicode_or_id(self, f, value):
        if value.isdigit():
            f.write(struct.pack('<H', 0xFFFF))
            f.write(struct.pack('<H', int(value)))
        else:
            for char in value:
                f.write(struct.pack('<H', ord(char)))
            f.write(struct.pack('<H', 0))

    def align_to_4_write(self, f):
        pad = (4 - f.tell() % 4) % 4
        f.write(b'\x00' * pad)

# Example usage
# res = RESFile('sample.res')
# res.print_properties()
# res.write('output.res')

5. Java class for .RES file

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class RESFile {
    private static class Resource {
        int dataSize;
        int headerSize;
        String type;
        String name;
        int dataVersion;
        int memoryFlags;
        int languageId;
        int version;
        int characteristics;
        byte[] data;
    }

    private Resource[] resources;

    public RESFile(String filename) throws IOException {
        read(filename);
    }

    private String readUnicodeOrId(DataInputStream dis) throws IOException {
        int first = dis.readUnsignedShort();
        if (first == 0xFFFF) {
            return Integer.toString(dis.readUnsignedShort());
        } else {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write((byte) (first & 0xFF));
            baos.write((byte) (first >> 8));
            while (true) {
                int charCode = dis.readUnsignedShort();
                if (charCode == 0) break;
                baos.write((byte) (charCode & 0xFF));
                baos.write((byte) (charCode >> 8));
            }
            return new String(baos.toByteArray(), "UTF-16LE");
        }
    }

    private void alignTo4(DataInputStream dis) throws IOException {
        int pad = (4 - (int) (dis.available() % 4 ? wait, no, we need to skip based on current position.
        // Since we don't have position, we can use a byte buffer instead for better control.
        // For simplicity, we'll assume alignment by reading dummy bytes if needed, but to make it work, use ByteBuffer.

        // Let's use ByteBuffer for accuracy.
    }

    // Rewriting with ByteBuffer for better position control.
    public void read(String filename) throws IOException {
        byte[] fileBytes = Files.readAllBytes(Paths.get(filename));
        ByteBuffer bb = ByteBuffer.wrap(fileBytes);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        List<Resource> resList = new ArrayList<>();
        while (bb.hasRemaining()) {
            Resource res = new Resource();
            res.dataSize = bb.getInt();
            res.headerSize = bb.getInt();
            res.type = readUnicodeOrIdBB(bb);
            alignTo4BB(bb);
            res.name = readUnicodeOrIdBB(bb);
            alignTo4BB(bb);
            res.dataVersion = bb.getInt();
            res.memoryFlags = bb.getShort();
            res.languageId = bb.getShort();
            res.version = bb.getInt();
            res.characteristics = bb.getInt();
            res.data = new byte[res.dataSize];
            bb.get(res.data);
            alignTo4BB(bb);
            resList.add(res);
            if (res.dataSize == 0 && resList.size() > 1) break; // Stop after initial empty and some
        }
        resources = resList.toArray(new Resource[0]);
    }

    private String readUnicodeOrIdBB(ByteBuffer bb) {
        short first = bb.getShort();
        if (first == (short)0xFFFF) {
            return Short.toUnsignedInt(bb.getShort()) + "";
        } else {
            bb.position(bb.position() - 2);
            StringBuilder sb = new StringBuilder();
            while (true) {
                short charCode = bb.getShort();
                if (charCode == 0) break;
                sb.append((char) charCode);
            }
            return sb.toString();
        }
    }

    private void alignTo4BB(ByteBuffer bb) {
        int pad = (4 - (bb.position() % 4)) % 4;
        bb.position(bb.position() + pad);
    }

    public void printProperties() {
        for (int i = 0; i < resources.length; i++) {
            Resource res = resources[i];
            System.out.println("Resource " + i + ":");
            System.out.println("dataSize: " + res.dataSize);
            System.out.println("headerSize: " + res.headerSize);
            System.out.println("type: " + res.type);
            System.out.println("name: " + res.name);
            System.out.println("dataVersion: " + res.dataVersion);
            System.out.println("memoryFlags: " + res.memoryFlags);
            System.out.println("languageId: " + res.languageId);
            System.out.println("version: " + res.version);
            System.out.println("characteristics: " + res.characteristics);
            System.out.println();
        }
    }

    public void write(String filename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            for (Resource res : resources) {
                ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
                DataOutputStream headerDos = new DataOutputStream(headerBaos);
                headerDos.writeInt(res.dataSize);
                headerDos.writeInt(0); // Placeholder for headerSize
                writeUnicodeOrId(headerDos, res.type);
                alignTo4Dos(headerDos);
                writeUnicodeOrId(headerDos, res.name);
                alignTo4Dos(headerDos);
                headerDos.writeInt(res.dataVersion);
                headerDos.writeShort(res.memoryFlags);
                headerDos.writeShort(res.languageId);
                headerDos.writeInt(res.version);
                headerDos.writeInt(res.characteristics);
                byte[] header = headerBaos.toByteArray();
                ByteBuffer headerBb = ByteBuffer.wrap(header);
                headerBb.order(ByteOrder.LITTLE_ENDIAN);
                headerBb.putInt(4, header.length); // Update headerSize
                fos.write(header);
                fos.write(res.data);
                // Align to 4
                int pad = (4 - (res.dataSize % 4)) % 4;
                fos.write(new byte[pad]);
            }
        }
    }

    private void writeUnicodeOrId(DataOutputStream dos, String value) throws IOException {
        if (value.matches("\\d+")) {
            dos.writeShort(0xFFFF);
            dos.writeShort(Integer.parseInt(value));
        } else {
            for (char c : value.toCharArray()) {
                dos.writeShort(c);
            }
            dos.writeShort(0);
        }
    }

    private void alignTo4Dos(DataOutputStream dos) throws IOException {
        int pad = (4 - (int) (dos.size() % 4)) % 4;
        for (int i = 0; i < pad; i++) {
            dos.writeByte(0);
        }
    }

    // Example usage
    // RESFile res = new RESFile("sample.res");
    // res.printProperties();
    // res.write("output.res");
}

6. JavaScript class for .RES file

class RESFile {
  constructor(arrayBuffer = null) {
    this.resources = [];
    if (arrayBuffer) this.read(arrayBuffer);
  }

  readUnicodeOrId(dv, offset) {
    let first = dv.getUint16(offset, true);
    offset += 2;
    if (first === 0xFFFF) {
      let id = dv.getUint16(offset, true);
      offset += 2;
      return { value: id.toString(), offset };
    } else {
      offset -= 2; // Rewind
      let str = '';
      while (true) {
        let char = dv.getUint16(offset, true);
        offset += 2;
        if (char === 0) break;
        str += String.fromCharCode(char);
      }
      return { value: str, offset };
    }
  }

  alignTo4(offset) {
    return offset + (4 - (offset % 4)) % 4;
  }

  read(arrayBuffer) {
    const dv = new DataView(arrayBuffer);
    let offset = 0;
    while (offset < dv.byteLength) {
      const res = {};
      res.dataSize = dv.getUint32(offset, true);
      offset += 4;
      res.headerSize = dv.getUint32(offset, true);
      offset += 4;
      let typeInfo = this.readUnicodeOrId(dv, offset);
      res.type = typeInfo.value;
      offset = typeInfo.offset;
      offset = this.alignTo4(offset);
      let nameInfo = this.readUnicodeOrId(dv, offset);
      res.name = nameInfo.value;
      offset = nameInfo.offset;
      offset = this.alignTo4(offset);
      res.dataVersion = dv.getUint32(offset, true);
      offset += 4;
      res.memoryFlags = dv.getUint16(offset, true);
      offset += 2;
      res.languageId = dv.getUint16(offset, true);
      offset += 2;
      res.version = dv.getUint32(offset, true);
      offset += 4;
      res.characteristics = dv.getUint32(offset, true);
      offset += 4;
      res.data = new Uint8Array(arrayBuffer.slice(offset, offset + res.dataSize));
      offset += res.dataSize;
      offset = this.alignTo4(offset);
      this.resources.push(res);
    }
  }

  printProperties() {
    this.resources.forEach((res, index) => {
      console.log(`Resource ${index}:`);
      for (const key in res) {
        if (key !== 'data') {
          console.log(`${key}: ${res[key]}`);
        }
      }
      console.log('');
    });
  }

  write() {
    let totalSize = 0;
    const headers = this.resources.map(res => {
      const header = new ArrayBuffer(1024); // Temp large
      const dv = new DataView(header);
      let offset = 0;
      dv.setUint32(offset, res.dataSize, true);
      offset += 4;
      dv.setUint32(offset, 0, true); // Placeholder headerSize
      offset += 4;
      let typeOffset = this.writeUnicodeOrId(dv, offset, res.type);
      offset = this.alignTo4(typeOffset);
      let nameOffset = this.writeUnicodeOrId(dv, offset, res.name);
      offset = this.alignTo4(nameOffset);
      dv.setUint32(offset, res.dataVersion, true);
      offset += 4;
      dv.setUint16(offset, res.memoryFlags, true);
      offset += 2;
      dv.setUint16(offset, res.languageId, true);
      offset += 2;
      dv.setUint32(offset, res.version, true);
      offset += 4;
      dv.setUint32(offset, res.characteristics, true);
      offset += 4;
      dv.setUint32(4, offset, true); // Update headerSize
      return new Uint8Array(header.slice(0, offset));
    });

    headers.forEach(h => totalSize += h.length);
    this.resources.forEach(r => totalSize += r.data.length + (4 - (r.data.length % 4)) % 4);

    const outputBuffer = new ArrayBuffer(totalSize);
    const outputDv = new DataView(outputBuffer);
    let offset = 0;
    for (let i = 0; i < this.resources.length; i++) {
      const header = headers[i];
      new Uint8Array(outputBuffer).set(header, offset);
      offset += header.length;
      new Uint8Array(outputBuffer).set(this.resources[i].data, offset);
      offset += this.resources[i].data.length;
      offset = this.alignTo4(offset);
    }
    return outputBuffer;
  }

  writeUnicodeOrId(dv, offset, value) {
    if (/^\d+$/.test(value)) {
      dv.setUint16(offset, 0xFFFF, true);
      offset += 2;
      dv.setUint16(offset, parseInt(value), true);
      offset += 2;
    } else {
      for (let char of value) {
        dv.setUint16(offset, char.charCodeAt(0), true);
        offset += 2;
      }
      dv.setUint16(offset, 0, true);
      offset += 2;
    }
    return offset;
  }

  // Example usage
  // const reader = new FileReader();
  // reader.onload = e => {
  //   const res = new RESFile(e.target.result);
  //   res.printProperties();
  //   const newBuffer = res.write();
  //   // Save newBuffer as file
  // };
  // reader.readAsArrayBuffer(file);
}

7. C class for .RES file

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

typedef struct {
    uint32_t dataSize;
    uint32_t headerSize;
    char *type;
    char *name;
    uint32_t dataVersion;
    uint16_t memoryFlags;
    uint16_t languageId;
    uint32_t version;
    uint32_t characteristics;
    uint8_t *data;
} Resource;

typedef struct {
    Resource *resources;
    size_t count;
} RESFile;

char *read_unicode_or_id(FILE *f, long *pos) {
    uint16_t first;
    fread(&first, sizeof(uint16_t), 1, f);
    *pos += sizeof(uint16_t);
    if (first == 0xFFFF) {
        uint16_t id;
        fread(&id, sizeof(uint16_t), 1, f);
        *pos += sizeof(uint16_t);
        char *str = malloc(11);
        sprintf(str, "%u", id);
        return str;
    } else {
        fseek(f, -sizeof(uint16_t), SEEK_CUR);
        *pos -= sizeof(uint16_t);
        char *str = malloc(256); // Max size
        size_t len = 0;
        while (true) {
            uint16_t charCode;
            fread(&charCode, sizeof(uint16_t), 1, f);
            *pos += sizeof(uint16_t);
            if (charCode == 0) break;
            str[len++] = (char) charCode; // Simplification, assuming ASCII range
        }
        str[len] = '\0';
        return str;
    }
}

void align_to_4(FILE *f, long *pos) {
    int pad = (4 - (*pos % 4)) % 4;
    fseek(f, pad, SEEK_CUR);
    *pos += pad;
}

RESFile *res_file_open(const char *filename) {
    RESFile *res = malloc(sizeof(RESFile));
    res->resources = NULL;
    res->count = 0;

    FILE *f = fopen(filename, "rb");
    if (!f) return NULL;

    long pos = 0;
    while (!feof(f)) {
        res->resources = realloc(res->resources, sizeof(Resource) * (res->count + 1));
        Resource *r = &res->resources[res->count];
        fread(&r->dataSize, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        fread(&r->headerSize, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        r->type = read_unicode_or_id(f, &pos);
        align_to_4(f, &pos);
        r->name = read_unicode_or_id(f, &pos);
        align_to_4(f, &pos);
        fread(&r->dataVersion, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        fread(&r->memoryFlags, sizeof(uint16_t), 1, f);
        pos += sizeof(uint16_t);
        fread(&r->languageId, sizeof(uint16_t), 1, f);
        pos += sizeof(uint16_t);
        fread(&r->version, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        fread(&r->characteristics, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        r->data = malloc(r->dataSize);
        fread(r->data, 1, r->dataSize, f);
        pos += r->dataSize;
        align_to_4(f, &pos);
        res->count++;
    }
    fclose(f);
    return res;
}

void res_file_print_properties(RESFile *res) {
    for (size_t i = 0; i < res->count; i++) {
        Resource *r = &res->resources[i];
        printf("Resource %zu:\n", i);
        printf("dataSize: %u\n", r->dataSize);
        printf("headerSize: %u\n", r->headerSize);
        printf("type: %s\n", r->type);
        printf("name: %s\n", r->name);
        printf("dataVersion: %u\n", r->dataVersion);
        printf("memoryFlags: %u\n", r->memoryFlags);
        printf("languageId: %u\n", r->languageId);
        printf("version: %u\n", r->version);
        printf("characteristics: %u\n", r->characteristics);
        printf("\n");
    }
}

void write_unicode_or_id(FILE *f, const char *value, long *pos) {
    if (strspn(value, "0123456789") == strlen(value)) {
        uint16_t prefix = 0xFFFF;
        fwrite(&prefix, sizeof(uint16_t), 1, f);
        *pos += sizeof(uint16_t);
        uint16_t id = atoi(value);
        fwrite(&id, sizeof(uint16_t), 1, f);
        *pos += sizeof(uint16_t);
    } else {
        for (size_t i = 0; i < strlen(value); i++) {
            uint16_t charCode = (uint16_t) value[i];
            fwrite(&charCode, sizeof(uint16_t), 1, f);
            *pos += sizeof(uint16_t);
        }
        uint16_t zero = 0;
        fwrite(&zero, sizeof(uint16_t), 1, f);
        *pos += sizeof(uint16_t);
    }
}

void align_to_4_write(FILE *f, long *pos) {
    int pad = (4 - (*pos % 4)) % 4;
    for (int i = 0; i < pad; i++) {
        uint8_t zero = 0;
        fwrite(&zero, 1, 1, f);
    }
    *pos += pad;
}

void res_file_write(RESFile *res, const char *filename) {
    FILE *f = fopen(filename, "wb");
    if (!f) return;

    long pos = 0;
    for (size_t i = 0; i < res->count; i++) {
        Resource *r = &res->resources[i];
        long start_pos = pos;
        fwrite(&r->dataSize, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        uint32_t placeholder = 0;
        fwrite(&placeholder, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        write_unicode_or_id(f, r->type, &pos);
        align_to_4_write(f, &pos);
        write_unicode_or_id(f, r->name, &pos);
        align_to_4_write(f, &pos);
        fwrite(&r->dataVersion, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        fwrite(&r->memoryFlags, sizeof(uint16_t), 1, f);
        pos += sizeof(uint16_t);
        fwrite(&r->languageId, sizeof(uint16_t), 1, f);
        pos += sizeof(uint16_t);
        fwrite(&r->version, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        fwrite(&r->characteristics, sizeof(uint32_t), 1, f);
        pos += sizeof(uint32_t);
        long long header_size = pos - start_pos;
        fseek(f, start_pos + 4, SEEK_SET);
        fwrite(&header_size, sizeof(uint32_t), 1, f);
        fseek(f, pos, SEEK_SET);
        fwrite(r->data, 1, r->dataSize, f);
        pos += r->dataSize;
        align_to_4_write(f, &pos);
    }
    fclose(f);
}

void res_file_close(RESFile *res) {
    for (size_t i = 0; i < res->count; i++) {
        free(res->resources[i].type);
        free(res->resources[i].name);
        free(res->resources[i].data);
    }
    free(res->resources);
    free(res);
}

// Example usage
// RESFile *res = res_file_open("sample.res");
// res_file_print_properties(res);
// res_file_write(res, "output.res");
// res_file_close(res);