Task 002: .3DSX File Format
Task 002: .3DSX File Format
1. Properties of the .3DSX File Format
The .3DSX file format is an executable format designed for homebrew applications on the Nintendo 3DS console. It includes a structured header, optional extended header, relocation tables, code segments, and optional metadata sections. The intrinsic properties, derived from the file's binary structure and specifications, are as follows:
- Magic Identifier: A 4-byte string "3DSX" at offset 0x0, serving as the file signature.
- Header Size: A 2-byte unsigned integer at offset 0x4, indicating the total size of the header (typically 0x20 for basic headers or 0x2C for extended headers).
- Relocation Header Size: A 2-byte unsigned integer at offset 0x6, specifying the size of each relocation table header (typically 0x08).
- Format Version: A 4-byte unsigned integer at offset 0x8, denoting the version of the format (commonly 0).
- Flags: A 4-byte unsigned integer at offset 0xC, reserved for flags (often 0 in practice).
- Code Segment Size: A 4-byte unsigned integer at offset 0x10, representing the size of the code segment in bytes.
- Read-Only Data (Rodata) Segment Size: A 4-byte unsigned integer at offset 0x14, representing the size of the read-only data segment in bytes.
- Data Segment Size: A 4-byte unsigned integer at offset 0x18, representing the size of the data segment in bytes (including the BSS section).
- BSS Segment Size: A 4-byte unsigned integer at offset 0x1C, representing the size of the uninitialized data (BSS) section in bytes.
- SMDH Offset (extended header only): A 4-byte unsigned integer at offset 0x20 (relative to header start), indicating the file offset to the SMDH (icon and title metadata) section, if present.
- SMDH Size (extended header only): A 4-byte unsigned integer at offset 0x24, specifying the size of the SMDH section in bytes (typically 0x36C0 if present).
- RomFS Offset (extended header only): A 4-byte unsigned integer at offset 0x28, indicating the file offset to the RomFS (embedded file system) section, if present.
- Code Relocation Counts: In the code relocation header (8 bytes following the main header), 4-byte unsigned integers for the number of absolute and relative relocations (relative often 0).
- Rodata Relocation Counts: Similar to code, in the rodata relocation header (next 8 bytes), counts for absolute and relative relocations.
- Data Relocation Counts: Similar to above, in the data relocation header (next 8 bytes), counts for absolute and relative relocations.
These properties define the file's layout, including segment alignments (typically 0x1000-byte aligned) and relocation entries (each 4 bytes: 2-byte skip count and 2-byte patch count, repeated per relocation count).
2. Direct Download Links for .3DSX Files
The following are two direct download links to sample .3DSX files from public repositories:
- https://raw.githubusercontent.com/norips/3DS_Motion/master/3DS_Motion.3dsx
- https://raw.githubusercontent.com/meladroit/svdt/master/svdt.3dsx
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .3DSX File Dumping
The following is a self-contained HTML snippet with embedded JavaScript that can be inserted into a Ghost blog post (or any HTML-based platform). It enables users to drag and drop a .3DSX file, parses the file using a DataView, extracts the properties listed above, and displays them on the screen.
4. Python Class for .3DSX File Handling
The following Python class uses the struct module to decode, read, write, and print the properties of a .3DSX file. It assumes little-endian byte order based on the format specification. The read method parses the file and stores properties in a dictionary. The print_properties method outputs them to the console. The write method allows creating or modifying a file with given properties (minimal implementation, focusing on header; full segment writing requires additional data).
import struct
class ThreeDSXHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
def read(self):
with open(self.filepath, 'rb') as f:
data = f.read()
# Unpack header
magic, header_size, reloc_hdr_size, format_ver, flags, code_size, rodata_size, data_size, bss_size = struct.unpack('<4sHHIIIII', data[:32])
self.properties['magic'] = magic.decode('ascii')
self.properties['header_size'] = header_size
self.properties['reloc_header_size'] = reloc_hdr_size
self.properties['format_version'] = format_ver
self.properties['flags'] = flags
self.properties['code_segment_size'] = code_size
self.properties['rodata_segment_size'] = rodata_size
self.properties['data_segment_size'] = data_size
self.properties['bss_segment_size'] = bss_size
offset = 32
if header_size > 32:
smdh_offset, smdh_size, romfs_offset = struct.unpack('<III', data[offset:offset+12])
self.properties['smdh_offset'] = smdh_offset
self.properties['smdh_size'] = smdh_size
self.properties['romfs_offset'] = romfs_offset
offset += 12
# Relocation headers
reloc_start = offset
code_abs, code_rel = struct.unpack('<II', data[reloc_start:reloc_start+8])
rodata_abs, rodata_rel = struct.unpack('<II', data[reloc_start+8:reloc_start+16])
data_abs, data_rel = struct.unpack('<II', data[reloc_start+16:reloc_start+24])
self.properties['code_abs_relocs'] = code_abs
self.properties['code_rel_relocs'] = code_rel
self.properties['rodata_abs_relocs'] = rodata_abs
self.properties['rodata_rel_relocs'] = rodata_rel
self.properties['data_abs_relocs'] = data_abs
self.properties['data_rel_relocs'] = data_rel
def print_properties(self):
for key, value in self.properties.items():
print(f"{key.capitalize().replace('_', ' ')}: {value if isinstance(value, str) else hex(value)}")
def write(self, output_path, properties=None):
if properties is None:
properties = self.properties
header = struct.pack('<4sHHIIIII',
properties['magic'].encode('ascii'),
properties['header_size'],
properties['reloc_header_size'],
properties['format_version'],
properties['flags'],
properties['code_segment_size'],
properties['rodata_segment_size'],
properties['data_segment_size'],
properties['bss_segment_size'])
extended = b''
if properties['header_size'] > 32:
extended = struct.pack('<III',
properties['smdh_offset'],
properties['smdh_size'],
properties['romfs_offset'])
relocs = struct.pack('<IIIIII',
properties['code_abs_relocs'], properties['code_rel_relocs'],
properties['rodata_abs_relocs'], properties['rodata_rel_relocs'],
properties['data_abs_relocs'], properties['data_rel_relocs'])
with open(output_path, 'wb') as f:
f.write(header + extended + relocs)
# Additional segments/reloc entries would be appended here if provided
# Example usage:
# handler = ThreeDSXHandler('example.3dsx')
# handler.read()
# handler.print_properties()
# handler.write('output.3dsx')
5. Java Class for .3DSX File Handling
The following Java class uses ByteBuffer for decoding, reading, writing, and printing the properties. It handles little-endian order. The read method parses the file into a Map. The printProperties method outputs to the console. The write method supports creating or modifying the header section.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
public class ThreeDSXHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public ThreeDSXHandler(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
try (FileInputStream fis = new FileInputStream(filepath);
FileChannel channel = fis.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
byte[] magicBytes = new byte[4];
buffer.get(magicBytes);
properties.put("magic", new String(magicBytes));
properties.put("header_size", (int) buffer.getShort());
properties.put("reloc_header_size", (int) buffer.getShort());
properties.put("format_version", buffer.getInt());
properties.put("flags", buffer.getInt());
properties.put("code_segment_size", buffer.getInt());
properties.put("rodata_segment_size", buffer.getInt());
properties.put("data_segment_size", buffer.getInt());
properties.put("bss_segment_size", buffer.getInt());
int headerSize = (int) properties.get("header_size");
int offset = 32;
if (headerSize > 32) {
properties.put("smdh_offset", buffer.getInt(offset));
properties.put("smdh_size", buffer.getInt(offset + 4));
properties.put("romfs_offset", buffer.getInt(offset + 8));
offset += 12;
}
int relocStart = offset;
properties.put("code_abs_relocs", buffer.getInt(relocStart));
properties.put("code_rel_relocs", buffer.getInt(relocStart + 4));
properties.put("rodata_abs_relocs", buffer.getInt(relocStart + 8));
properties.put("rodata_rel_relocs", buffer.getInt(relocStart + 12));
properties.put("data_abs_relocs", buffer.getInt(relocStart + 16));
properties.put("data_rel_relocs", buffer.getInt(relocStart + 20));
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey().replace("_", " ").toUpperCase();
Object value = entry.getValue();
String valStr = (value instanceof String) ? (String) value : "0x" + Integer.toHexString((Integer) value);
System.out.println(key + ": " + valStr);
}
}
public void write(String outputPath, Map<String, Object> props) throws IOException {
if (props == null) {
props = properties;
}
ByteBuffer buffer = ByteBuffer.allocate(64); // Sufficient for header + relocs
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put(((String) props.get("magic")).getBytes());
buffer.putShort(((Integer) props.get("header_size")).shortValue());
buffer.putShort(((Integer) props.get("reloc_header_size")).shortValue());
buffer.putInt((Integer) props.get("format_version"));
buffer.putInt((Integer) props.get("flags"));
buffer.putInt((Integer) props.get("code_segment_size"));
buffer.putInt((Integer) props.get("rodata_segment_size"));
buffer.putInt((Integer) props.get("data_segment_size"));
buffer.putInt((Integer) props.get("bss_segment_size"));
int headerSize = (Integer) props.get("header_size");
if (headerSize > 32) {
buffer.putInt((Integer) props.get("smdh_offset"));
buffer.putInt((Integer) props.get("smdh_size"));
buffer.putInt((Integer) props.get("romfs_offset"));
}
buffer.putInt((Integer) props.get("code_abs_relocs"));
buffer.putInt((Integer) props.get("code_rel_relocs"));
buffer.putInt((Integer) props.get("rodata_abs_relocs"));
buffer.putInt((Integer) props.get("rodata_rel_relocs"));
buffer.putInt((Integer) props.get("data_abs_relocs"));
buffer.putInt((Integer) props.get("data_rel_relocs"));
buffer.flip();
try (FileOutputStream fos = new FileOutputStream(outputPath);
FileChannel channel = fos.getChannel()) {
channel.write(buffer);
// Append segments/reloc entries if needed
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// ThreeDSXHandler handler = new ThreeDSXHandler("example.3dsx");
// handler.read();
// handler.printProperties();
// handler.write("output.3dsx", null);
// }
}
6. JavaScript Class for .3DSX File Handling
The following JavaScript class uses DataView for browser-based or Node.js file handling (with fs for Node). The read method parses an ArrayBuffer and stores properties. The printProperties method logs to the console. The write method generates a new ArrayBuffer for the header.
class ThreeDSXHandler {
constructor(buffer) {
this.buffer = buffer; // ArrayBuffer
this.properties = {};
}
read() {
const dataView = new DataView(this.buffer);
const magic = String.fromCharCode(...new Uint8Array(this.buffer.slice(0, 4)));
this.properties.magic = magic;
this.properties.header_size = dataView.getUint16(4, true);
this.properties.reloc_header_size = dataView.getUint16(6, true);
this.properties.format_version = dataView.getUint32(8, true);
this.properties.flags = dataView.getUint32(12, true);
this.properties.code_segment_size = dataView.getUint32(16, true);
this.properties.rodata_segment_size = dataView.getUint32(20, true);
this.properties.data_segment_size = dataView.getUint32(24, true);
this.properties.bss_segment_size = dataView.getUint32(28, true);
let offset = 32;
if (this.properties.header_size > 32) {
this.properties.smdh_offset = dataView.getUint32(offset, true);
this.properties.smdh_size = dataView.getUint32(offset + 4, true);
this.properties.romfs_offset = dataView.getUint32(offset + 8, true);
offset += 12;
}
const relocStart = offset;
this.properties.code_abs_relocs = dataView.getUint32(relocStart, true);
this.properties.code_rel_relocs = dataView.getUint32(relocStart + 4, true);
this.properties.rodata_abs_relocs = dataView.getUint32(relocStart + 8, true);
this.properties.rodata_rel_relocs = dataView.getUint32(relocStart + 12, true);
this.properties.data_abs_relocs = dataView.getUint32(relocStart + 16, true);
this.properties.data_rel_relocs = dataView.getUint32(relocStart + 20, true);
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key.replace(/_/g, ' ').toUpperCase()}: ${typeof value === 'string' ? value : '0x' + value.toString(16)}`);
}
}
write() {
const headerSize = this.properties.header_size;
const bufferSize = headerSize + 24; // Header + relocs
const newBuffer = new ArrayBuffer(bufferSize);
const dataView = new DataView(newBuffer);
const magicBytes = this.properties.magic.split('').map(c => c.charCodeAt(0));
magicBytes.forEach((byte, i) => dataView.setUint8(i, byte));
dataView.setUint16(4, this.properties.header_size, true);
dataView.setUint16(6, this.properties.reloc_header_size, true);
dataView.setUint32(8, this.properties.format_version, true);
dataView.setUint32(12, this.properties.flags, true);
dataView.setUint32(16, this.properties.code_segment_size, true);
dataView.setUint32(20, this.properties.rodata_segment_size, true);
dataView.setUint32(24, this.properties.data_segment_size, true);
dataView.setUint32(28, this.properties.bss_segment_size, true);
let offset = 32;
if (headerSize > 32) {
dataView.setUint32(offset, this.properties.smdh_offset, true);
dataView.setUint32(offset + 4, this.properties.smdh_size, true);
dataView.setUint32(offset + 8, this.properties.romfs_offset, true);
offset += 12;
}
dataView.setUint32(offset, this.properties.code_abs_relocs, true);
dataView.setUint32(offset + 4, this.properties.code_rel_relocs, true);
dataView.setUint32(offset + 8, this.properties.rodata_abs_relocs, true);
dataView.setUint32(offset + 12, this.properties.rodata_rel_relocs, true);
dataView.setUint32(offset + 16, this.properties.data_abs_relocs, true);
dataView.setUint32(offset + 20, this.properties.data_rel_relocs, true);
return newBuffer; // Return ArrayBuffer for saving (e.g., via Blob)
}
}
// Example Node.js usage:
// const fs = require('fs');
// const buffer = fs.readFileSync('example.3dsx').buffer;
// const handler = new ThreeDSXHandler(buffer);
// handler.read();
// handler.printProperties();
// const newBuffer = handler.write();
// fs.writeFileSync('output.3dsx', new Uint8Array(newBuffer));
7. C Implementation for .3DSX File Handling
Since C does not support classes natively, the following implementation uses a struct for properties and functions for decoding, reading, writing, and printing. It employs fread and fwrite for file operations, assuming little-endian. The read_3dsx function parses the file. The print_properties function outputs to stdout. The write_3dsx function writes the header to a new file.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char magic[5]; // Null-terminated
uint16_t header_size;
uint16_t reloc_header_size;
uint32_t format_version;
uint32_t flags;
uint32_t code_segment_size;
uint32_t rodata_segment_size;
uint32_t data_segment_size;
uint32_t bss_segment_size;
uint32_t smdh_offset;
uint32_t smdh_size;
uint32_t romfs_offset;
uint32_t code_abs_relocs;
uint32_t code_rel_relocs;
uint32_t rodata_abs_relocs;
uint32_t rodata_rel_relocs;
uint32_t data_abs_relocs;
uint32_t data_rel_relocs;
int has_extended; // Flag for extended header
} ThreeDSXProperties;
void read_3dsx(const char* filepath, ThreeDSXProperties* props) {
FILE* f = fopen(filepath, "rb");
if (!f) {
perror("Failed to open file");
return;
}
// Read header
char magic[4];
fread(magic, 1, 4, f);
strncpy(props->magic, magic, 4);
props->magic[4] = '\0';
fread(&props->header_size, sizeof(uint16_t), 1, f);
fread(&props->reloc_header_size, sizeof(uint16_t), 1, f);
fread(&props->format_version, sizeof(uint32_t), 1, f);
fread(&props->flags, sizeof(uint32_t), 1, f);
fread(&props->code_segment_size, sizeof(uint32_t), 1, f);
fread(&props->rodata_segment_size, sizeof(uint32_t), 1, f);
fread(&props->data_segment_size, sizeof(uint32_t), 1, f);
fread(&props->bss_segment_size, sizeof(uint32_t), 1, f);
props->has_extended = (props->header_size > 32);
if (props->has_extended) {
fread(&props->smdh_offset, sizeof(uint32_t), 1, f);
fread(&props->smdh_size, sizeof(uint32_t), 1, f);
fread(&props->romfs_offset, sizeof(uint32_t), 1, f);
}
// Read relocation headers
fread(&props->code_abs_relocs, sizeof(uint32_t), 1, f);
fread(&props->code_rel_relocs, sizeof(uint32_t), 1, f);
fread(&props->rodata_abs_relocs, sizeof(uint32_t), 1, f);
fread(&props->rodata_rel_relocs, sizeof(uint32_t), 1, f);
fread(&props->data_abs_relocs, sizeof(uint32_t), 1, f);
fread(&props->data_rel_relocs, sizeof(uint32_t), 1, f);
fclose(f);
}
void print_properties(const ThreeDSXProperties* props) {
printf("Magic Identifier: %s\n", props->magic);
printf("Header Size: 0x%X\n", props->header_size);
printf("Relocation Header Size: 0x%X\n", props->reloc_header_size);
printf("Format Version: %u\n", props->format_version);
printf("Flags: 0x%X\n", props->flags);
printf("Code Segment Size: 0x%X\n", props->code_segment_size);
printf("Read-Only Data Segment Size: 0x%X\n", props->rodata_segment_size);
printf("Data Segment Size: 0x%X\n", props->data_segment_size);
printf("BSS Segment Size: 0x%X\n", props->bss_segment_size);
if (props->has_extended) {
printf("SMDH Offset: 0x%X\n", props->smdh_offset);
printf("SMDH Size: 0x%X\n", props->smdh_size);
printf("RomFS Offset: 0x%X\n", props->romfs_offset);
}
printf("Code Absolute Relocations: %u\n", props->code_abs_relocs);
printf("Code Relative Relocations: %u\n", props->code_rel_relocs);
printf("Rodata Absolute Relocations: %u\n", props->rodata_abs_relocs);
printf("Rodata Relative Relocations: %u\n", props->rodata_rel_relocs);
printf("Data Absolute Relocations: %u\n", props->data_abs_relocs);
printf("Data Relative Relocations: %u\n", props->data_rel_relocs);
}
void write_3dsx(const char* output_path, const ThreeDSXProperties* props) {
FILE* f = fopen(output_path, "wb");
if (!f) {
perror("Failed to open output file");
return;
}
fwrite(props->magic, 1, 4, f);
fwrite(&props->header_size, sizeof(uint16_t), 1, f);
fwrite(&props->reloc_header_size, sizeof(uint16_t), 1, f);
fwrite(&props->format_version, sizeof(uint32_t), 1, f);
fwrite(&props->flags, sizeof(uint32_t), 1, f);
fwrite(&props->code_segment_size, sizeof(uint32_t), 1, f);
fwrite(&props->rodata_segment_size, sizeof(uint32_t), 1, f);
fwrite(&props->data_segment_size, sizeof(uint32_t), 1, f);
fwrite(&props->bss_segment_size, sizeof(uint32_t), 1, f);
if (props->has_extended) {
fwrite(&props->smdh_offset, sizeof(uint32_t), 1, f);
fwrite(&props->smdh_size, sizeof(uint32_t), 1, f);
fwrite(&props->romfs_offset, sizeof(uint32_t), 1, f);
}
fwrite(&props->code_abs_relocs, sizeof(uint32_t), 1, f);
fwrite(&props->code_rel_relocs, sizeof(uint32_t), 1, f);
fwrite(&props->rodata_abs_relocs, sizeof(uint32_t), 1, f);
fwrite(&props->rodata_rel_relocs, sizeof(uint32_t), 1, f);
fwrite(&props->data_abs_relocs, sizeof(uint32_t), 1, f);
fwrite(&props->data_rel_relocs, sizeof(uint32_t), 1, f);
fclose(f);
// Additional writing of segments/reloc entries can be added as needed
}
// Example usage:
// int main() {
// ThreeDSXProperties props;
// read_3dsx("example.3dsx", &props);
// print_properties(&props);
// write_3dsx("output.3dsx", &props);
// return 0;
// }