Task 036: .ART File Format

Task 036: .ART File Format

  1. The properties of the .ART file format intrinsic to its structure are as follows:
  • Signature: 2 bytes ('J' 'G')
  • Version: 1 byte (typically 0x03 or 0x04)
  • Unknown field 1: 1 byte (typically 0x0E)
  • Padding: 9 bytes (typically 0x00)
  • Width: 2 bytes (little-endian unsigned integer)
  • Height: 2 bytes (little-endian unsigned integer)
  • Compressed image data: Remainder of the file (proprietary Johnson-Grace compression algorithm)
  1. Two direct download links for .ART files:
  1. The following is an embedded HTML and JavaScript code snippet suitable for a Ghost blog post. It enables drag-and-drop functionality for .ART files and displays the extracted properties on the screen:
.ART File Property Dumper
Drag and drop a .ART file here
  1. The following is a Python class for handling .ART files:
import struct

class ArtFileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.signature = None
        self.version = None
        self.unknown1 = None
        self.padding = None
        self.width = None
        self.height = None
        self.compressed_data = None

    def open_and_decode(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        if len(data) < 17:
            raise ValueError("File too small for .ART header")
        self.signature = data[0:2].decode('ascii', errors='ignore')
        self.version = hex(data[2])
        self.unknown1 = hex(data[3])
        self.padding = ' '.join(hex(b) for b in data[4:13])
        self.width = struct.unpack('<H', data[13:15])[0]
        self.height = struct.unpack('<H', data[15:17])[0]
        self.compressed_data = data[17:]

    def print_properties(self):
        print(f"Signature: {self.signature}")
        print(f"Version: {self.version}")
        print(f"Unknown field 1: {self.unknown1}")
        print(f"Padding: {self.padding}")
        print(f"Width: {self.width}")
        print(f"Height: {self.height}")
        print(f"Compressed image data size: {len(self.compressed_data)} bytes")

    def write(self, new_filename):
        if self.signature is None:
            raise ValueError("No data decoded yet")
        header = (
            self.signature.encode('ascii') +
            bytes([int(self.version, 0)]) +
            bytes([int(self.unknown1, 0)]) +
            bytes([int(h, 0) for h in self.padding.split()]) +
            struct.pack('<H', self.width) +
            struct.pack('<H', self.height)
        )
        with open(new_filename, 'wb') as f:
            f.write(header + self.compressed_data)

# Example usage:
# handler = ArtFileHandler('example.ART')
# handler.open_and_decode()
# handler.print_properties()
# handler.write('new.ART')
  1. The following is a Java class for handling .ART files:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ArtFileHandler {
    private String filename;
    private String signature;
    private String version;
    private String unknown1;
    private String padding;
    private int width;
    private int height;
    private byte[] compressedData;

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

    public void openAndDecode() throws IOException {
        byte[] data = readFileToByteArray(filename);
        if (data.length < 17) {
            throw new IOException("File too small for .ART header");
        }
        signature = new String(data, 0, 2);
        version = String.format("0x%02X", data[2]);
        unknown1 = String.format("0x%02X", data[3]);
        StringBuilder padBuilder = new StringBuilder();
        for (int i = 4; i < 13; i++) {
            padBuilder.append(String.format("%02X ", data[i]));
        }
        padding = padBuilder.toString().trim();
        ByteBuffer buffer = ByteBuffer.wrap(data, 13, 4).order(ByteOrder.LITTLE_ENDIAN);
        width = buffer.getShort();
        height = buffer.getShort();
        compressedData = new byte[data.length - 17];
        System.arraycopy(data, 17, compressedData, 0, compressedData.length);
    }

    public void printProperties() {
        System.out.println("Signature: " + signature);
        System.out.println("Version: " + version);
        System.out.println("Unknown field 1: " + unknown1);
        System.out.println("Padding: " + padding);
        System.out.println("Width: " + width);
        System.out.println("Height: " + height);
        System.out.println("Compressed image data size: " + compressedData.length + " bytes");
    }

    public void write(String newFilename) throws IOException {
        if (signature == null) {
            throw new IOException("No data decoded yet");
        }
        ByteArrayOutputStream header = new ByteArrayOutputStream();
        header.write(signature.getBytes());
        header.write((byte) Integer.parseInt(version.substring(2), 16));
        header.write((byte) Integer.parseInt(unknown1.substring(2), 16));
        String[] padBytes = padding.split(" ");
        for (String pad : padBytes) {
            header.write((byte) Integer.parseInt(pad, 16));
        }
        ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
        buffer.putShort((short) width);
        buffer.putShort((short) height);
        header.write(buffer.array());
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            fos.write(header.toByteArray());
            fos.write(compressedData);
        }
    }

    private byte[] readFileToByteArray(String filePath) throws IOException {
        try (FileInputStream fis = new FileInputStream(filePath);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     ArtFileHandler handler = new ArtFileHandler("example.ART");
    //     handler.openAndDecode();
    //     handler.printProperties();
    //     handler.write("new.ART");
    // }
}
  1. The following is a JavaScript class for handling .ART files (node.js compatible, using fs module):
const fs = require('fs');

class ArtFileHandler {
    constructor(filename) {
        this.filename = filename;
        this.signature = null;
        this.version = null;
        this.unknown1 = null;
        this.padding = null;
        this.width = null;
        this.height = null;
        this.compressedData = null;
    }

    openAndDecode() {
        const data = fs.readFileSync(this.filename);
        if (data.length < 17) {
            throw new Error('File too small for .ART header');
        }
        this.signature = data.toString('ascii', 0, 2);
        this.version = '0x' + data[2].toString(16).padStart(2, '0');
        this.unknown1 = '0x' + data[3].toString(16).padStart(2, '0');
        this.padding = Array.from(data.slice(4, 13)).map(b => b.toString(16).padStart(2, '0')).join(' ');
        const view = new DataView(data.buffer);
        this.width = view.getUint16(13, true);
        this.height = view.getUint16(15, true);
        this.compressedData = data.slice(17);
    }

    printProperties() {
        console.log(`Signature: ${this.signature}`);
        console.log(`Version: ${this.version}`);
        console.log(`Unknown field 1: ${this.unknown1}`);
        console.log(`Padding: ${this.padding}`);
        console.log(`Width: ${this.width}`);
        console.log(`Height: ${this.height}`);
        console.log(`Compressed image data size: ${this.compressedData.length} bytes`);
    }

    write(newFilename) {
        if (this.signature === null) {
            throw new Error('No data decoded yet');
        }
        const header = Buffer.alloc(17);
        header.write(this.signature, 0, 2, 'ascii');
        header[2] = parseInt(this.version, 16);
        header[3] = parseInt(this.unknown1, 16);
        this.padding.split(' ').forEach((hex, i) => {
            header[4 + i] = parseInt(hex, 16);
        });
        const view = new DataView(header.buffer);
        view.setUint16(13, this.width, true);
        view.setUint16(15, this.height, true);
        const fullBuffer = Buffer.concat([header, this.compressedData]);
        fs.writeFileSync(newFilename, fullBuffer);
    }
}

// Example usage:
// const handler = new ArtFileHandler('example.ART');
// handler.openAndDecode();
// handler.printProperties();
// handler.write('new.ART');
  1. The following is a C++ class for handling .ART files:
#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include <cstring>

class ArtFileHandler {
private:
    std::string filename;
    char signature[3]; // Null-terminated
    unsigned char version;
    unsigned char unknown1;
    unsigned char padding[9];
    unsigned short width;
    unsigned short height;
    std::vector<unsigned char> compressedData;

public:
    ArtFileHandler(const std::string& fn) : filename(fn), version(0), unknown1(0), width(0), height(0) {
        std::memset(signature, 0, sizeof(signature));
        std::memset(padding, 0, sizeof(padding));
    }

    void openAndDecode() {
        std::ifstream file(filename, std::ios::binary | std::ios::ate);
        if (!file.is_open()) {
            throw std::runtime_error("Unable to open file");
        }
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);
        if (size < 17) {
            throw std::runtime_error("File too small for .ART header");
        }
        std::vector<unsigned char> data(size);
        file.read(reinterpret_cast<char*>(data.data()), size);
        signature[0] = data[0];
        signature[1] = data[1];
        signature[2] = '\0';
        version = data[2];
        unknown1 = data[3];
        std::memcpy(padding, &data[4], 9);
        width = static_cast<unsigned short>(data[13]) | (static_cast<unsigned short>(data[14]) << 8);
        height = static_cast<unsigned short>(data[15]) | (static_cast<unsigned short>(data[16]) << 8);
        compressedData.assign(data.begin() + 17, data.end());
    }

    void printProperties() {
        std::cout << "Signature: " << signature << std::endl;
        std::cout << "Version: 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(version) << std::dec << std::endl;
        std::cout << "Unknown field 1: 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(unknown1) << std::dec << std::endl;
        std::cout << "Padding: ";
        for (int i = 0; i < 9; ++i) {
            std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(padding[i]) << " ";
        }
        std::cout << std::dec << std::endl;
        std::cout << "Width: " << width << std::endl;
        std::cout << "Height: " << height << std::endl;
        std::cout << "Compressed image data size: " << compressedData.size() << " bytes" << std::endl;
    }

    void write(const std::string& newFilename) {
        if (std::strlen(signature) == 0) {
            throw std::runtime_error("No data decoded yet");
        }
        std::ofstream file(newFilename, std::ios::binary);
        if (!file.is_open()) {
            throw std::runtime_error("Unable to write file");
        }
        file.write(signature, 2);
        file.write(reinterpret_cast<const char*>(&version), 1);
        file.write(reinterpret_cast<const char*>(&unknown1), 1);
        file.write(reinterpret_cast<const char*>(padding), 9);
        unsigned char widthBytes[2] = { static_cast<unsigned char>(width & 0xFF), static_cast<unsigned char>(width >> 8) };
        file.write(reinterpret_cast<const char*>(widthBytes), 2);
        unsigned char heightBytes[2] = { static_cast<unsigned char>(height & 0xFF), static_cast<unsigned char>(height >> 8) };
        file.write(reinterpret_cast<const char*>(heightBytes), 2);
        file.write(reinterpret_cast<const char*>(compressedData.data()), compressedData.size());
    }
};

// Example usage:
// int main() {
//     try {
//         ArtFileHandler handler("example.ART");
//         handler.openAndDecode();
//         handler.printProperties();
//         handler.write("new.ART");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }