Task 005: .3GX File Format

Task 005: .3GX File Format

Task Response: .3GX File Format Specifications

The .3GX file format is a Luma3DS 3GX (3DS Game eXtension) plug-in used primarily with Nintendo 3DS consoles running Boot9Strap custom firmware and the Luma3DS program. It is designed to extend the functionality of Nintendo 3DS games by allowing runtime loading and execution of C/C++/assembly code, such as cheats or game mods. The format evolved from the NTR .plg format and is developed by Nanquitas. Below, I address each part of the task based on available information and critical analysis, as the full specification is not publicly documented.


1. Properties of the .3GX File Format Intrinsic to Its File System

The .3GX file format is a binary format tailored for the Nintendo 3DS environment, and its intrinsic properties are tied to its functionality within the Luma3DS ecosystem. Based on available information, the following properties are intrinsic to the .3GX file format:

  • File Extension: .3gx
  • MIME Type: application/octet-stream (binary data format).
  • Primary Function: Extends Nintendo 3DS game functionality by loading custom C/C++/assembly code at runtime.
  • File Content: Contains executable plug-in code (C/C++/assembly) that integrates with a specific game or application, often for cheats, mods, or trainers.
  • Storage Location: Typically stored on an SD card in specific directories:
  • sd:/luma/plugins/<TITLEID>/<filename>.3gx (game-specific plug-in, higher priority).
  • sd:/luma/plugins/default.3gx (default plug-in for all games, lower priority).
  • Binary Structure: Likely includes headers, metadata (e.g., target game Title ID), and executable code segments, though exact structure details are not publicly specified.
  • Platform Dependency: Designed exclusively for Nintendo 3DS with Luma3DS and Boot9Strap installed; not intended for general-purpose systems like Windows or Mac.
  • Runtime Execution: Loaded and executed by the Luma3DS plug-in loader, often with a user interface accessible via the SELECT button.
  • File Integrity: Must be uncorrupted and correctly formatted to be recognized by Luma3DS; often scanned for malware due to potential risks.
  • Customizability: Supports user-created plug-ins via an API (e.g., CTRPluginFramework), allowing developers to create mods or cheats.
  • Non-Standard Access: Not meant to be opened or edited on standard computers; requires specific handling within the 3DS environment.

Note: The exact binary structure (e.g., header format, offsets, or checksums) is not publicly documented in the provided sources or widely available, as the format is specific to the Luma3DS ecosystem and considered partially proprietary or de facto defined by Luma3DS behavior. For programming purposes, I will assume a simplified structure for demonstration, focusing on metadata (e.g., Title ID) and a payload (executable code), as these are implied by the format’s purpose.


2. Python Class for .3GX File Handling

Below is a Python class that attempts to open, read, write, and print properties of a .3GX file. Since the exact binary structure is not fully specified, this implementation assumes a basic format with a header containing a magic number, version, Title ID, and a payload. The class uses the struct module for binary parsing and handles file I/O.

import struct
import os

class ThreeGXFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.magic = b'3GX0'  # Hypothetical magic number
        self.version = 0
        self.title_id = ""
        self.payload_size = 0
        self.payload = b''

    def read_3gx(self):
        """Read and decode a .3GX file."""
        try:
            with open(self.filepath, 'rb') as f:
                # Assume header: 4-byte magic, 4-byte version, 8-byte Title ID, 4-byte payload size
                header = f.read(20)
                if len(header) < 20:
                    raise ValueError("File too small to be a valid .3GX file")
                
                self.magic = header[0:4]
                self.version = struct.unpack('<I', header[4:8])[0]
                self.title_id = header[8:16].decode('ascii', errors='ignore').strip('\x00')
                self.payload_size = struct.unpack('<I', header[16:20])[0]
                self.payload = f.read(self.payload_size)

                if self.magic != b'3GX0':
                    raise ValueError("Invalid .3GX file: incorrect magic number")
        except Exception as e:
            print(f"Error reading .3GX file: {e}")

    def write_3gx(self, output_filepath):
        """Write a .3GX file with current properties."""
        try:
            with open(output_filepath, 'wb') as f:
                f.write(self.magic)
                f.write(struct.pack('<I', self.version))
                f.write(self.title_id.encode('ascii').ljust(8, b'\x00'))
                f.write(struct.pack('<I', self.payload_size))
                f.write(self.payload)
            print(f"Successfully wrote .3GX file to {output_filepath}")
        except Exception as e:
            print(f"Error writing .3GX file: {e}")

    def print_properties(self):
        """Print all intrinsic properties of the .3GX file."""
        print("=== .3GX File Properties ===")
        print(f"File Extension: .3gx")
        print(f"MIME Type: application/octet-stream")
        print(f"Magic Number: {self.magic.decode('ascii', errors='ignore')}")
        print(f"Version: {self.version}")
        print(f"Title ID: {self.title_id}")
        print(f"Payload Size: {self.payload_size} bytes")
        print(f"Storage Location: {self.filepath}")
        print(f"Platform: Nintendo 3DS with Luma3DS and Boot9Strap")
        print(f"Purpose: Extend Nintendo 3DS game functionality")
        print(f"Customizability: Supports user-created plug-ins via API")
        print(f"Access: Loaded by Luma3DS plug-in loader, not opened on standard systems")

# Example usage
if __name__ == "__main__":
    # Example file (replace with actual .3GX file path)
    file_path = "example.3gx"
    
    # Create and read
    three_gx = ThreeGXFile(file_path)
    three_gx.read_3gx()
    three_gx.print_properties()
    
    # Write to a new file
    three_gx.write_3gx("output.3gx")

Notes:

  • The class assumes a hypothetical header structure due to the lack of a public specification. In a real implementation, you would need the exact format, possibly reverse-engineered from Luma3DS source code or Nanquitas’ documentation.
  • The read_3gx method checks for a magic number and reads metadata and payload.
  • The write_3gx method creates a new .3GX file with the same structure.
  • Error handling ensures robustness for invalid or corrupted files.
  • The print_properties method outputs all intrinsic properties listed in part 1.

3. Java Class for .3GX File Handling

Below is a Java class with similar functionality, using DataInputStream and DataOutputStream for binary file handling.

import java.io.*;

public class ThreeGXFile {
    private String filepath;
    private String magic = "3GX0";
    private int version;
    private String titleId;
    private int payloadSize;
    private byte[] payload;

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

    public void read3GX() throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(filepath))) {
            // Read header: 4-byte magic, 4-byte version, 8-byte Title ID, 4-byte payload size
            byte[] header = new byte[20];
            int bytesRead = dis.read(header);
            if (bytesRead < 20) {
                throw new IOException("File too small to be a valid .3GX file");
            }

            this.magic = new String(header, 0, 4, "ASCII");
            this.version = (header[4] & 0xFF) | (header[5] & 0xFF) << 8 | 
                          (header[6] & 0xFF) << 16 | (header[7] & 0xFF) << 24;
            this.titleId = new String(header, 8, 8, "ASCII").trim();
            this.payloadSize = (header[16] & 0xFF) | (header[17] & 0xFF) << 8 | 
                              (header[18] & 0xFF) << 16 | (header[19] & 0xFF) << 24;
            this.payload = new byte[payloadSize];
            dis.readFully(payload);

            if (!this.magic.equals("3GX0")) {
                throw new IOException("Invalid .3GX file: incorrect magic number");
            }
        } catch (Exception e) {
            System.err.println("Error reading .3GX file: " + e.getMessage());
        }
    }

    public void write3GX(String outputFilepath) {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(outputFilepath))) {
            dos.writeBytes(magic);
            dos.writeInt(Integer.reverseBytes(version)); // Little-endian
            dos.writeBytes(String.format("%-8s", titleId).getBytes("ASCII"));
            dos.writeInt(Integer.reverseBytes(payloadSize)); // Little-endian
            dos.write(payload);
            System.out.println("Successfully wrote .3GX file to " + outputFilepath);
        } catch (Exception e) {
            System.err.println("Error writing .3GX file: " + e.getMessage());
        }
    }

    public void printProperties() {
        System.out.println("=== .3GX File Properties ===");
        System.out.println("File Extension: .3gx");
        System.out.println("MIME Type: application/octet-stream");
        System.out.println("Magic Number: " + magic);
        System.out.println("Version: " + version);
        System.out.println("Title ID: " + titleId);
        System.out.println("Payload Size: " + payloadSize + " bytes");
        System.out.println("Storage Location: " + filepath);
        System.out.println("Platform: Nintendo 3DS with Luma3DS and Boot9Strap");
        System.out.println("Purpose: Extend Nintendo 3DS game functionality");
        System.out.println("Customizability: Supports user-created plug-ins via API");
        System.out.println("Access: Loaded by Luma3DS plug-in loader, not opened on standard systems");
    }

    public static void main(String[] args) {
        // Example usage
        String filePath = "example.3gx";
        ThreeGXFile threeGX = new ThreeGXFile(filePath);
        try {
            threeGX.read3GX();
            threeGX.printProperties();
            threeGX.write3GX("output.3gx");
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Notes:

  • The Java class mirrors the Python implementation, assuming the same hypothetical structure.
  • It uses DataInputStream for reading and DataOutputStream for writing, handling little-endian byte order.
  • Error handling ensures the file is valid and complete.
  • The printProperties method lists all intrinsic properties.

4. JavaScript Class for .3GX File Handling

Below is a JavaScript class using Node.js’s fs module for file I/O, as .3GX files are not typically handled in browser environments.

const fs = require('fs').promises;
const Buffer = require('buffer').Buffer;

class ThreeGXFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.magic = '3GX0';
        this.version = 0;
        this.titleId = '';
        this.payloadSize = 0;
        this.payload = Buffer.alloc(0);
    }

    async read3GX() {
        try {
            const data = await fs.readFile(this.filepath);
            if (data.length < 20) {
                throw new Error('File too small to be a valid .3GX file');
            }

            this.magic = data.toString('ascii', 0, 4);
            this.version = data.readUInt32LE(4);
            this.titleId = data.toString('ascii', 8, 16).trim();
            this.payloadSize = data.readUInt32LE(16);
            this.payload = data.slice(20, 20 + this.payloadSize);

            if (this.magic !== '3GX0') {
                throw new Error('Invalid .3GX file: incorrect magic number');
            }
        } catch (error) {
            console.error(`Error reading .3GX file: ${error.message}`);
        }
    }

    async write3GX(outputFilepath) {
        try {
            const buffer = Buffer.alloc(20 + this.payloadSize);
            buffer.write(this.magic, 0, 4, 'ascii');
            buffer.writeUInt32LE(this.version, 4);
            buffer.write(this.titleId.padEnd(8, '\0'), 8, 8, 'ascii');
            buffer.writeUInt32LE(this.payloadSize, 16);
            this.payload.copy(buffer, 20);
            await fs.writeFile(outputFilepath, buffer);
            console.log(`Successfully wrote .3GX file to ${outputFilepath}`);
        } catch (error) {
            console.error(`Error writing .3GX file: ${error.message}`);
        }
    }

    printProperties() {
        console.log('=== .3GX File Properties ===');
        console.log('File Extension: .3gx');
        console.log('MIME Type: application/octet-stream');
        console.log(`Magic Number: ${this.magic}`);
        console.log(`Version: ${this.version}`);
        console.log(`Title ID: ${this.titleId}`);
        console.log(`Payload Size: ${this.payloadSize} bytes`);
        console.log(`Storage Location: ${this.filepath}`);
        console.log('Platform: Nintendo 3DS with Luma3DS and Boot9Strap');
        console.log('Purpose: Extend Nintendo 3DS game functionality');
        console.log('Customizability: Supports user-created plug-ins via API');
        console.log('Access: Loaded by Luma3DS plug-in loader, not opened on standard systems');
    }
}

// Example usage
(async () => {
    const filePath = 'example.3gx';
    const threeGX = new ThreeGXFile(filePath);
    await threeGX.read3GX();
    threeGX.printProperties();
    await threeGX.write3GX('output.3gx');
})();

Notes:

  • The class uses Node.js’s fs.promises for asynchronous file operations.
  • It assumes the same header structure as the Python and Java implementations.
  • The Buffer class handles binary data, with little-endian parsing for integers.
  • The printProperties method outputs all properties.

5. C Class for .3GX File Handling

In C, there is no direct equivalent to a class, so we use a struct with associated functions to achieve similar functionality. Below is the implementation.

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

#define MAGIC "3GX0"
#define HEADER_SIZE 20

typedef struct {
    char* filepath;
    char magic[4];
    unsigned int version;
    char title_id[9]; // 8 chars + null terminator
    unsigned int payload_size;
    unsigned char* payload;
} ThreeGXFile;

ThreeGXFile* create_three_gx_file(const char* filepath) {
    ThreeGXFile* tgx = (ThreeGXFile*)malloc(sizeof(ThreeGXFile));
    if (!tgx) {
        fprintf(stderr, "Error: Memory allocation failed\n");
        return NULL;
    }
    tgx->filepath = strdup(filepath);
    strncpy(tgx->magic, MAGIC, 4);
    tgx->version = 0;
    memset(tgx->title_id, 0, 9);
    tgx->payload_size = 0;
    tgx->payload = NULL;
    return tgx;
}

void destroy_three_gx_file(ThreeGXFile* tgx) {
    if (tgx) {
        free(tgx->filepath);
        free(tgx->payload);
        free(tgx);
    }
}

int read_3gx(ThreeGXFile* tgx) {
    FILE* file = fopen(tgx->filepath, "rb");
    if (!file) {
        fprintf(stderr, "Error: Could not open file %s\n", tgx->filepath);
        return -1;
    }

    unsigned char header[HEADER_SIZE];
    if (fread(header, 1, HEADER_SIZE, file) != HEADER_SIZE) {
        fprintf(stderr, "Error: File too small to be a valid .3GX file\n");
        fclose(file);
        return -1;
    }

    memcpy(tgx->magic, header, 4);
    if (strncmp(tgx->magic, MAGIC, 4) != 0) {
        fprintf(stderr, "Error: Invalid .3GX file: incorrect magic number\n");
        fclose(file);
        return -1;
    }

    tgx->version = header[4] | (header[5] << 8) | (header[6] << 16) | (header[7] << 24);
    memcpy(tgx->title_id, &header[8], 8);
    tgx->title_id[8] = '\0';
    tgx->payload_size = header[16] | (header[17] << 8) | (header[18] << 16) | (header[19] << 24);

    tgx->payload = (unsigned char*)malloc(tgx->payload_size);
    if (!tgx->payload) {
        fprintf(stderr, "Error: Memory allocation for payload failed\n");
        fclose(file);
        return -1;
    }
    if (fread(tgx->payload, 1, tgx->payload_size, file) != tgx->payload_size) {
        fprintf(stderr, "Error: Failed to read payload\n");
        free(tgx->payload);
        tgx->payload = NULL;
        fclose(file);
        return -1;
    }

    fclose(file);
    return 0;
}

int write_3gx(ThreeGXFile* tgx, const char* output_filepath) {
    FILE* file = fopen(output_filepath, "wb");
    if (!file) {
        fprintf(stderr, "Error: Could not open output file %s\n", output_filepath);
        return -1;
    }

    fwrite(tgx->magic, 1, 4, file);
    fputc(tgx->version & 0xFF, file);
    fputc((tgx->version >> 8) & 0xFF, file);
    fputc((tgx->version >> 16) & 0xFF, file);
    fputc((tgx->version >> 24) & 0xFF, file);
    fwrite(tgx->title_id, 1, 8, file);
    fputc(tgx->payload_size & 0xFF, file);
    fputc((tgx->payload_size >> 8) & 0xFF, file);
    fputc((tgx->payload_size >> 16) & 0xFF, file);
    fputc((tgx->payload_size >> 24) & 0xFF, file);
    fwrite(tgx->payload, 1, tgx->payload_size, file);

    fclose(file);
    printf("Successfully wrote .3GX file to %s\n", output_filepath);
    return 0;
}

void print_properties(ThreeGXFile* tgx) {
    printf("=== .3GX File Properties ===\n");
    printf("File Extension: .3gx\n");
    printf("MIME Type: application/octet-stream\n");
    printf("Magic Number: %.4s\n", tgx->magic);
    printf("Version: %u\n", tgx->version);
    printf("Title ID: %s\n", tgx->title_id);
    printf("Payload Size: %u bytes\n", tgx->payload_size);
    printf("Storage Location: %s\n", tgx->filepath);
    printf("Platform: Nintendo 3DS with Luma3DS and Boot9Strap\n");
    printf("Purpose: Extend Nintendo 3DS game functionality\n");
    printf("Customizability: Supports user-created plug-ins via API\n");
    printf("Access: Loaded by Luma3DS plug-in loader, not opened on standard systems\n");
}

int main() {
    const char* file_path = "example.3gx";
    ThreeGXFile* tgx = create_three_gx_file(file_path);
    if (!tgx) return -1;

    if (read_3gx(tgx) == 0) {
        print_properties(tgx);
        write_3gx(tgx, "output.3gx");
    }

    destroy_three_gx_file(tgx);
    return 0;
}

Notes:

  • The C implementation uses a struct to represent the .3GX file and functions for operations.
  • It assumes the same header structure, with manual byte manipulation for little-endian encoding.
  • Memory management is handled explicitly with malloc and free.
  • Error handling ensures robust file I/O and validation.

Caveats and Limitations

  • Lack of Full Specification: The .3GX file format lacks a publicly available detailed specification, so the binary structure used here (magic number, version, Title ID, payload) is a reasonable assumption based on typical binary file formats and the format’s purpose. For real-world use, you would need to analyze Luma3DS source code or Nanquitas’ documentation (e.g., CTRPluginFramework) to determine the exact structure.
  • Platform Limitation: .3GX files are not designed to be opened or edited on standard computers, so the classes focus on parsing and printing properties rather than executing the plug-in, which requires a Nintendo 3DS environment.
  • Hypothetical Structure: The assumed header structure may not match the actual format. Developers should verify with Luma3DS or related homebrew communities.
  • Testing: The code assumes the existence of a valid .3GX file (example.3gx). You would need an actual .3GX file to test these implementations.

If you have access to a .3GX file or additional specifications, I can refine the code further. Let me know if you need assistance testing or extending these classes!

The .3GX file format is a plugin extension used with Luma3DS on the Nintendo 3DS, evolved from the older NTR PLG format. It is used to load additional code into running games for features like cheats or mods. However, detailed technical specifications for the binary structure (such as headers, magic numbers, fields, offsets, or data layouts) are not publicly documented in online sources, wikis, or the Luma3DS repository documentation. The format is handled internally by Luma3DS's plugin loader, but source code parsing details are not readily accessible or described in public repositories.

  1. Based on available information, the following are known general properties of the .3GX file format (intrinsic to its usage and structure within the Nintendo 3DS ecosystem, rather than specific file system metadata like size or permissions, which are not unique to the format):
  • File extension: .3gx
  • MIME type: application/octet-stream (not formally defined, as it's a custom format)
  • Associated software: Luma3DS (built-in plugin loader) on Nintendo 3DS
  • Purpose: Runtime plugin for extending Nintendo 3DS game functionality (e.g., cheats, mods, extra code execution)
  • Origin: Evolved from NTR CFW's .plg format, introduced as an updated plugin system in Luma3DS forks and later merged into official Luma3DS
  • Location on SD card: Typically placed in /luma/plugins// where TitleID is the game's unique identifier
  • Compatibility: Requires Luma3DS plugin loader enabled via Rosalina menu; not compatible with standard Nintendo firmware without custom firmware
  • Content type: Binary code and data (C/C++/ASM) to be loaded into game memory at runtime
  • Limitations: Plugins are game-specific and may not work on all firmware versions or with all games

2-5. Without detailed file format specifications (e.g., header structure, byte layouts, or field definitions), it is not possible to write accurate classes in Python, Java, JavaScript, or C that can reliably open, decode, read, and write .3GX files. Attempting to do so without the spec would result in incorrect or incomplete implementations. If you can provide a sample .3GX file or the binary spec, I can assist further.