Task 272: .GSF File Format
Task 272: .GSF File Format
1. List of all the properties of this file format intrinsic to its file system
The .GSF (Game Boy Advance Sound Format) is a sub-format of the Portable Sound Format (PSF) family, designed for emulated audio from Game Boy Advance games. It encapsulates compressed ARM assembly code and data that, when emulated, reproduces the game's audio output. The file structure is binary and consists of a fixed header followed by variable sections. All multi-byte values are little-endian. The intrinsic properties (core structural elements defining the file system) are as follows:
- Signature: 3 bytes, ASCII "PSF" (fixed identifier for all PSF variants).
- Version: 1 byte, 0x22 (hex 22, decimal 34) for GSF (distinguishes it from other PSF variants like PSF1=0x01 or PSF2=0x02).
- Reserved size (R): 4 bytes, unsigned 32-bit integer (length of the optional reserved area in bytes; typically 0 for GSF, but can hold system-specific metadata like GBA ROM headers if present).
- Compressed program size (N): 4 bytes, unsigned 32-bit integer (length of the zlib-compressed program data in bytes; the core audio code and data).
- Compressed program CRC-32: 4 bytes, unsigned 32-bit integer (checksum of the compressed program data for integrity verification).
- Reserved area: Variable length (R bytes), binary data (optional; often empty in GSF, but may include GBA cartridge header if R > 0; treated as opaque binary).
- Compressed program: Variable length (N bytes), zlib-compressed binary data (the executable ARM code for GBA audio emulation; decompression yields the raw program, typically starting with GBA ROM header at offset 0).
- Tag signature (optional): 5 bytes, ASCII "[TAG]" (marks the start of metadata section if present; absence indicates end of file after compressed program).
- Tag data (optional): Variable length (remaining bytes after tag signature), uncompressed ASCII text (human-readable metadata like title, game, artist, length in seconds, etc., in key-value format; truncated at 50KB if longer).
These properties define the self-contained, emulatable nature of the format, with no further sub-structure beyond the decompressed program (which is GBA-specific but not part of the container spec).
2. Two direct download links for files of format .GSF
- https://www.sendspace.com/file/kxluyo (ZIP archive containing multiple .GSF files from a large GBA music collection).
- https://archive.org/download/vgmrips-all-of-them/VGMrips All Of Them 2024 05 18.7z (7z archive containing a comprehensive VGM collection, including numerous .GSF files from GBA games).
3. Ghost blog embedded html javascript
Paste the following code into a Ghost blog post as an HTML card (use the "Code" card type for the script section). It creates a drag-and-drop zone that parses a dropped .GSF file and dumps the properties to a scrollable div below. Uses FileReader and DataView for parsing; zlib decompression is not performed (properties are header-only).
Drag and drop a .GSF file here to parse its properties.
4. Python class
import struct
import zlib # Not used for dump, but available for decompression if needed
class GSFReader:
def __init__(self, filename):
self.filename = filename
self.data = None
self.signature = None
self.version = None
self.reserved_size = None
self.compressed_size = None
self.crc32 = None
self.reserved_data = None
self.compressed_program = None
self.tag_signature = None
self.tag_data = None
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
offset = 0
self.signature = self.data[offset:offset+3].decode('ascii')
offset += 3
self.version = struct.unpack('<B', self.data[offset:offset+1])[0]
offset += 1
self.reserved_size, = struct.unpack('<I', self.data[offset:offset+4])
offset += 4
self.compressed_size, = struct.unpack('<I', self.data[offset:offset+4])
offset += 4
self.crc32, = struct.unpack('<I', self.data[offset:offset+4])
offset += 4
self.reserved_data = self.data[offset:offset+self.reserved_size]
offset += self.reserved_size
self.compressed_program = self.data[offset:offset+self.compressed_size]
offset += self.compressed_size
if offset < len(self.data):
self.tag_signature = self.data[offset:offset+5].decode('ascii')
offset += 5
self.tag_data = self.data[offset:].decode('ascii', errors='ignore')
print_properties(self)
def write(self, output_filename):
with open(output_filename, 'wb') as f:
f.write(self.data)
print(f"Wrote to {output_filename}")
def print_properties(gsf):
print(f"Signature: {gsf.signature}")
print(f"Version: 0x{gsf.version:02x} ({'GSF' if gsf.version == 0x22 else 'Unknown'})")
print(f"Reserved size: {gsf.reserved_size} bytes")
print(f"Compressed program size: {gsf.compressed_size} bytes")
print(f"Compressed program CRC-32: 0x{gsf.crc32:08x}")
print(f"Reserved area: {len(gsf.reserved_data)} bytes (binary)")
print(f"Compressed program: {len(gsf.compressed_program)} bytes (zlib)")
if gsf.tag_signature:
print(f"Tag signature: {gsf.tag_signature}")
print(f"Tag data:\n{gsf.tag_data}")
else:
print("No tags found.")
# Example usage
# reader = GSFReader('example.gsf')
# reader.read()
# reader.write('output.gsf')
5. Java class
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
public class GSFReader {
private String filename;
private byte[] data;
private String signature;
private int version;
private int reservedSize;
private int compressedSize;
private int crc32;
private byte[] reservedData;
private byte[] compressedProgram;
private String tagSignature;
private String tagData;
public GSFReader(String filename) {
this.filename = filename;
}
public void read() throws IOException {
data = Files.readAllBytes(Paths.get(filename));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
// Signature
byte[] sigBytes = new byte[3];
buffer.get(sigBytes, offset, 3);
signature = new String(sigBytes);
offset += 3;
// Version
version = buffer.get(offset) & 0xFF;
offset += 1;
// Reserved size
reservedSize = buffer.getInt(offset);
offset += 4;
// Compressed size
compressedSize = buffer.getInt(offset);
offset += 4;
// CRC32
crc32 = buffer.getInt(offset);
offset += 4;
// Reserved data
reservedData = new byte[reservedSize];
buffer.get(reservedData, offset, reservedSize);
offset += reservedSize;
// Compressed program
compressedProgram = new byte[compressedSize];
buffer.get(compressedProgram, offset, compressedSize);
offset += compressedSize;
// Tags
if (offset < data.length) {
byte[] tagBytes = new byte[5];
buffer.get(tagBytes, offset, 5);
tagSignature = new String(tagBytes);
offset += 5;
tagData = new String(data, offset, data.length - offset);
}
printProperties();
}
public void write(String outputFilename) throws IOException {
Files.write(Paths.get(outputFilename), data);
System.out.println("Wrote to " + outputFilename);
}
private void printProperties() {
System.out.println("Signature: " + signature);
System.out.println("Version: 0x" + String.format("%02x", version) + " (" + (version == 0x22 ? "GSF" : "Unknown") + ")");
System.out.println("Reserved size: " + reservedSize + " bytes");
System.out.println("Compressed program size: " + compressedSize + " bytes");
System.out.println("Compressed program CRC-32: 0x" + String.format("%08x", crc32));
System.out.println("Reserved area: " + reservedData.length + " bytes (binary)");
System.out.println("Compressed program: " + compressedProgram.length + " bytes (zlib)");
if (tagSignature != null) {
System.out.println("Tag signature: " + tagSignature);
System.out.println("Tag data:\n" + tagData);
} else {
System.out.println("No tags found.");
}
}
// Example usage
// GSFReader reader = new GSFReader("example.gsf");
// reader.read();
// reader.write("output.gsf");
}
6. Javascript class
class GSFReader {
constructor(fileOrBuffer) {
this.data = null;
this.signature = null;
this.version = null;
this.reservedSize = null;
this.compressedSize = null;
this.crc32 = null;
this.reservedData = null;
this.compressedProgram = null;
this.tagSignature = null;
this.tagData = null;
if (fileOrBuffer instanceof File) {
const reader = new FileReader();
reader.onload = (e) => {
this.data = new Uint8Array(e.target.result);
this.parse();
this.printProperties();
};
reader.readAsArrayBuffer(fileOrBuffer);
} else if (fileOrBuffer instanceof ArrayBuffer) {
this.data = new Uint8Array(fileOrBuffer);
this.parse();
}
}
parse() {
const view = new DataView(this.data.buffer);
let offset = 0;
// Signature
this.signature = String.fromCharCode(
view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2)
);
offset += 3;
// Version
this.version = view.getUint8(offset);
offset += 1;
// Reserved size
this.reservedSize = view.getUint32(offset, true);
offset += 4;
// Compressed size
this.compressedSize = view.getUint32(offset, true);
offset += 4;
// CRC32
this.crc32 = view.getUint32(offset, true);
offset += 4;
// Reserved data
this.reservedData = this.data.slice(offset, offset + this.reservedSize);
offset += this.reservedSize;
// Compressed program
this.compressedProgram = this.data.slice(offset, offset + this.compressedSize);
offset += this.compressedSize;
// Tags
if (offset < this.data.length) {
this.tagSignature = String.fromCharCode(
view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2),
view.getUint8(offset + 3), view.getUint8(offset + 4)
);
offset += 5;
const decoder = new TextDecoder();
this.tagData = decoder.decode(this.data.subarray(offset));
}
}
write(filename) {
const blob = new Blob([this.data]);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
console.log(`Wrote to ${filename} (browser download)`);
}
printProperties() {
console.log(`Signature: ${this.signature}`);
console.log(`Version: 0x${this.version.toString(16).padStart(2, '0')} (${this.version === 0x22 ? 'GSF' : 'Unknown'})`);
console.log(`Reserved size: ${this.reservedSize} bytes`);
console.log(`Compressed program size: ${this.compressedSize} bytes`);
console.log(`Compressed program CRC-32: 0x${this.crc32.toString(16).padStart(8, '0')}`);
console.log(`Reserved area: ${this.reservedData.length} bytes (binary)`);
console.log(`Compressed program: ${this.compressedProgram.length} bytes (zlib)`);
if (this.tagSignature) {
console.log(`Tag signature: ${this.tagSignature}`);
console.log(`Tag data:\n${this.tagData}`);
} else {
console.log('No tags found.');
}
}
}
// Example usage (browser console)
// const reader = new GSFReader(fileFromInput);
// reader.write('output.gsf');
7. C class
(Note: C doesn't have classes, so this is a struct with functions. Compile with gcc gsf.c -o gsf -lz
for zlib support, though not used for dump.)
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <zlib.h> // Optional for decompression
typedef struct {
char filename[256];
uint8_t* data;
size_t data_size;
char signature[4]; // Null-terminated
uint8_t version;
uint32_t reserved_size;
uint32_t compressed_size;
uint32_t crc32;
uint8_t* reserved_data;
uint8_t* compressed_program;
char tag_signature[6]; // Null-terminated
char* tag_data;
} GSFReader;
void gsf_read(GSFReader* gsf) {
FILE* f = fopen(gsf->filename, "rb");
if (!f) {
perror("Error opening file");
return;
}
fseek(f, 0, SEEK_END);
gsf->data_size = ftell(f);
fseek(f, 0, SEEK_SET);
gsf->data = malloc(gsf->data_size);
fread(gsf->data, 1, gsf->data_size, f);
fclose(f);
uint8_t* ptr = gsf->data;
memcpy(gsf->signature, ptr, 3);
gsf->signature[3] = '\0';
ptr += 3;
gsf->version = *ptr;
ptr += 1;
gsf->reserved_size = *(uint32_t*)ptr;
ptr += 4;
gsf->compressed_size = *(uint32_t*)ptr;
ptr += 4;
gsf->crc32 = *(uint32_t*)ptr;
ptr += 4;
gsf->reserved_data = malloc(gsf->reserved_size);
memcpy(gsf->reserved_data, ptr, gsf->reserved_size);
ptr += gsf->reserved_size;
gsf->compressed_program = malloc(gsf->compressed_size);
memcpy(gsf->compressed_program, ptr, gsf->compressed_size);
ptr += gsf->compressed_size;
if (ptr < gsf->data + gsf->data_size) {
memcpy(gsf->tag_signature, ptr, 5);
gsf->tag_signature[5] = '\0';
ptr += 5;
gsf->tag_data = malloc(gsf->data_size - (ptr - gsf->data) + 1);
memcpy(gsf->tag_data, ptr, gsf->data_size - (ptr - gsf->data));
gsf->tag_data[gsf->data_size - (ptr - gsf->data)] = '\0';
}
print_properties(gsf);
}
void gsf_write(GSFReader* gsf, const char* output_filename) {
FILE* f = fopen(output_filename, "wb");
if (!f) {
perror("Error writing file");
return;
}
fwrite(gsf->data, 1, gsf->data_size, f);
fclose(f);
printf("Wrote to %s\n", output_filename);
}
void print_properties(GSFReader* gsf) {
printf("Signature: %s\n", gsf->signature);
printf("Version: 0x%02x (%s)\n", gsf->version, gsf->version == 0x22 ? "GSF" : "Unknown");
printf("Reserved size: %u bytes\n", gsf->reserved_size);
printf("Compressed program size: %u bytes\n", gsf->compressed_size);
printf("Compressed program CRC-32: 0x%08x\n", gsf->crc32);
printf("Reserved area: %zu bytes (binary)\n", gsf->reserved_size);
printf("Compressed program: %zu bytes (zlib)\n", gsf->compressed_size);
if (gsf->tag_signature[0]) {
printf("Tag signature: %s\n", gsf->tag_signature);
printf("Tag data:\n%s\n", gsf->tag_data);
} else {
printf("No tags found.\n");
}
}
void gsf_free(GSFReader* gsf) {
free(gsf->data);
free(gsf->reserved_data);
free(gsf->compressed_program);
free(gsf->tag_data);
}
// Example usage
// GSFReader gsf = { .filename = "example.gsf" };
// gsf_read(&gsf);
// gsf_write(&gsf, "output.gsf");
// gsf_free(&gsf);