Task 606: .RBXL File Format
Task 606: .RBXL File Format
- 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
- Two direct download links for .RBXL files:
- https://raw.githubusercontent.com/BlobJanitor/old-2000s-rblx-game-files/main/2006 - Angel Of Truth.rbxl
- https://raw.githubusercontent.com/BlobJanitor/old-2000s-rblx-game-files/main/2006 - Lava Rush.rbxl
- Ghost blog embedded HTML JavaScript for drag and drop .RBXL file to dump properties:
Drag and Drop .RBXL File
(Note: This is a basic dumper; full chunk parsing is omitted for simplicity but can be extended.)
- 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.)
- 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.)
- 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.)
- 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.)