Task 595: .R01 File Format

Task 595: .R01 File Format

1. List of Properties of the .R01 File Format Intrinsic to Its File System

The .R01 file format is part of the RAR archive format for multi-volume archives (typically RAR versions 1.5-4.0, where .rar is the first volume, .r00 the second, .r01 the third, etc.). It shares the same structure as other RAR volumes, including headers and blocks. The intrinsic properties refer to the structural and metadata fields in the format's headers, which define its layout, compression, encryption, and file system-like organization (e.g., files, directories, attributes). Based on the RAR format specifications, the key properties are:

  • Magic Signature: The 7-byte identifier (0x52 0x61 0x72 0x21 0x1A 0x07 0x00) at the start of the file.
  • Header CRC16: 2-byte CRC-16 checksum of the header.
  • Header Type: 1-byte value indicating block type (e.g., 0x73 for main archive header, 0x74 for file header).
  • Header Flags: 2-byte flags controlling behavior (e.g., volume, solid, locked, password, split before/after for multi-volume).
  • Header Size: 2-byte size of the header block.
  • Add Size: 4-byte size of additional data (if flag 0x8000 is set).
  • HighPosAV: 2-byte high part of AV position (main header).
  • PosAV: 4-byte AV position (main header).
  • EncryptVer: 1-byte encryption version (if flag 0x0200 is set in main header).
  • PackSize: 4-byte compressed size (low 32 bits; file header).
  • UnpSize: 4-byte uncompressed size (low 32 bits; file header).
  • HostOS: 1-byte host OS code (0=MS-DOS, 1=OS/2, 2=Windows, 3=Unix, 4=Mac OS, 5=BeOS).
  • FileCRC: 4-byte CRC-32 of the uncompressed file.
  • FileTime: 4-byte MS-DOS timestamp for modification time.
  • UnpVer: 1-byte version needed to unpack (e.g., 15, 20, 29).
  • Method: 1-byte compression method (0x30=store, 0x31=fastest, 0x32=fast, 0x33=normal, 0x34=good, 0x35=best).
  • NameSize: 2-byte length of the file name.
  • FileAttr: 4-byte file attributes (OS-specific).
  • HighPackSize: 4-byte compressed size (high 32 bits, if large file flag 0x0100 set).
  • HighUnpSize: 4-byte uncompressed size (high 32 bits, if large file flag 0x0100 set).
  • FileName: Variable-length string (up to NameSize bytes; Unicode if flag 0x0200 set).
  • Salt: 8-byte encryption salt (if flag 0x0400 set).
  • ExtTime: Variable-length extended timestamps (if flag 0x1000 set; includes ctime, atime, arctime with fractional seconds).
  • Compression Algorithm: Derived from UnpVer (e.g., Unpack15 for 15, Unpack20 for 20/26, Unpack29 for 29/36; uses LZSS and PPM).
  • Encryption: Password-based (if flag 0x0004 set; supports salt and version).
  • Multi-Volume Flags: Split before (0x0001), split after (0x0002) for volume spanning.
  • Solid Flag: 0x0010 if files are compressed together across volumes.
  • Unicode Flag: 0x0200 if file name is Unicode.

These properties allow the format to function as a compressed file system, supporting directories (via file attr), timestamps, attributes, and spanning.

Despite extensive searches, direct download links for sample .R01 files were not found in reliable sources (most results were for NIH R01 grant documents or general RAR files). .R01 files are typically part of multi-volume sets and not commonly hosted as isolated test files. As a fallback, here are direct links to sample multi-volume RAR archives that include .r01-like parts (e.g., .part03.rar, which is structurally identical):

For real .R01, you can create one by splitting a RAR archive using WinRAR with the old naming scheme.

3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .R01 File Dump

Here is an HTML page with embedded JavaScript that can be embedded in a Ghost blog post. It allows drag-and-drop of a .R01 file and dumps the properties to the screen using a simple parser based on the RAR format.

.R01 Properties Dumper
Drag and drop a .R01 file here

4. Python Class for .R01 File

Here is a Python class that can open, decode, read, write, and print the properties from a .R01 file. The write method creates a simple .R01-like file with a main header and one file header (for demonstration; full write is complex).

import struct

class R01Handler:
    def __init__(self, filename):
        self.filename = filename
        self.data = None

    def read(self):
        with open(self.filename, 'rb') as f:
            self.data = f.read()

    def decode_and_print(self):
        if self.data is None:
            self.read()
        view = memoryview(self.data)
        pos = 0
        signature = ' '.join(f'{b:02x}' for b in view[pos:pos+7])
        print(f'Magic Signature: {signature}')
        pos += 7

        while pos < len(view):
            crc16, = struct.unpack_from('<H', view, pos)
            pos += 2
            type_, = struct.unpack_from('<B', view, pos)
            pos += 1
            flags, = struct.unpack_from('<H', view, pos)
            pos += 2
            size, = struct.unpack_from('<H', view, pos)
            pos += 2
            print(f'Header CRC16: 0x{crc16:04x}')
            print(f'Header Type: 0x{type_:02x}')
            print(f'Header Flags: 0x{flags:04x}')
            print(f'Header Size: {size}')

            if flags & 0x8000:
                add_size, = struct.unpack_from('<I', view, pos)
                pos += 4
                print(f'Add Size: {add_size}')

            if type_ == 0x73:  # Main header
                high_pos_av, = struct.unpack_from('<H', view, pos)
                pos += 2
                pos_av, = struct.unpack_from('<I', view, pos)
                pos += 4
                print(f'HighPosAV: {high_pos_av}')
                print(f'PosAV: {pos_av}')
                if flags & 0x0200:
                    encrypt_ver, = struct.unpack_from('<B', view, pos)
                    pos += 1
                    print(f'EncryptVer: {encrypt_ver}')

            elif type_ == 0x74:  # File header
                pack_size, = struct.unpack_from('<I', view, pos)
                pos += 4
                unp_size, = struct.unpack_from('<I', view, pos)
                pos += 4
                host_os, = struct.unpack_from('<B', view, pos)
                pos += 1
                file_crc, = struct.unpack_from('<I', view, pos)
                pos += 4
                file_time, = struct.unpack_from('<I', view, pos)
                pos += 4
                unp_ver, = struct.unpack_from('<B', view, pos)
                pos += 1
                method, = struct.unpack_from('<B', view, pos)
                pos += 1
                name_size, = struct.unpack_from('<H', view, pos)
                pos += 2
                file_attr, = struct.unpack_from('<I', view, pos)
                pos += 4
                print(f'PackSize: {pack_size}')
                print(f'UnpSize: {unp_size}')
                print(f'HostOS: {host_os}')
                print(f'FileCRC: 0x{file_crc:08x}')
                print(f'FileTime: {file_time}')
                print(f'UnpVer: {unp_ver}')
                print(f'Method: 0x{method:02x}')
                print(f'NameSize: {name_size}')
                print(f'FileAttr: 0x{file_attr:08x}')

                if flags & 0x0100:
                    high_pack_size, = struct.unpack_from('<I', view, pos)
                    pos += 4
                    high_unp_size, = struct.unpack_from('<I', view, pos)
                    pos += 4
                    print(f'HighPackSize: {high_pack_size}')
                    print(f'HighUnpSize: {high_unp_size}')

                file_name = view[pos:pos+name_size].tobytes().decode('utf-8', errors='ignore')
                pos += name_size
                print(f'FileName: {file_name}')

                if flags & 0x0400:
                    salt = ' '.join(f'{b:02x}' for b in view[pos:pos+8])
                    pos += 8
                    print(f'Salt: {salt}')

                if flags & 0x1000:
                    ext_flags, = struct.unpack_from('<H', view, pos)
                    pos += 2
                    print(f'ExtTime Flags: 0x{ext_flags:04x}')
                    # Further parsing omitted for brevity

            # Skip to next (simplified; adjust for exact size)
            pos += size - 7 - (4 if flags & 0x8000 else 0)

    def write(self, output_filename, sample_data=b'Hello World'):
        # Simple write: create a basic .R01 with main and file header (no compression)
        sig = b'\x52\x61\x72\x21\x1A\x07\x00'
        # Main header
        main_header = struct.pack('<H B H H', 0, 0x73, 0, 13)  # CRC, type, flags, size (dummy)
        main_body = struct.pack('<H I B', 0, 0, 0)  # HighPosAV, PosAV, EncryptVer
        # File header
        file_header = struct.pack('<H B H H', 0, 0x74, 0, 32 + len(sample_data))  # CRC, type, flags, size
        file_body = struct.pack('<I I B I I B B H I', len(sample_data), len(sample_data), 2, 0, 0, 29, 0x30, 11, 0)  # Pack/Unp size, HostOS, CRC, Time, Ver, Method, NameSize, Attr
        file_name = b'sample.txt'
        file_body += file_name
        file_body += sample_data  # "Compressed" data (stored)

        data = sig + main_header + main_body + file_header + file_body
        with open(output_filename, 'wb') as f:
            f.write(data)
        print(f'Wrote sample .R01 to {output_filename}')

5. Java Class for .R01 File

Here is a Java class that can open, decode, read, write, and print the properties from a .R01 file.

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

public class R01Handler {
    private String filename;
    private byte[] data;

    public R01Handler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            data = fis.readAllBytes();
        }
    }

    public void decodeAndPrint() throws IOException {
        if (data == null) read();
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        int pos = 0;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 7; i++) sb.append(String.format("%02x ", bb.get(pos++)));
        System.out.println("Magic Signature: " + sb.toString().trim());

        while (pos < data.length) {
            short crc16 = bb.getShort(pos);
            pos += 2;
            byte type = bb.get(pos);
            pos += 1;
            short flags = bb.getShort(pos);
            pos += 2;
            short size = bb.getShort(pos);
            pos += 2;
            System.out.println("Header CRC16: 0x" + Integer.toHexString(crc16 & 0xFFFF));
            System.out.println("Header Type: 0x" + Integer.toHexString(type & 0xFF));
            System.out.println("Header Flags: 0x" + Integer.toHexString(flags & 0xFFFF));
            System.out.println("Header Size: " + (size & 0xFFFF));

            if ((flags & 0x8000) != 0) {
                int addSize = bb.getInt(pos);
                pos += 4;
                System.out.println("Add Size: " + addSize);
            }

            if (type == 0x73) { // Main header
                short highPosAv = bb.getShort(pos);
                pos += 2;
                int posAv = bb.getInt(pos);
                pos += 4;
                System.out.println("HighPosAV: " + highPosAv);
                System.out.println("PosAV: " + posAv);
                if ((flags & 0x0200) != 0) {
                    byte encryptVer = bb.get(pos);
                    pos += 1;
                    System.out.println("EncryptVer: " + (encryptVer & 0xFF));
                }
            } else if (type == 0x74) { // File header
                int packSize = bb.getInt(pos);
                pos += 4;
                int unpSize = bb.getInt(pos);
                pos += 4;
                byte hostOS = bb.get(pos);
                pos += 1;
                int fileCRC = bb.getInt(pos);
                pos += 4;
                int fileTime = bb.getInt(pos);
                pos += 4;
                byte unpVer = bb.get(pos);
                pos += 1;
                byte method = bb.get(pos);
                pos += 1;
                short nameSize = bb.getShort(pos);
                pos += 2;
                int fileAttr = bb.getInt(pos);
                pos += 4;
                System.out.println("PackSize: " + packSize);
                System.out.println("UnpSize: " + unpSize);
                System.out.println("HostOS: " + (hostOS & 0xFF));
                System.out.println("FileCRC: 0x" + Integer.toHexString(fileCRC));
                System.out.println("FileTime: " + fileTime);
                System.out.println("UnpVer: " + (unpVer & 0xFF));
                System.out.println("Method: 0x" + Integer.toHexString(method & 0xFF));
                System.out.println("NameSize: " + (nameSize & 0xFFFF));
                System.out.println("FileAttr: 0x" + Integer.toHexString(fileAttr));

                if ((flags & 0x0100) != 0) {
                    int highPackSize = bb.getInt(pos);
                    pos += 4;
                    int highUnpSize = bb.getInt(pos);
                    pos += 4;
                    System.out.println("HighPackSize: " + highPackSize);
                    System.out.println("HighUnpSize: " + highUnpSize);
                }

                String fileName = new String(data, pos, nameSize);
                pos += nameSize;
                System.out.println("FileName: " + fileName);

                if ((flags & 0x0400) != 0) {
                    sb = new StringBuilder();
                    for (int i = 0; i < 8; i++) sb.append(String.format("%02x ", bb.get(pos++)));
                    System.out.println("Salt: " + sb.toString().trim());
                }

                if ((flags & 0x1000) != 0) {
                    short extFlags = bb.getShort(pos);
                    pos += 2;
                    System.out.println("ExtTime Flags: 0x" + Integer.toHexString(extFlags & 0xFFFF));
                    // Further parsing omitted
                }
            }

            // Skip to next
            pos += size - 7 - (((flags & 0x8000) != 0) ? 4 : 0);
        }
    }

    public void write(String outputFilename, byte[] sampleData) throws IOException {
        // Simple write: basic .R01 with main and file header
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(new byte[]{0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00});
        // Main header
        ByteBuffer mainBb = ByteBuffer.allocate(13).order(ByteOrder.LITTLE_ENDIAN);
        mainBb.putShort((short)0); // CRC
        mainBb.put((byte)0x73); // Type
        mainBb.putShort((short)0); // Flags
        mainBb.putShort((short)13); // Size
        mainBb.putShort((short)0); // HighPosAV
        mainBb.putInt(0); // PosAV
        mainBb.put((byte)0); // EncryptVer
        baos.write(mainBb.array());
        // File header
        int nameLen = 11; // "sample.txt"
        int headerSize = 32 + nameLen;
        ByteBuffer fileBb = ByteBuffer.allocate(headerSize).order(ByteOrder.LITTLE_ENDIAN);
        fileBb.putShort((short)0); // CRC
        fileBb.put((byte)0x74); // Type
        fileBb.putShort((short)0); // Flags
        fileBb.putShort((short)headerSize); // Size
        fileBb.putInt(sampleData.length); // PackSize
        fileBb.putInt(sampleData.length); // UnpSize
        fileBb.put((byte)2); // HostOS
        fileBb.putInt(0); // FileCRC
        fileBb.putInt(0); // FileTime
        fileBb.put((byte)29); // UnpVer
        fileBb.put((byte)0x30); // Method
        fileBb.putShort((short)nameLen); // NameSize
        fileBb.putInt(0); // FileAttr
        fileBb.put("sample.txt".getBytes()); // Name
        baos.write(fileBb.array());
        baos.write(sampleData); // Data

        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(baos.toByteArray());
        }
        System.out.println("Wrote sample .R01 to " + outputFilename);
    }
}

6. JavaScript Class for .R01 File

Here is a JavaScript class (for Node.js) that can open, decode, read, write, and print the properties from a .R01 file.

const fs = require('fs');

class R01Handler {
  constructor(filename) {
    this.filename = filename;
    this.data = null;
  }

  read() {
    this.data = fs.readFileSync(this.filename);
  }

  decodeAndPrint() {
    if (!this.data) this.read();
    const view = new DataView(this.data.buffer);
    let pos = 0;
    let sb = '';
    for (let i = 0; i < 7; i++) sb += view.getUint8(pos++).toString(16).padStart(2, '0') + ' ';
    console.log('Magic Signature: ' + sb.trim());

    while (pos < this.data.length) {
      const crc16 = view.getUint16(pos, true);
      pos += 2;
      const type = view.getUint8(pos);
      pos += 1;
      const flags = view.getUint16(pos, true);
      pos += 2;
      const size = view.getUint16(pos, true);
      pos += 2;
      console.log('Header CRC16: 0x' + crc16.toString(16));
      console.log('Header Type: 0x' + type.toString(16));
      console.log('Header Flags: 0x' + flags.toString(16));
      console.log('Header Size: ' + size);

      if (flags & 0x8000) {
        const addSize = view.getUint32(pos, true);
        pos += 4;
        console.log('Add Size: ' + addSize);
      }

      if (type === 0x73) { // Main header
        const highPosAv = view.getUint16(pos, true);
        pos += 2;
        const posAv = view.getUint32(pos, true);
        pos += 4;
        console.log('HighPosAV: ' + highPosAv);
        console.log('PosAV: ' + posAv);
        if (flags & 0x0200) {
          const encryptVer = view.getUint8(pos);
          pos += 1;
          console.log('EncryptVer: ' + encryptVer);
        }
      } else if (type === 0x74) { // File header
        const packSize = view.getUint32(pos, true);
        pos += 4;
        const unpSize = view.getUint32(pos, true);
        pos += 4;
        const hostOS = view.getUint8(pos);
        pos += 1;
        const fileCRC = view.getUint32(pos, true);
        pos += 4;
        const fileTime = view.getUint32(pos, true);
        pos += 4;
        const unpVer = view.getUint8(pos);
        pos += 1;
        const method = view.getUint8(pos);
        pos += 1;
        const nameSize = view.getUint16(pos, true);
        pos += 2;
        const fileAttr = view.getUint32(pos, true);
        pos += 4;
        console.log('PackSize: ' + packSize);
        console.log('UnpSize: ' + unpSize);
        console.log('HostOS: ' + hostOS);
        console.log('FileCRC: 0x' + fileCRC.toString(16));
        console.log('FileTime: ' + fileTime);
        console.log('UnpVer: ' + unpVer);
        console.log('Method: 0x' + method.toString(16));
        console.log('NameSize: ' + nameSize);
        console.log('FileAttr: 0x' + fileAttr.toString(16));

        if (flags & 0x0100) {
          const highPackSize = view.getUint32(pos, true);
          pos += 4;
          const highUnpSize = view.getUint32(pos, true);
          pos += 4;
          console.log('HighPackSize: ' + highPackSize);
          console.log('HighUnpSize: ' + highUnpSize);
        }

        const fileName = new TextDecoder().decode(this.data.subarray(pos, pos + nameSize));
        pos += nameSize;
        console.log('FileName: ' + fileName);

        if (flags & 0x0400) {
          sb = '';
          for (let i = 0; i < 8; i++) sb += view.getUint8(pos++).toString(16).padStart(2, '0') + ' ';
          console.log('Salt: ' + sb.trim());
        }

        if (flags & 0x1000) {
          const extFlags = view.getUint16(pos, true);
          pos += 2;
          console.log('ExtTime Flags: 0x' + extFlags.toString(16));
          // Further parsing omitted
        }
      }

      pos += size - 7 - (flags & 0x8000 ? 4 : 0);
    }
  }

  write(outputFilename, sampleData = Buffer.from('Hello World')) {
    const sig = Buffer.from([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00]);
    // Main header
    const mainHeader = Buffer.alloc(13);
    const mainView = new DataView(mainHeader.buffer);
    mainView.setUint16(0, 0, true); // CRC
    mainView.setUint8(2, 0x73); // Type
    mainView.setUint16(3, 0, true); // Flags
    mainView.setUint16(5, 13, true); // Size
    mainView.setUint16(7, 0, true); // HighPosAV
    mainView.setUint32(9, 0, true); // PosAV
    mainView.setUint8(13, 0); // EncryptVer (offset adjusted)
    // File header
    const name = Buffer.from('sample.txt');
    const headerSize = 32 + name.length;
    const fileHeader = Buffer.alloc(headerSize);
    const fileView = new DataView(fileHeader.buffer);
    fileView.setUint16(0, 0, true); // CRC
    fileView.setUint8(2, 0x74); // Type
    fileView.setUint16(3, 0, true); // Flags
    fileView.setUint16(5, headerSize, true); // Size
    fileView.setUint32(7, sampleData.length, true); // PackSize
    fileView.setUint32(11, sampleData.length, true); // UnpSize
    fileView.setUint8(15, 2); // HostOS
    fileView.setUint32(16, 0, true); // FileCRC
    fileView.setUint32(20, 0, true); // FileTime
    fileView.setUint8(24, 29); // UnpVer
    fileView.setUint8(25, 0x30); // Method
    fileView.setUint16(26, name.length, true); // NameSize
    fileView.setUint32(28, 0, true); // FileAttr
    name.copy(fileHeader, 32);
    const fullData = Buffer.concat([sig, mainHeader, fileHeader, sampleData]);
    fs.writeFileSync(outputFilename, fullData);
    console.log(`Wrote sample .R01 to ${outputFilename}`);
  }
}

7. C Class for .R01 File

Here is a C++ class that can open, decode, read, write, and print the properties from a .R01 file.

#include <fstream>
#include <iostream>
#include <vector>
#include <iomanip>

class R01Handler {
private:
    std::string filename;
    std::vector<unsigned char> data;

public:
    R01Handler(const std::string& fn) : filename(fn) {}

    void read() {
        std::ifstream file(filename, std::ios::binary);
        if (file) {
            data = std::vector<unsigned char>((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        }
    }

    void decodeAndPrint() {
        if (data.empty()) read();
        size_t pos = 0;
        std::cout << "Magic Signature: ";
        for (int i = 0; i < 7; ++i) std::cout << std::hex << std::setfill('0') << std::setw(2) << (int)data[pos++] << " ";
        std::cout << std::dec << std::endl;

        while (pos < data.size()) {
            unsigned short crc16 = *(unsigned short*)(&data[pos]);
            pos += 2;
            unsigned char type = data[pos++];
            unsigned short flags = *(unsigned short*)(&data[pos]);
            pos += 2;
            unsigned short size = *(unsigned short*)(&data[pos]);
            pos += 2;
            std::cout << "Header CRC16: 0x" << std::hex << crc16 << std::dec << std::endl;
            std::cout << "Header Type: 0x" << std::hex << (int)type << std::dec << std::endl;
            std::cout << "Header Flags: 0x" << std::hex << flags << std::dec << std::endl;
            std::cout << "Header Size: " << size << std::endl;

            if (flags & 0x8000) {
                unsigned int addSize = *(unsigned int*)(&data[pos]);
                pos += 4;
                std::cout << "Add Size: " << addSize << std::endl;
            }

            if (type == 0x73) { // Main header
                unsigned short highPosAv = *(unsigned short*)(&data[pos]);
                pos += 2;
                unsigned int posAv = *(unsigned int*)(&data[pos]);
                pos += 4;
                std::cout << "HighPosAV: " << highPosAv << std::endl;
                std::cout << "PosAV: " << posAv << std::endl;
                if (flags & 0x0200) {
                    unsigned char encryptVer = data[pos++];
                    std::cout << "EncryptVer: " << (int)encryptVer << std::endl;
                }
            } else if (type == 0x74) { // File header
                unsigned int packSize = *(unsigned int*)(&data[pos]);
                pos += 4;
                unsigned int unpSize = *(unsigned int*)(&data[pos]);
                pos += 4;
                unsigned char hostOS = data[pos++];
                unsigned int fileCRC = *(unsigned int*)(&data[pos]);
                pos += 4;
                unsigned int fileTime = *(unsigned int*)(&data[pos]);
                pos += 4;
                unsigned char unpVer = data[pos++];
                unsigned char method = data[pos++];
                unsigned short nameSize = *(unsigned short*)(&data[pos]);
                pos += 2;
                unsigned int fileAttr = *(unsigned int*)(&data[pos]);
                pos += 4;
                std::cout << "PackSize: " << packSize << std::endl;
                std::cout << "UnpSize: " << unpSize << std::endl;
                std::cout << "HostOS: " << (int)hostOS << std::endl;
                std::cout << "FileCRC: 0x" << std::hex << fileCRC << std::dec << std::endl;
                std::cout << "FileTime: " << fileTime << std::endl;
                std::cout << "UnpVer: " << (int)unpVer << std::endl;
                std::cout << "Method: 0x" << std::hex << (int)method << std::dec << std::endl;
                std::cout << "NameSize: " << nameSize << std::endl;
                std::cout << "FileAttr: 0x" << std::hex << fileAttr << std::dec << std::endl;

                if (flags & 0x0100) {
                    unsigned int highPackSize = *(unsigned int*)(&data[pos]);
                    pos += 4;
                    unsigned int highUnpSize = *(unsigned int*)(&data[pos]);
                    pos += 4;
                    std::cout << "HighPackSize: " << highPackSize << std::endl;
                    std::cout << "HighUnpSize: " << highUnpSize << std::endl;
                }

                std::string fileName(data.begin() + pos, data.begin() + pos + nameSize);
                pos += nameSize;
                std::cout << "FileName: " << fileName << std::endl;

                if (flags & 0x0400) {
                    std::cout << "Salt: ";
                    for (int i = 0; i < 8; ++i) std::cout << std::hex << std::setfill('0') << std::setw(2) << (int)data[pos++] << " ";
                    std::cout << std::dec << std::endl;
                }

                if (flags & 0x1000) {
                    unsigned short extFlags = *(unsigned short*)(&data[pos]);
                    pos += 2;
                    std::cout << "ExtTime Flags: 0x" << std::hex << extFlags << std::dec << std::endl;
                    // Further parsing omitted
                }
            }

            pos += size - 7 - ((flags & 0x8000) ? 4 : 0);
        }
    }

    void write(const std::string& outputFilename, const std::vector<unsigned char>& sampleData) {
        std::ofstream file(outputFilename, std::ios::binary);
        if (!file) return;

        unsigned char sig[7] = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00};
        file.write((char*)sig, 7);

        // Main header (dummy)
        unsigned char mainHeader[13] = {0}; // CRC, type 0x73, flags 0, size 13, HighPosAV 0, PosAV 0, EncryptVer 0
        mainHeader[2] = 0x73;
        mainHeader[5] = 13; // Low byte of size
        file.write((char*)mainHeader, 13);

        // File header
        std::string name = "sample.txt";
        size_t headerSize = 32 + name.size();
        std::vector<unsigned char> fileHeader(headerSize, 0);
        fileHeader[2] = 0x74; // Type
        *(unsigned short*)(&fileHeader[5]) = headerSize; // Size
        *(unsigned int*)(&fileHeader[7]) = sampleData.size(); // PackSize
        *(unsigned int*)(&fileHeader[11]) = sampleData.size(); // UnpSize
        fileHeader[15] = 2; // HostOS
        fileHeader[24] = 29; // UnpVer
        fileHeader[25] = 0x30; // Method
        *(unsigned short*)(&fileHeader[26]) = name.size(); // NameSize
        name.copy((char*)&fileHeader[32], name.size());
        file.write((char*)fileHeader.data(), headerSize);
        file.write((char*)sampleData.data(), sampleData.size());

        std::cout << "Wrote sample .R01 to " << outputFilename << std::endl;
    }
};