Task 015: .ADX File Format
Task 015: .ADX File Format
File Format Specifications for the .ADX File Format
The .ADX file format refers to the CRI ADX audio container and compression format, a proprietary lossy format developed by CRI Middleware for use in video games. It is derived from ADPCM and supports single or stereo audio streams with optional looping. The format uses big-endian byte order. The header structure varies slightly based on the version (type 03, 04, or 05), with differences in loop data. The specifications are derived from established technical references, including the Multimedia Wiki and Wikipedia.
1. List of All Properties Intrinsic to This File Format
The properties are the fields in the file's header and structure, which define its metadata and configuration. Below is a comprehensive list, presented in a table for clarity. Addresses are in hexadecimal, and all integers are big-endian unless noted.
| Address | Size (Bytes) | Field Name | Description |
|---|---|---|---|
| 0x00 | 2 | Magic Number | Fixed identifier (0x8000). |
| 0x02 | 2 | Data Offset | Byte offset to audio data from the start of this field (add 2 to get absolute offset from file start). |
| 0x04 | 1 | Encoding Type | Format identifier (0x03 for standard ADX; 0x04 for ADX with exponential scale; 0x02 for fixed coefficient ADPCM; 0x11 for AHX). |
| 0x05 | 1 | Block Size | Frame size in bytes (typically 18). |
| 0x06 | 1 | Sample Bitdepth | Bits per sample (typically 4). |
| 0x07 | 1 | Channel Count | Number of channels (1 for mono, 2 for stereo; up to 255 supported but rarely used). |
| 0x08 | 4 | Sample Rate | Sampling frequency in Hz (e.g., 44100). |
| 0x0C | 4 | Total Samples | Total number of samples in the stream. |
| 0x10 | 2 | Highpass Frequency | Cutoff value for high-pass filter, used in coefficient calculation. |
| 0x12 | 1 | Version (Loop Data Style) | Indicates loop data format (0x03, 0x04, or 0x05; 0x05 lacks loop info). |
| 0x13 | 1 | Flags | Encryption flag (0x08 for enabled) and other possible flags. |
| 0x14 (Type 03) | 4 | Unknown (Type 03) | Purpose unknown. |
| 0x18 (Type 03) | 4 | Loop Flag (Type 03) | Indicates if looping is enabled (non-zero for enabled). |
| 0x1C (Type 03) | 4 | Loop Start Sample (Type 03) | Sample index where loop begins. |
| 0x20 (Type 03) | 4 | Loop Start Byte (Type 03) | Byte offset where loop begins. |
| 0x24 (Type 03) | 4 | Loop End Sample (Type 03) | Sample index where loop ends. |
| 0x28 (Type 03) | 4 | Loop End Byte (Type 03) | Byte offset where loop ends. |
| 0x14 (Type 04) | 16 | Unknown (Type 04) | Possibly decoder history initialization. |
| 0x24 (Type 04) | 4 | Loop Flag (Type 04) | Indicates if looping is enabled (non-zero for enabled). |
| 0x28 (Type 04) | 4 | Loop Start Sample (Type 04) | Sample index where loop begins. |
| 0x2C (Type 04) | 4 | Loop Start Byte (Type 04) | Byte offset where loop begins. |
| 0x30 (Type 04) | 4 | Loop End Sample (Type 04) | Sample index where loop ends. |
| 0x34 (Type 04) | 4 | Loop End Byte (Type 04) | Byte offset where loop ends. |
| Variable (after header) | Variable | Padding | Padding bytes to align with data offset, often ending with ASCII "(c)CRI". |
| Data Offset + 2 | Variable | Audio Data | ADPCM-encoded audio frames, ending with a dummy frame (scale with high bit set). |
Note: For non-looping files, the header may be truncated. Encryption, if enabled, uses XOR with a linear congruential generator. The audio data properties (e.g., frame structure) are intrinsic but not listed as header fields.
2. Two Direct Download Links for Files of Format .ADX
The following links lead to archives containing CRI ADX tools and sample .ADX files:
- https://sourceforge.net/projects/shenmuesubs/files/addons/CRI_Middleware_ADX_Tools_v1.14_Win32.rar/download
- https://sourceforge.net/projects/shenmuesubs/files/addons/CRI_Middleware_ADX_Tools_v1.15_Linux_Win32.rar/download
These RAR files include Windows and Linux binaries for ADX tools, along with example .ADX files for testing.
3. Ghost Blog Embedded HTML JavaScript for Drag and Drop
The following is a self-contained HTML file with embedded JavaScript that allows a user to drag and drop a .ADX file. It parses the file and dumps all properties to the screen. Save this as an HTML file and open in a web browser.
4. Python Class for .ADX File Handling
The following Python class can open, decode, read, write, and print the properties of a .ADX file. It uses the struct module for binary parsing. The write method creates a basic non-looping .ADX file for demonstration.
import struct
import os
class AdxFile:
def __init__(self, filepath):
self.filepath = filepath
self.magic = None
self.data_offset = None
self.encoding_type = None
self.block_size = None
self.sample_bitdepth = None
self.channel_count = None
self.sample_rate = None
self.total_samples = None
self.highpass_freq = None
self.version = None
self.flags = None
# Loop data (example for type 04)
self.loop_flag = None
self.loop_start_sample = None
self.loop_start_byte = None
self.loop_end_sample = None
self.loop_end_byte = None
self.copyright = None
self.audio_data = None
def read(self):
with open(self.filepath, 'rb') as f:
data = f.read()
self.magic = struct.unpack('>H', data[0:2])[0]
self.data_offset = struct.unpack('>H', data[2:4])[0]
self.encoding_type = data[4]
self.block_size = data[5]
self.sample_bitdepth = data[6]
self.channel_count = data[7]
self.sample_rate = struct.unpack('>I', data[8:12])[0]
self.total_samples = struct.unpack('>I', data[12:16])[0]
self.highpass_freq = struct.unpack('>H', data[16:18])[0]
self.version = data[18]
self.flags = data[19]
if self.version == 4:
self.loop_flag = struct.unpack('>I', data[36:40])[0]
self.loop_start_sample = struct.unpack('>I', data[40:44])[0]
self.loop_start_byte = struct.unpack('>I', data[44:48])[0]
self.loop_end_sample = struct.unpack('>I', data[48:52])[0]
self.loop_end_byte = struct.unpack('>I', data[52:56])[0]
copyright_offset = self.data_offset - 4
self.copyright = data[copyright_offset:copyright_offset + 6].decode('ascii', errors='ignore')
self.audio_data = data[self.data_offset + 2:]
self.print_properties()
def print_properties(self):
print(f"Magic Number: 0x{self.magic:04X}")
print(f"Data Offset: {self.data_offset}")
print(f"Encoding Type: 0x{self.encoding_type:02X}")
print(f"Block Size: {self.block_size}")
print(f"Sample Bitdepth: {self.sample_bitdepth}")
print(f"Channel Count: {self.channel_count}")
print(f"Sample Rate: {self.sample_rate}")
print(f"Total Samples: {self.total_samples}")
print(f"Highpass Frequency: {self.highpass_freq}")
print(f"Version: 0x{self.version:02X}")
print(f"Flags: 0x{self.flags:02X}")
if self.version == 4:
print(f"Loop Flag: {self.loop_flag}")
print(f"Loop Start Sample: {self.loop_start_sample}")
print(f"Loop Start Byte: {self.loop_start_byte}")
print(f"Loop End Sample: {self.loop_end_sample}")
print(f"Loop End Byte: {self.loop_end_byte}")
print(f"Copyright String: {self.copyright}")
def write(self, new_filepath):
# Basic write for non-looping file (type 03, no loop)
header = struct.pack('>HHBBBBIIHB B', 0x8000, 0x20, 0x03, 18, 4, 1, 44100, 0, 500, 3, 0) # Example values
padding = b'\x00' * (0x20 - len(header) - 6) + b'(c)CRI'
with open(new_filepath, 'wb') as f:
f.write(header + padding + self.audio_data if self.audio_data else b'')
# Example usage
# adx = AdxFile('path/to/file.adx')
# adx.read()
# adx.write('path/to/new_file.adx')
5. Java Class for .ADX File Handling
The following Java class can open, decode, read, write, and print the properties of a .ADX file. It uses DataInputStream for reading and DataOutputStream for writing.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class AdxFile {
private String filepath;
private int magic;
private int dataOffset;
private byte encodingType;
private byte blockSize;
private byte sampleBitdepth;
private byte channelCount;
private int sampleRate;
private int totalSamples;
private int highpassFreq;
private byte version;
private byte flags;
private int loopFlag;
private int loopStartSample;
private int loopStartByte;
private int loopEndSample;
private int loopEndByte;
private String copyright;
private byte[] audioData;
public AdxFile(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
try (DataInputStream dis = new DataInputStream(new FileInputStream(filepath))) {
magic = dis.readUnsignedShort();
dataOffset = dis.readUnsignedShort();
encodingType = dis.readByte();
blockSize = dis.readByte();
sampleBitdepth = dis.readByte();
channelCount = dis.readByte();
sampleRate = dis.readInt();
totalSamples = dis.readInt();
highpassFreq = dis.readUnsignedShort();
version = dis.readByte();
flags = dis.readByte();
if (version == 4) {
dis.skipBytes(16); // Unknown
loopFlag = dis.readInt();
loopStartSample = dis.readInt();
loopStartByte = dis.readInt();
loopEndSample = dis.readInt();
loopEndByte = dis.readInt();
}
// Skip to copyright
dis.skipBytes(dataOffset - 0x14 - (version == 4 ? 36 : 0) - 6);
byte[] copyBytes = new byte[6];
dis.readFully(copyBytes);
copyright = new String(copyBytes, "ASCII");
// Read audio data (for write)
audioData = new byte[dis.available()];
dis.readFully(audioData);
}
printProperties();
}
public void printProperties() {
System.out.println("Magic Number: 0x" + Integer.toHexString(magic).toUpperCase());
System.out.println("Data Offset: " + dataOffset);
System.out.println("Encoding Type: 0x" + Integer.toHexString(encodingType & 0xFF).toUpperCase());
System.out.println("Block Size: " + (blockSize & 0xFF));
System.out.println("Sample Bitdepth: " + (sampleBitdepth & 0xFF));
System.out.println("Channel Count: " + (channelCount & 0xFF));
System.out.println("Sample Rate: " + sampleRate);
System.out.println("Total Samples: " + totalSamples);
System.out.println("Highpass Frequency: " + highpassFreq);
System.out.println("Version: 0x" + Integer.toHexString(version & 0xFF).toUpperCase());
System.out.println("Flags: 0x" + Integer.toHexString(flags & 0xFF).toUpperCase());
if (version == 4) {
System.out.println("Loop Flag: " + loopFlag);
System.out.println("Loop Start Sample: " + loopStartSample);
System.out.println("Loop Start Byte: " + loopStartByte);
System.out.println("Loop End Sample: " + loopEndSample);
System.out.println("Loop End Byte: " + loopEndByte);
}
System.out.println("Copyright String: " + copyright);
}
public void write(String newFilepath) throws IOException {
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(newFilepath))) {
dos.writeShort(0x8000);
dos.writeShort(0x20);
dos.writeByte(0x03);
dos.writeByte(18);
dos.writeByte(4);
dos.writeByte(1);
dos.writeInt(44100);
dos.writeInt(0);
dos.writeShort(500);
dos.writeByte(3);
dos.writeByte(0);
// Padding and copyright
dos.writeBytes("\0\0\0\0(c)CRI");
if (audioData != null) dos.write(audioData);
}
}
// Example usage
// public static void main(String[] args) throws IOException {
// AdxFile adx = new AdxFile("path/to/file.adx");
// adx.read();
// adx.write("path/to/new_file.adx");
// }
}
6. JavaScript Class for .ADX File Handling
The following JavaScript class can open, decode, read, write, and print the properties of a .ADX file. It is designed for Node.js, using the fs module.
const fs = require('fs');
class AdxFile {
constructor(filepath) {
this.filepath = filepath;
this.magic = null;
this.dataOffset = null;
this.encodingType = null;
this.blockSize = null;
this.sampleBitdepth = null;
this.channelCount = null;
this.sampleRate = null;
this.totalSamples = null;
this.highpassFreq = null;
this.version = null;
this.flags = null;
this.loopFlag = null;
this.loopStartSample = null;
this.loopStartByte = null;
this.loopEndSample = null;
this.loopEndByte = null;
this.copyright = null;
this.audioData = null;
}
read() {
const data = fs.readFileSync(this.filepath);
const dv = new DataView(data.buffer);
this.magic = dv.getUint16(0, false);
this.dataOffset = dv.getUint16(2, false);
this.encodingType = dv.getUint8(4);
this.blockSize = dv.getUint8(5);
this.sampleBitdepth = dv.getUint8(6);
this.channelCount = dv.getUint8(7);
this.sampleRate = dv.getUint32(8, false);
this.totalSamples = dv.getUint32(12, false);
this.highpassFreq = dv.getUint16(16, false);
this.version = dv.getUint8(18);
this.flags = dv.getUint8(19);
if (this.version === 4) {
this.loopFlag = dv.getUint32(36, false);
this.loopStartSample = dv.getUint32(40, false);
this.loopStartByte = dv.getUint32(44, false);
this.loopEndSample = dv.getUint32(48, false);
this.loopEndByte = dv.getUint32(52, false);
}
const copyrightOffset = this.dataOffset - 4;
this.copyright = new TextDecoder('ascii').decode(data.slice(copyrightOffset, copyrightOffset + 6));
this.audioData = data.slice(this.dataOffset + 2);
this.printProperties();
}
printProperties() {
console.log(`Magic Number: 0x${this.magic.toString(16).toUpperCase()}`);
console.log(`Data Offset: ${this.dataOffset}`);
console.log(`Encoding Type: 0x${this.encodingType.toString(16).toUpperCase()}`);
console.log(`Block Size: ${this.blockSize}`);
console.log(`Sample Bitdepth: ${this.sampleBitdepth}`);
console.log(`Channel Count: ${this.channelCount}`);
console.log(`Sample Rate: ${this.sampleRate}`);
console.log(`Total Samples: ${this.totalSamples}`);
console.log(`Highpass Frequency: ${this.highpassFreq}`);
console.log(`Version: 0x${this.version.toString(16).toUpperCase()}`);
console.log(`Flags: 0x${this.flags.toString(16).toUpperCase()}`);
if (this.version === 4) {
console.log(`Loop Flag: ${this.loopFlag}`);
console.log(`Loop Start Sample: ${this.loopStartSample}`);
console.log(`Loop Start Byte: ${this.loopStartByte}`);
console.log(`Loop End Sample: ${this.loopEndSample}`);
console.log(`Loop End Byte: ${this.loopEndByte}`);
}
console.log(`Copyright String: ${this.copyright}`);
}
write(newFilepath) {
const buffer = Buffer.alloc(0x20 + (this.audioData ? this.audioData.length : 0));
const dv = new DataView(buffer.buffer);
dv.setUint16(0, 0x8000, false);
dv.setUint16(2, 0x20, false);
dv.setUint8(4, 0x03);
dv.setUint8(5, 18);
dv.setUint8(6, 4);
dv.setUint8(7, 1);
dv.setUint32(8, 44100, false);
dv.setUint32(12, 0, false);
dv.setUint16(16, 500, false);
dv.setUint8(18, 3);
dv.setUint8(19, 0);
buffer.write('(c)CRI', 0x20 - 6);
if (this.audioData) buffer.copy(this.audioData, 0x20);
fs.writeFileSync(newFilepath, buffer);
}
}
// Example usage
// const adx = new AdxFile('path/to/file.adx');
// adx.read();
// adx.write('path/to/new_file.adx');
7. C Class for .ADX File Handling
The following is a C++ class for handling .ADX files. It can open, decode, read, write, and print the properties to console. It uses <fstream> and <iostream>.
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdint>
class AdxFile {
private:
std::string filepath;
uint16_t magic;
uint16_t dataOffset;
uint8_t encodingType;
uint8_t blockSize;
uint8_t sampleBitdepth;
uint8_t channelCount;
uint32_t sampleRate;
uint32_t totalSamples;
uint16_t highpassFreq;
uint8_t version;
uint8_t flags;
uint32_t loopFlag;
uint32_t loopStartSample;
uint32_t loopStartByte;
uint32_t loopEndSample;
uint32_t loopEndByte;
std::string copyrightStr;
char* audioData;
size_t audioSize;
public:
AdxFile(const std::string& fp) : filepath(fp), audioData(nullptr), audioSize(0) {}
~AdxFile() { delete[] audioData; }
void read() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
file.seekg(0);
char* buffer = new char[size];
file.read(buffer, size);
magic = *reinterpret_cast<uint16_t*>(buffer);
dataOffset = *reinterpret_cast<uint16_t*>(buffer + 2);
encodingType = *(buffer + 4);
blockSize = *(buffer + 5);
sampleBitdepth = *(buffer + 6);
channelCount = *(buffer + 7);
sampleRate = *reinterpret_cast<uint32_t*>(buffer + 8);
totalSamples = *reinterpret_cast<uint32_t*>(buffer + 12);
highpassFreq = *reinterpret_cast<uint16_t*>(buffer + 16);
version = *(buffer + 18);
flags = *(buffer + 19);
if (version == 4) {
loopFlag = *reinterpret_cast<uint32_t*>(buffer + 36);
loopStartSample = *reinterpret_cast<uint32_t*>(buffer + 40);
loopStartByte = *reinterpret_cast<uint32_t*>(buffer + 44);
loopEndSample = *reinterpret_cast<uint32_t*>(buffer + 48);
loopEndByte = *reinterpret_cast<uint32_t*>(buffer + 52);
}
char copyBuf[7] = {0};
memcpy(copyBuf, buffer + dataOffset - 4, 6);
copyrightStr = std::string(copyBuf);
audioSize = size - (dataOffset + 2);
audioData = new char[audioSize];
memcpy(audioData, buffer + dataOffset + 2, audioSize);
delete[] buffer;
printProperties();
}
void printProperties() {
std::cout << "Magic Number: 0x" << std::hex << magic << std::dec << std::endl;
std::cout << "Data Offset: " << dataOffset << std::endl;
std::cout << "Encoding Type: 0x" << std::hex << static_cast<int>(encodingType) << std::dec << std::endl;
std::cout << "Block Size: " << static_cast<int>(blockSize) << std::endl;
std::cout << "Sample Bitdepth: " << static_cast<int>(sampleBitdepth) << std::endl;
std::cout << "Channel Count: " << static_cast<int>(channelCount) << std::endl;
std::cout << "Sample Rate: " << sampleRate << std::endl;
std::cout << "Total Samples: " << totalSamples << std::endl;
std::cout << "Highpass Frequency: " << highpassFreq << std::endl;
std::cout << "Version: 0x" << std::hex << static_cast<int>(version) << std::dec << std::endl;
std::cout << "Flags: 0x" << std::hex << static_cast<int>(flags) << std::dec << std::endl;
if (version == 4) {
std::cout << "Loop Flag: " << loopFlag << std::endl;
std::cout << "Loop Start Sample: " << loopStartSample << std::endl;
std::cout << "Loop Start Byte: " << loopStartByte << std::endl;
std::cout << "Loop End Sample: " << loopEndSample << std::endl;
std::cout << "Loop End Byte: " << loopEndByte << std::endl;
}
std::cout << "Copyright String: " << copyrightStr << std::endl;
}
void write(const std::string& newFilepath) {
std::ofstream file(newFilepath, std::ios::binary);
uint16_t tempMagic = 0x8000;
file.write(reinterpret_cast<char*>(&tempMagic), 2);
uint16_t tempOffset = 0x20;
file.write(reinterpret_cast<char*>(&tempOffset), 2);
uint8_t tempType = 0x03;
file.write(reinterpret_cast<char*>(&tempType), 1);
uint8_t tempBlock = 18;
file.write(reinterpret_cast<char*>(&tempBlock), 1);
uint8_t tempBit = 4;
file.write(reinterpret_cast<char*>(&tempBit), 1);
uint8_t tempChan = 1;
file.write(reinterpret_cast<char*>(&tempChan), 1);
uint32_t tempRate = 44100;
file.write(reinterpret_cast<char*>(&tempRate), 4);
uint32_t tempSamples = 0;
file.write(reinterpret_cast<char*>(&tempSamples), 4);
uint16_t tempHigh = 500;
file.write(reinterpret_cast<char*>(&tempHigh), 2);
uint8_t tempVer = 3;
file.write(reinterpret_cast<char*>(&tempVer), 1);
uint8_t tempFlags = 0;
file.write(reinterpret_cast<char*>(&tempFlags), 1);
char padding[4] = {0};
file.write(padding, 4);
file.write("(c)CRI", 6);
if (audioData) file.write(audioData, audioSize);
}
};
// Example usage
// int main() {
// AdxFile adx("path/to/file.adx");
// adx.read();
// adx.write("path/to/new_file.adx");
// return 0;
// }