Task 606: .RBXL File Format

Task 606: .RBXL File Format

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

Based on the Roblox Binary Model Format specification, the intrinsic properties (structural fields, headers, and elements) are as follows:

File Header:

  • Magic Number: 8 bytes, always "<roblox!"
  • Signature: 6 bytes, always [0x89, 0xFF, 0x0D, 0x0A, 0x1A, 0x0A]
  • Version: Unsigned 16-bit integer (u16), always 0
  • Class Count: Signed 32-bit integer (i32), number of distinct classes
  • Instance Count: Signed 32-bit integer (i32), total number of instances
  • Reserved: 8 bytes, always zeros

Chunk Header (repeated for each chunk):

  • Chunk Name: 4 bytes (ASCII, e.g., "INST", padded with zeros if shorter)
  • Compressed Length: Unsigned 32-bit integer (u32), length of compressed data (0 if uncompressed)
  • Uncompressed Length: Unsigned 32-bit integer (u32), length after decompression
  • Reserved: 4 bytes, always zeros
  • Chunk Data: Variable length, compressed (LZ4 or ZSTD) or uncompressed

Compression Details:

  • Compression Type: Determined by first 4 bytes of chunk data if compressed; [0x28, 0xB5, 0x2F, 0xFD] indicates ZSTD, otherwise LZ4
  • ZSTD Frame: Present if ZSTD compressed

Chunk Types and Their Properties:

  • META Chunk (optional, 0 or 1):
  • Number of Metadata Entries: u32
  • Metadata Entries: Array of (Key: string, Value: string)
  • SSTR Chunk (optional, 0 or 1):
  • Version: u32, always 0
  • Shared String Count: u32
  • Shared Strings: Array of (MD5 Hash: 16 bytes, String: length-prefixed bytes)
  • INST Chunk (0 or more, one per class):
  • Class ID: u32
  • Class Name: string
  • Object Format: u8 (0 for regular, 1 for service)
  • Instance Count: u32
  • Referents: Array of i32 (transformed, interleaved)
  • Service Markers: Array of u8 (if Object Format == 1, one 1 per instance)
  • PROP Chunk (0 or more, one per property per class):
  • Class ID: u32
  • Property Name: string
  • Type ID: u8 (determines data type, e.g., 0x01 string, 0x02 bool, 0x03 int32)
  • Values: Array depending on Type ID (with transformations like interleaving, integer transform, float format)
  • PRNT Chunk (exactly 1):
  • Version: u8, always 0
  • Instance Count: u32
  • Child Referents: Array of i32
  • Parent Referents: Array of i32
  • END Chunk (exactly 1):
  • Magic Value: 9 bytes, always ""

Data Types for PROP Values (with Type IDs and encoding details):

  • 0x01 String: u32 length + bytes (UTF-8)
  • 0x02 Bool: u8 (0 false, 1 true)
  • 0x03 Int32: Big-endian i32, transformed (x2 for positive, -x*2-1 for negative), interleaved in arrays
  • 0x04 Float32: Big-endian custom format (sign, exponent, mantissa), interleaved in arrays
  • 0x05 Float64: Little-endian IEEE 754 double, no transform
  • 0x06 UDim: Float32 scale + Int32 offset
  • 0x07 UDim2: Two UDim
  • And others like Vector3, CFrame, etc., with specific structs and transforms

General Intrinsic Properties:

  • Endianness: Mostly little-endian, big-endian for some (e.g., int32, float32)
  • Referent Transformation: Relative differences in arrays
  • String Encoding: UTF-8 or ISO-8859-1 in older specs
  • File Validation: END chunk uncompressed for upload checks
  1. Two direct download links for .RBXL files:
  1. Ghost blog embedded HTML JavaScript for drag and drop .RBXL file to dump properties:
RBXL Property Dumper

Drag and Drop .RBXL File

Drop .RBXL file here

(Note: This is a basic dumper; full chunk parsing is omitted for simplicity but can be extended.)

  1. Python class for .RBXL handling:
import struct
import lz4.block
import zstandard as zstd

class RBXLHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.properties = {}

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

    def decode(self):
        offset = 0
        # File Header
        magic = self.data[offset:offset+8].decode('ascii')
        self.properties['Magic Number'] = magic
        offset += 8
        sig = list(self.data[offset:offset+6])
        self.properties['Signature'] = sig
        offset += 6
        version, = struct.unpack('<H', self.data[offset:offset+2])
        self.properties['Version'] = version
        offset += 2
        class_count, = struct.unpack('<i', self.data[offset:offset+4])
        self.properties['Class Count'] = class_count
        offset += 4
        instance_count, = struct.unpack('<i', self.data[offset:offset+4])
        self.properties['Instance Count'] = instance_count
        offset += 4
        self.properties['Reserved Header'] = [0] * 8
        offset += 8

        # Chunks (basic parse)
        chunks = []
        while offset < len(self.data):
            chunk_name = self.data[offset:offset+4].decode('ascii')
            offset += 4
            comp_len, = struct.unpack('<I', self.data[offset:offset+4])
            offset += 4
            uncomp_len, = struct.unpack('<I', self.data[offset:offset+4])
            offset += 4
            offset += 4  # reserved
            data_size = comp_len if comp_len else uncomp_len
            chunk_data = self.data[offset:offset+data_size]
            offset += data_size

            # Decompress if needed
            if comp_len:
                if chunk_data[:4] == b'\x28\xb5\x2f\xfd':
                    decompressor = zstd.ZstdDecompressor()
                    chunk_data = decompressor.decompress(chunk_data)
                else:
                    chunk_data = lz4.block.decompress(chunk_data, uncompressed_size=uncomp_len)

            chunks.append({'Name': chunk_name, 'Uncomp Len': uncomp_len, 'Data': len(chunk_data)})

        self.properties['Chunks'] = chunks

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, new_filepath):
        # Basic write (reassemble from data, placeholder for full encode)
        with open(new_filepath, 'wb') as f:
            f.write(self.data)

# Usage example:
# handler = RBXLHandler('example.rbxl')
# handler.read()
# handler.print_properties()
# handler.write('output.rbxl')

(Note: Full decode/encode of chunks is placeholder; extend for complete functionality.)

  1. Java class for .RBXL handling:
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class RBXLHandler {
    private String filepath;
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();

    public RBXLHandler(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        data = Files.readAllBytes(Paths.get(filepath));
        decode();
    }

    private void decode() {
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        int offset = 0;

        // File Header
        byte[] magicBytes = new byte[8];
        buffer.position(offset);
        buffer.get(magicBytes);
        properties.put("Magic Number", new String(magicBytes));
        offset += 8;
        byte[] sig = new byte[6];
        buffer.position(offset);
        buffer.get(sig);
        properties.put("Signature", Arrays.toString(sig));
        offset += 6;
        short version = buffer.getShort(offset);
        properties.put("Version", version);
        offset += 2;
        int classCount = buffer.getInt(offset);
        properties.put("Class Count", classCount);
        offset += 4;
        int instanceCount = buffer.getInt(offset);
        properties.put("Instance Count", instanceCount);
        offset += 4;
        properties.put("Reserved Header", "8 zeros");
        offset += 8;

        // Chunks (basic, no full decompress)
        List<Map<String, Object>> chunks = new ArrayList<>();
        while (offset < data.length) {
            byte[] nameBytes = new byte[4];
            buffer.position(offset);
            buffer.get(nameBytes);
            String chunkName = new String(nameBytes);
            offset += 4;
            int compLen = buffer.getInt(offset);
            offset += 4;
            int uncompLen = buffer.getInt(offset);
            offset += 4;
            offset += 4; // reserved
            int dataSize = compLen != 0 ? compLen : uncompLen;
            // Skip data parse for brevity
            offset += dataSize;

            Map<String, Object> chunk = new HashMap<>();
            chunk.put("Name", chunkName);
            chunk.put("Compressed Length", compLen);
            chunk.put("Uncompressed Length", uncompLen);
            chunks.add(chunk);
        }
        properties.put("Chunks", chunks);
    }

    public void printProperties() {
        properties.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public void write(String newFilepath) throws IOException {
        Files.write(Paths.get(newFilepath), data);
    }

    // Usage:
    // RBXLHandler handler = new RBXLHandler("example.rbxl");
    // handler.read();
    // handler.printProperties();
    // handler.write("output.rbxl");
}

(Note: Compression libraries like lz4-java or zstd-jni needed for full decompress; omitted here.)

  1. JavaScript class for .RBXL handling:
class RBXLHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
        this.data = null;
    }

    async read() {
        // Assume Node.js with fs
        const fs = require('fs');
        this.data = fs.readFileSync(this.filepath);
        this.decode();
    }

    decode() {
        const view = new DataView(this.data.buffer);
        let offset = 0;

        // File Header
        this.properties['Magic Number'] = getString(view, offset, 8);
        offset += 8;
        const sig = [];
        for (let i = 0; i < 6; i++) sig.push(view.getUint8(offset + i));
        this.properties['Signature'] = sig;
        offset += 6;
        this.properties['Version'] = view.getUint16(offset, true);
        offset += 2;
        this.properties['Class Count'] = view.getInt32(offset, true);
        offset += 4;
        this.properties['Instance Count'] = view.getInt32(offset, true);
        offset += 4;
        this.properties['Reserved Header'] = '8 zeros';
        offset += 8;

        // Chunks (basic)
        const chunks = [];
        while (offset < this.data.length) {
            const chunkName = getString(view, offset, 4);
            offset += 4;
            const compLen = view.getUint32(offset, true);
            offset += 4;
            const uncompLen = view.getUint32(offset, true);
            offset += 4;
            offset += 4; // reserved
            const dataSize = compLen || uncompLen;
            offset += dataSize;
            chunks.push({Name: chunkName, CompLen: compLen, UncompLen: uncompLen});
        }
        this.properties['Chunks'] = chunks;

        function getString(view, offset, len) {
            let s = '';
            for (let i = 0; i < len; i++) s += String.fromCharCode(view.getUint8(offset + i));
            return s;
        }
    }

    printProperties() {
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${JSON.stringify(value)}`);
        }
    }

    write(newFilepath) {
        const fs = require('fs');
        fs.writeFileSync(newFilepath, this.data);
    }
}

// Usage:
// const handler = new RBXLHandler('example.rbxl');
// await handler.read();
// handler.printProperties();
// handler.write('output.rbxl');

(Note: For browser, use FileReader; compression requires libs like lz4js, pako for zstd.)

  1. C++ class for .RBXL handling (using C++ for "c class"):
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>

// Placeholder for compression; use lz4/zstd libs in practice
class RBXLHandler {
private:
    std::string filepath;
    std::vector<uint8_t> data;
    std::map<std::string, std::string> properties; // Simplified as strings

public:
    RBXLHandler(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::binary);
        if (file) {
            data.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
            decode();
        }
    }

    void decode() {
        size_t offset = 0;
        // File Header
        std::string magic(reinterpret_cast<char*>(data.data() + offset), 8);
        properties["Magic Number"] = magic;
        offset += 8;
        std::string sig;
        for (size_t i = 0; i < 6; ++i) {
            sig += std::to_string(data[offset + i]) + " ";
        }
        properties["Signature"] = sig;
        offset += 6;
        uint16_t version = *reinterpret_cast<uint16_t*>(data.data() + offset);
        properties["Version"] = std::to_string(version);
        offset += 2;
        int32_t classCount = *reinterpret_cast<int32_t*>(data.data() + offset);
        properties["Class Count"] = std::to_string(classCount);
        offset += 4;
        int32_t instanceCount = *reinterpret_cast<int32_t*>(data.data() + offset);
        properties["Instance Count"] = std::to_string(instanceCount);
        offset += 4;
        properties["Reserved Header"] = "8 zeros";
        offset += 8;

        // Chunks (basic)
        std::string chunksStr;
        while (offset < data.size()) {
            std::string chunkName(reinterpret_cast<char*>(data.data() + offset), 4);
            offset += 4;
            uint32_t compLen = *reinterpret_cast<uint32_t*>(data.data() + offset);
            offset += 4;
            uint32_t uncompLen = *reinterpret_cast<uint32_t*>(data.data() + offset);
            offset += 4;
            offset += 4; // reserved
            size_t dataSize = compLen ? compLen : uncompLen;
            offset += dataSize;
            chunksStr += "Chunk: " + chunkName + ", CompLen: " + std::to_string(compLen) + ", UncompLen: " + std::to_string(uncompLen) + "\n";
        }
        properties["Chunks"] = chunksStr;
    }

    void printProperties() {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const std::string& newFilepath) {
        std::ofstream out(newFilepath, std::ios::binary);
        out.write(reinterpret_cast<const char*>(data.data()), data.size());
    }
};

// Usage:
// RBXLHandler handler("example.rbxl");
// handler.read();
// handler.printProperties();
// handler.write("output.rbxl");

(Note: Little-endian assumed; add endian swap if needed. Compression not implemented.)