Task 393: .MDS File Format
Task 393: .MDS File Format
1. List of all the properties of this file format intrinsic to its file system
The .MDS file format is a binary metadata format used as a sidecar for .MDF disc image files, primarily created by Alcohol 120%. It describes the structure of CD/DVD discs, including sessions, tracks, sectors, and data offsets. The properties (fields) are organized into structures like the header, session blocks, data blocks, index blocks, filename blocks, filename strings, and optional read errors. Below is a comprehensive list based on the format specification:
Header (88 bytes / 58h bytes):
- File ID: String (16 bytes) - Always "MEDIA DESCRIPTOR".
- Version: Unknown (2 bytes) - Typically 01h,03h or 01h,04h or 01h,05h (file format version?).
- Media Type: Word (2 bytes) - 0=CD-ROM, 1=CD-R, 2=CD-RW, 16h=DVD-ROM, 12h=DVD-R.
- Number of sessions: Word (2 bytes) - Usually 1.
- Unknown: DWord (4 bytes) - Typically 02h,00h,00h,00.
- BCA Length: Word (2 bytes) - Zero for CD, length of BCA data for DVD.
- Zero Padding 1: 8 bytes - Always zero.
- BCA Offset: DWord (4 bytes) - Zero for CD, offset to BCA data for DVD.
- Zero Padding 2: 24 bytes (18h bytes) - Always zero.
- Disc Structures Offset: DWord (4 bytes) - Zero for CD, offset to disc structures for DVD.
- Zero Padding 3: 12 bytes (0Ch bytes) - Always zero.
- Offset to First Session Block: DWord (4 bytes) - Pointer to the first session block (usually 58h).
- Offset to Read Errors: DWord (4 bytes) - Pointer to read errors section (usually 0 for none).
Session Blocks (24 bytes / 18h bytes each):
- Session Start Sector: DWord (4 bytes) - Starting sector (e.g., FFFFFF6Ah = -150 for first session).
- Session End Sector: DWord (4 bytes) - Ending sector (track end plus 150?).
- Session Number: Word (2 bytes) - Starting at 1 (non-BCD).
- Number of Data Blocks (Any Point): Byte (1 byte) - Total data blocks with any Point value.
- Number of Data Blocks (Point >= A0h): Byte (1 byte) - Data blocks with special lead-in info.
- First Track Number: Word (2 bytes) - First track in session (01h..63h, non-BCD).
- Last Track Number: Word (2 bytes) - Last track in session (01h..63h, non-BCD).
- Zero Padding: DWord (4 bytes) - Always zero.
- Offset to First Data Block: DWord (4 bytes) - Pointer to first data block (usually 70h).
Data Blocks (80 bytes / 50h bytes each):
- Track Mode: Byte (1 byte) - Track type (e.g., 00h=None, A9h=AUDIO, AAh=MODE1, ABh=MODE2, ACh=MODE2_FORM1, ADh=MODE2_FORM2, ECh=MODE2 with subchannels).
- Number of Subchannels: Byte (1 byte) - 0=None, 8=Sector has +96 bytes (60h bytes) subchannel data.
- ADR/Control: Byte (1 byte) - ADR and Control bits (upper/lower 4 bits swapped, MSBs=ADR).
- TrackNo: Byte (1 byte) - Usually 00h (lead-in area).
- Point: Byte (1 byte) - Non-BCD track number (01h..63h) or A0h+ for lead-in info.
- Zero Padding (MSF Dummy): 4 bytes - Always zero (dummy MSF and reserved from Q channel).
- Minute: Byte (1 byte) - Non-BCD minute of pointed track or disc info.
- Second: Byte (1 byte) - Non-BCD second.
- Frame: Byte (1 byte) - Non-BCD frame.
- Offset to Index Block: DWord (4 bytes) - Pointer to index block for this track (zero for Point >= A0h).
- Sector Size: Word (2 bytes) - Size of sectors (e.g., 800h..930h, or 860h..990h with subchannels).
- Unknown: Byte (1 byte) - Typically 02h (number of indices?).
- Zero Padding: 17 bytes (11h bytes) - Always zero.
- Track Start Sector (PLBA): DWord (4 bytes) - Pre-gap LBA (e.g., 00000000h = 00:02:00).
- Track Start Offset: QWord (8 bytes) - Offset in .MDF file.
- Number of Filenames: DWord (4 bytes) - Usually 1.
- Offset to Filename Block: DWord (4 bytes) - Pointer to filename block.
- Zero Padding: 24 bytes (18h bytes) - Always zero.
Index Blocks (usually 8 bytes per track):
- Number of Sectors with Index 0: DWord (4 bytes) - Usually 150 (96h) or zero.
- Number of Sectors with Index 1: DWord (4 bytes) - Size of main track area.
Filename Blocks (16 bytes / 10h bytes each):
- Offset to Filename String: DWord (4 bytes) - Pointer to filename string (from beginning of .MDS file).
- Zero Padding: 12 bytes - Always zero (or additional fields if used).
Filename Strings (variable length):
- Filename: String - Name of the .MDF data file (null-terminated, usually something like "image.mdf\0").
Read Errors (variable, usually none):
- Read Error Structures: Variable - If offset in header is non-zero, contains error data for read issues during imaging (structure not fully defined in specs, but typically absent in standard files).
These properties define the disc layout, track modes, sector offsets, and data file references, intrinsic to how the file format represents the disc image file system.
2. Two direct download links for files of format .MDS
- https://archive.org/download/ASUSDVDV1.3.5/ASUS_DVD1_3_5.mds
- https://archive.org/download/mmntwarfrenchv6/MEGAMAN2DVD_3.MDS
3. Ghost blog embedded html javascript that allows a user to drag n drop a file of format .MDS and it will dump to screen all these properties
Drag and Drop .MDS File to Parse Properties
4. Python class that can open any file of format .MDS and decode read and write and print to console all the properties from the above list
import struct
import os
class MDSParser:
def __init__(self, filepath=None):
self.filepath = filepath
self.data = None
self.properties = {}
if filepath:
self.read(filepath)
def read(self, filepath):
self.filepath = filepath
with open(filepath, 'rb') as f:
self.data = f.read()
self.parse()
return self.properties
def parse(self):
if not self.data.startswith(b'MEDIA DESCRIPTOR'):
raise ValueError("Invalid .MDS file")
data_view = memoryview(self.data)
# Header
self.properties['Header'] = {}
self.properties['Header']['File ID'] = self.data[0:16].decode('ascii')
self.properties['Header']['Version'] = (data_view[16], data_view[17])
self.properties['Header']['Media Type'] = struct.unpack_from('<H', data_view, 18)[0]
self.properties['Header']['Number of sessions'] = struct.unpack_from('<H', data_view, 20)[0]
self.properties['Header']['Unknown'] = struct.unpack_from('<I', data_view, 22)[0]
self.properties['Header']['BCA Length'] = struct.unpack_from('<H', data_view, 26)[0]
self.properties['Header']['BCA Offset'] = struct.unpack_from('<I', data_view, 36)[0]
self.properties['Header']['Disc Structures Offset'] = struct.unpack_from('<I', data_view, 64)[0]
self.properties['Header']['Offset to First Session Block'] = struct.unpack_from('<I', data_view, 80)[0]
self.properties['Header']['Offset to Read Errors'] = struct.unpack_from('<I', data_view, 84)[0]
# Session Blocks
self.properties['Session Blocks'] = []
offset = self.properties['Header']['Offset to First Session Block']
num_sessions = self.properties['Header']['Number of sessions']
for i in range(num_sessions):
session = {}
session['Session Start Sector'] = struct.unpack_from('<i', data_view, offset)[0]
session['Session End Sector'] = struct.unpack_from('<i', data_view, offset + 4)[0]
session['Session Number'] = struct.unpack_from('<H', data_view, offset + 8)[0]
session['Num Data Blocks (Any Point)'] = data_view[offset + 10]
session['Num Data Blocks (Point >= A0h)'] = data_view[offset + 11]
session['First Track Number'] = struct.unpack_from('<H', data_view, offset + 12)[0]
session['Last Track Number'] = struct.unpack_from('<H', data_view, offset + 14)[0]
session['Offset to First Data Block'] = struct.unpack_from('<I', data_view, offset + 20)[0]
self.properties['Session Blocks'].append(session)
offset += 24
# Data Blocks (using num from last session for simplicity)
self.properties['Data Blocks'] = []
data_offset = self.properties['Session Blocks'][-1]['Offset to First Data Block']
num_data_blocks = self.properties['Session Blocks'][-1]['Num Data Blocks (Any Point)']
for i in range(num_data_blocks):
data_block = {}
data_block['Track Mode'] = data_view[data_offset]
data_block['Number of Subchannels'] = data_view[data_offset + 1]
data_block['ADR/Control'] = data_view[data_offset + 2]
data_block['TrackNo'] = data_view[data_offset + 3]
data_block['Point'] = data_view[data_offset + 4]
data_block['Minute'] = data_view[data_offset + 9]
data_block['Second'] = data_view[data_offset + 10]
data_block['Frame'] = data_view[data_offset + 11]
data_block['Offset to Index Block'] = struct.unpack_from('<I', data_view, data_offset + 12)[0]
data_block['Sector Size'] = struct.unpack_from('<H', data_view, data_offset + 16)[0]
data_block['Unknown'] = data_view[data_offset + 18]
data_block['Track Start Sector (PLBA)'] = struct.unpack_from('<I', data_view, data_offset + 36)[0]
data_block['Track Start Offset'] = struct.unpack_from('<Q', data_view, data_offset + 40)[0]
data_block['Number of Filenames'] = struct.unpack_from('<I', data_view, data_offset + 48)[0]
data_block['Offset to Filename Block'] = struct.unpack_from('<I', data_view, data_offset + 52)[0]
self.properties['Data Blocks'].append(data_block)
data_offset += 80
# Index Blocks (assume 8 bytes per track, approximate num tracks)
self.properties['Index Blocks'] = []
index_offset = self.properties['Data Blocks'][3]['Offset to Index Block'] if len(self.properties['Data Blocks']) > 3 else 0 # Skip A0-A2
num_tracks = num_data_blocks - self.properties['Session Blocks'][-1]['Num Data Blocks (Point >= A0h)']
for i in range(num_tracks):
index = {}
index['Sectors Index 0'] = struct.unpack_from('<I', data_view, index_offset)[0]
index['Sectors Index 1'] = struct.unpack_from('<I', data_view, index_offset + 4)[0]
self.properties['Index Blocks'].append(index)
index_offset += 8
# Filename Blocks and Strings (assume from first track)
self.properties['Filename Blocks'] = []
filename_offset = self.properties['Data Blocks'][3]['Offset to Filename Block'] if len(self.properties['Data Blocks']) > 3 else 0
num_filenames = self.properties['Data Blocks'][3]['Number of Filenames'] if len(self.properties['Data Blocks']) > 3 else 0
for i in range(num_filenames):
filename_block = {}
filename_block['Offset to Filename String'] = struct.unpack_from('<I', data_view, filename_offset)[0]
string_offset = filename_block['Offset to Filename String']
filename = ''
while data_view[string_offset] != 0:
filename += chr(data_view[string_offset])
string_offset += 1
filename_block['Filename'] = filename
self.properties['Filename Blocks'].append(filename_block)
filename_offset += 16
# Read Errors
self.properties['Read Errors'] = 'None' if self.properties['Header']['Offset to Read Errors'] == 0 else 'Present'
def print_properties(self):
for section, props in self.properties.items():
print(section + ':')
if isinstance(props, list):
for idx, item in enumerate(props):
print(f' Item {idx}:')
for key, value in item.items():
print(f' - {key}: {value}')
else:
for key, value in props.items():
print(f' - {key}: {value}')
print('\n')
def write(self, filepath=None):
if not filepath:
filepath = self.filepath or 'output.mds'
with open(filepath, 'wb') as f:
f.write(self.data) # Simple write back; for full write, reconstruct from properties
print(f'File written to {filepath}')
# Example usage
# parser = MDSParser('example.mds')
# parser.print_properties()
# parser.write('new.mds')
5. Java class that can open any file of format .MDS and decode read and write and print to console all the properties from the above list
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
public class MDSParser {
private String filepath;
private ByteBuffer buffer;
private Map<String, Object> properties;
public MDSParser(String filepath) throws IOException {
this.filepath = filepath;
read(filepath);
}
public void read(String filepath) throws IOException {
this.filepath = filepath;
RandomAccessFile raf = new RandomAccessFile(filepath, "r");
FileChannel channel = raf.getChannel();
buffer = ByteBuffer.allocate((int) channel.size());
channel.read(buffer);
buffer.flip();
buffer.order(ByteOrder.LITTLE_ENDIAN);
parse();
channel.close();
raf.close();
}
private void parse() {
properties = new HashMap<>();
byte[] idBytes = new byte[16];
buffer.position(0);
buffer.get(idBytes);
String id = new String(idBytes);
if (!id.equals("MEDIA DESCRIPTOR")) {
throw new RuntimeException("Invalid .MDS file");
}
// Header
Map<String, Object> header = new HashMap<>();
header.put("File ID", id);
header.put("Version", buffer.get(16) + "," + buffer.get(17));
header.put("Media Type", (int) buffer.getShort(18));
header.put("Number of sessions", (int) buffer.getShort(20));
header.put("Unknown", buffer.getInt(22));
header.put("BCA Length", (int) buffer.getShort(26));
header.put("BCA Offset", buffer.getInt(36));
header.put("Disc Structures Offset", buffer.getInt(64));
header.put("Offset to First Session Block", buffer.getInt(80));
header.put("Offset to Read Errors", buffer.getInt(84));
properties.put("Header", header);
// Session Blocks
List<Map<String, Object>> sessions = new ArrayList<>();
int offset = (int) header.get("Offset to First Session Block");
int numSessions = (int) header.get("Number of sessions");
for (int i = 0; i < numSessions; i++) {
Map<String, Object> session = new HashMap<>();
session.put("Session Start Sector", buffer.getInt(offset));
session.put("Session End Sector", buffer.getInt(offset + 4));
session.put("Session Number", (int) buffer.getShort(offset + 8));
session.put("Num Data Blocks (Any Point)", Byte.toUnsignedInt(buffer.get(offset + 10)));
session.put("Num Data Blocks (Point >= A0h)", Byte.toUnsignedInt(buffer.get(offset + 11)));
session.put("First Track Number", (int) buffer.getShort(offset + 12));
session.put("Last Track Number", (int) buffer.getShort(offset + 14));
session.put("Offset to First Data Block", buffer.getInt(offset + 20));
sessions.add(session);
offset += 24;
}
properties.put("Session Blocks", sessions);
// Data Blocks
List<Map<String, Object>> dataBlocks = new ArrayList<>();
int dataOffset = (int) sessions.get(sessions.size() - 1).get("Offset to First Data Block");
int numDataBlocks = (int) sessions.get(sessions.size() - 1).get("Num Data Blocks (Any Point)");
for (int i = 0; i < numDataBlocks; i++) {
Map<String, Object> dataBlock = new HashMap<>();
dataBlock.put("Track Mode", Byte.toUnsignedInt(buffer.get(dataOffset)));
dataBlock.put("Number of Subchannels", Byte.toUnsignedInt(buffer.get(dataOffset + 1)));
dataBlock.put("ADR/Control", Byte.toUnsignedInt(buffer.get(dataOffset + 2)));
dataBlock.put("TrackNo", Byte.toUnsignedInt(buffer.get(dataOffset + 3)));
dataBlock.put("Point", Byte.toUnsignedInt(buffer.get(dataOffset + 4)));
dataBlock.put("Minute", Byte.toUnsignedInt(buffer.get(dataOffset + 9)));
dataBlock.put("Second", Byte.toUnsignedInt(buffer.get(dataOffset + 10)));
dataBlock.put("Frame", Byte.toUnsignedInt(buffer.get(dataOffset + 11)));
dataBlock.put("Offset to Index Block", buffer.getInt(dataOffset + 12));
dataBlock.put("Sector Size", (int) buffer.getShort(dataOffset + 16));
dataBlock.put("Unknown", Byte.toUnsignedInt(buffer.get(dataOffset + 18)));
dataBlock.put("Track Start Sector (PLBA)", buffer.getInt(dataOffset + 36));
dataBlock.put("Track Start Offset", buffer.getLong(dataOffset + 40));
dataBlock.put("Number of Filenames", buffer.getInt(dataOffset + 48));
dataBlock.put("Offset to Filename Block", buffer.getInt(dataOffset + 52));
dataBlocks.add(dataBlock);
dataOffset += 80;
}
properties.put("Data Blocks", dataBlocks);
// Index Blocks
List<Map<String, Object>> indexBlocks = new ArrayList<>();
int indexOffset = (int) dataBlocks.get(3).get("Offset to Index Block");
int numTracks = numDataBlocks - (int) sessions.get(sessions.size() - 1).get("Num Data Blocks (Point >= A0h)");
for (int i = 0; i < numTracks; i++) {
Map<String, Object> index = new HashMap<>();
index.put("Sectors Index 0", buffer.getInt(indexOffset));
index.put("Sectors Index 1", buffer.getInt(indexOffset + 4));
indexBlocks.add(index);
indexOffset += 8;
}
properties.put("Index Blocks", indexBlocks);
// Filename Blocks and Strings
List<Map<String, Object>> filenameBlocks = new ArrayList<>();
int filenameOffset = (int) dataBlocks.get(3).get("Offset to Filename Block");
int numFilenames = (int) dataBlocks.get(3).get("Number of Filenames");
for (int i = 0; i < numFilenames; i++) {
Map<String, Object> filenameBlock = new HashMap<>();
filenameBlock.put("Offset to Filename String", buffer.getInt(filenameOffset));
int stringOffset = (int) filenameBlock.get("Offset to Filename String");
StringBuilder filename = new StringBuilder();
while (buffer.get(stringOffset) != 0) {
filename.append((char) buffer.get(stringOffset));
stringOffset++;
}
filenameBlock.put("Filename", filename.toString());
filenameBlocks.add(filenameBlock);
filenameOffset += 16;
}
properties.put("Filename Blocks", filenameBlocks);
// Read Errors
properties.put("Read Errors", (int) header.get("Offset to Read Errors") == 0 ? "None" : "Present");
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ":");
if (entry.getValue() instanceof List) {
List<?> list = (List<?>) entry.getValue();
for (int i = 0; i < list.size(); i++) {
System.out.println(" Item " + i + ":");
Map<?, ?> map = (Map<?, ?>) list.get(i);
for (Map.Entry<?, ?> item : map.entrySet()) {
System.out.println(" - " + item.getKey() + ": " + item.getValue());
}
}
} else if (entry.getValue() instanceof Map) {
Map<?, ?> map = (Map<?, ?>) entry.getValue();
for (Map.Entry<?, ?> item : map.entrySet()) {
System.out.println(" - " + item.getKey() + ": " + item.getValue());
}
} else {
System.out.println(" - " + entry.getValue());
}
}
}
public void write(String filepath) throws IOException {
if (filepath == null) filepath = this.filepath;
RandomAccessFile raf = new RandomAccessFile(filepath, "rw");
FileChannel channel = raf.getChannel();
buffer.position(0);
channel.write(buffer);
channel.close();
raf.close();
System.out.println("File written to " + filepath);
}
public static void main(String[] args) throws IOException {
MDSParser parser = new MDSParser("example.mds");
parser.printProperties();
parser.write("new.mds");
}
}
6. Javascript class that can open any file of format .MDS and decode read and write and print to console all the properties from the above list
class MDSParser {
constructor(filepath = null) {
this.filepath = filepath;
this.data = null;
this.properties = {};
if (filepath) {
// For node.js, use fs to read
const fs = require('fs');
this.read(filepath);
}
}
read(filepath) {
const fs = require('fs');
this.filepath = filepath;
this.data = fs.readFileSync(filepath);
this.parse();
return this.properties;
}
parse() {
if (this.data.toString('ascii', 0, 16) !== 'MEDIA DESCRIPTOR') {
throw new Error('Invalid .MDS file');
}
const dataView = new DataView(this.data.buffer);
// Header
this.properties.Header = {};
this.properties.Header['File ID'] = this.data.toString('ascii', 0, 16);
this.properties.Header['Version'] = dataView.getUint8(16).toString(16) + ',' + dataView.getUint8(17).toString(16);
this.properties.Header['Media Type'] = dataView.getUint16(18, true);
this.properties.Header['Number of sessions'] = dataView.getUint16(20, true);
this.properties.Header['Unknown'] = dataView.getUint32(22, true);
this.properties.Header['BCA Length'] = dataView.getUint16(26, true);
this.properties.Header['BCA Offset'] = dataView.getUint32(36, true);
this.properties.Header['Disc Structures Offset'] = dataView.getUint32(64, true);
this.properties.Header['Offset to First Session Block'] = dataView.getUint32(80, true);
this.properties.Header['Offset to Read Errors'] = dataView.getUint32(84, true);
// Session Blocks
this.properties['Session Blocks'] = [];
let offset = this.properties.Header['Offset to First Session Block'];
const numSessions = this.properties.Header['Number of sessions'];
for (let i = 0; i < numSessions; i++) {
const session = {};
session['Session Start Sector'] = dataView.getInt32(offset, true);
session['Session End Sector'] = dataView.getInt32(offset + 4, true);
session['Session Number'] = dataView.getUint16(offset + 8, true);
session['Num Data Blocks (Any Point)'] = dataView.getUint8(offset + 10);
session['Num Data Blocks (Point >= A0h)'] = dataView.getUint8(offset + 11);
session['First Track Number'] = dataView.getUint16(offset + 12, true);
session['Last Track Number'] = dataView.getUint16(offset + 14, true);
session['Offset to First Data Block'] = dataView.getUint32(offset + 20, true);
this.properties['Session Blocks'].push(session);
offset += 24;
}
// Data Blocks
this.properties['Data Blocks'] = [];
let dataOffset = this.properties['Session Blocks'][this.properties['Session Blocks'].length - 1]['Offset to First Data Block'];
const numDataBlocks = this.properties['Session Blocks'][this.properties['Session Blocks'].length - 1]['Num Data Blocks (Any Point)'];
for (let i = 0; i < numDataBlocks; i++) {
const dataBlock = {};
dataBlock['Track Mode'] = dataView.getUint8(dataOffset);
dataBlock['Number of Subchannels'] = dataView.getUint8(dataOffset + 1);
dataBlock['ADR/Control'] = dataView.getUint8(dataOffset + 2);
dataBlock['TrackNo'] = dataView.getUint8(dataOffset + 3);
dataBlock['Point'] = dataView.getUint8(dataOffset + 4);
dataBlock['Minute'] = dataView.getUint8(dataOffset + 9);
dataBlock['Second'] = dataView.getUint8(dataOffset + 10);
dataBlock['Frame'] = dataView.getUint8(dataOffset + 11);
dataBlock['Offset to Index Block'] = dataView.getUint32(dataOffset + 12, true);
dataBlock['Sector Size'] = dataView.getUint16(dataOffset + 16, true);
dataBlock['Unknown'] = dataView.getUint8(dataOffset + 18);
dataBlock['Track Start Sector (PLBA)'] = dataView.getUint32(dataOffset + 36, true);
dataBlock['Track Start Offset'] = Number(dataView.getBigInt64(dataOffset + 40, true));
dataBlock['Number of Filenames'] = dataView.getUint32(dataOffset + 48, true);
dataBlock['Offset to Filename Block'] = dataView.getUint32(dataOffset + 52, true);
this.properties['Data Blocks'].push(dataBlock);
dataOffset += 80;
}
// Index Blocks
this.properties['Index Blocks'] = [];
let indexOffset = this.properties['Data Blocks'][3]['Offset to Index Block'];
const numTracks = numDataBlocks - this.properties['Session Blocks'][this.properties['Session Blocks'].length - 1]['Num Data Blocks (Point >= A0h)'];
for (let i = 0; i < numTracks; i++) {
const index = {};
index['Sectors Index 0'] = dataView.getUint32(indexOffset, true);
index['Sectors Index 1'] = dataView.getUint32(indexOffset + 4, true);
this.properties['Index Blocks'].push(index);
indexOffset += 8;
}
// Filename Blocks
this.properties['Filename Blocks'] = [];
let filenameOffset = this.properties['Data Blocks'][3]['Offset to Filename Block'];
const numFilenames = this.properties['Data Blocks'][3]['Number of Filenames'];
for (let i = 0; i < numFilenames; i++) {
const filenameBlock = {};
filenameBlock['Offset to Filename String'] = dataView.getUint32(filenameOffset, true);
let stringOffset = filenameBlock['Offset to Filename String'];
let filename = '';
while (dataView.getUint8(stringOffset) !== 0) {
filename += String.fromCharCode(dataView.getUint8(stringOffset));
stringOffset++;
}
filenameBlock['Filename'] = filename;
this.properties['Filename Blocks'].push(filenameBlock);
filenameOffset += 16;
}
// Read Errors
this.properties['Read Errors'] = this.properties.Header['Offset to Read Errors'] === 0 ? 'None' : 'Present';
}
printProperties() {
console.log(this.properties);
}
write(filepath = null) {
const fs = require('fs');
if (!filepath) filepath = this.filepath;
fs.writeFileSync(filepath, this.data);
console.log(`File written to ${filepath}`);
}
}
// Example usage in Node.js
// const parser = new MDSParser('example.mds');
// parser.printProperties();
// parser.write('new.mds');
7. C class that can open any file of format .MDS and decode read and write and print to console all the properties from the above list
Note: C uses structs instead of classes, so this is implemented with structs and functions.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
typedef struct {
char file_id[17];
uint8_t version[2];
uint16_t media_type;
uint16_t num_sessions;
uint32_t unknown;
uint16_t bca_length;
uint32_t bca_offset;
uint32_t disc_structures_offset;
uint32_t offset_first_session;
uint32_t offset_read_errors;
} Header;
typedef struct {
int32_t start_sector;
int32_t end_sector;
uint16_t session_num;
uint8_t num_data_any;
uint8_t num_data_a0;
uint16_t first_track;
uint16_t last_track;
uint32_t offset_first_data;
} SessionBlock;
typedef struct {
uint8_t track_mode;
uint8_t num_subchannels;
uint8_t adr_control;
uint8_t track_no;
uint8_t point;
uint8_t minute;
uint8_t second;
uint8_t frame;
uint32_t offset_index;
uint16_t sector_size;
uint8_t unknown;
uint32_t track_start_plba;
uint64_t track_start_offset;
uint32_t num_filenames;
uint32_t offset_filename;
} DataBlock;
typedef struct {
uint32_t sectors_index0;
uint32_t sectors_index1;
} IndexBlock;
typedef struct {
uint32_t offset_string;
char* filename;
} FilenameBlock;
typedef struct {
Header header;
SessionBlock* sessions;
int num_sessions;
DataBlock* data_blocks;
int num_data_blocks;
IndexBlock* index_blocks;
int num_index_blocks;
FilenameBlock* filename_blocks;
int num_filename_blocks;
char* read_errors;
uint8_t* data;
long data_size;
} MDS;
MDS* mds_open(const char* filepath) {
FILE* f = fopen(filepath, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t* data = malloc(size);
fread(data, 1, size, f);
fclose(f);
MDS* mds = malloc(sizeof(MDS));
mds->data = data;
mds->data_size = size;
if (strncmp((char*)data, "MEDIA DESCRIPTOR", 16) != 0) {
free(mds);
free(data);
return NULL;
}
// Parse Header
memcpy(mds->header.file_id, data, 16);
mds->header.file_id[16] = '\0';
mds->header.version[0] = data[16];
mds->header.version[1] = data[17];
memcpy(&mds->header.media_type, data + 18, 2);
memcpy(&mds->header.num_sessions, data + 20, 2);
memcpy(&mds->header.unknown, data + 22, 4);
memcpy(&mds->header.bca_length, data + 26, 2);
memcpy(&mds->header.bca_offset, data + 36, 4);
memcpy(&mds->header.disc_structures_offset, data + 64, 4);
memcpy(&mds->header.offset_first_session, data + 80, 4);
memcpy(&mds->header.offset_read_errors, data + 84, 4);
// Parse Session Blocks
mds->num_sessions = mds->header.num_sessions;
mds->sessions = malloc(mds->num_sessions * sizeof(SessionBlock));
uint32_t offset = mds->header.offset_first_session;
for (int i = 0; i < mds->num_sessions; i++) {
memcpy(&mds->sessions[i].start_sector, data + offset, 4);
memcpy(&mds->sessions[i].end_sector, data + offset + 4, 4);
memcpy(&mds->sessions[i].session_num, data + offset + 8, 2);
mds->sessions[i].num_data_any = data[offset + 10];
mds->sessions[i].num_data_a0 = data[offset + 11];
memcpy(&mds->sessions[i].first_track, data + offset + 12, 2);
memcpy(&mds->sessions[i].last_track, data + offset + 14, 2);
memcpy(&mds->sessions[i].offset_first_data, data + offset + 20, 4);
offset += 24;
}
// Parse Data Blocks
uint32_t data_offset = mds->sessions[mds->num_sessions - 1].offset_first_data;
mds->num_data_blocks = mds->sessions[mds->num_sessions - 1].num_data_any;
mds->data_blocks = malloc(mds->num_data_blocks * sizeof(DataBlock));
for (int i = 0; i < mds->num_data_blocks; i++) {
mds->data_blocks[i].track_mode = data[data_offset];
mds->data_blocks[i].num_subchannels = data[data_offset + 1];
mds->data_blocks[i].adr_control = data[data_offset + 2];
mds->data_blocks[i].track_no = data[data_offset + 3];
mds->data_blocks[i].point = data[data_offset + 4];
mds->data_blocks[i].minute = data[data_offset + 9];
mds->data_blocks[i].second = data[data_offset + 10];
mds->data_blocks[i].frame = data[data_offset + 11];
memcpy(&mds->data_blocks[i].offset_index, data + data_offset + 12, 4);
memcpy(&mds->data_blocks[i].sector_size, data + data_offset + 16, 2);
mds->data_blocks[i].unknown = data[data_offset + 18];
memcpy(&mds->data_blocks[i].track_start_plba, data + data_offset + 36, 4);
memcpy(&mds->data_blocks[i].track_start_offset, data + data_offset + 40, 8);
memcpy(&mds->data_blocks[i].num_filenames, data + data_offset + 48, 4);
memcpy(&mds->data_blocks[i].offset_filename, data + data_offset + 52, 4);
data_offset += 80;
}
// Parse Index Blocks (assume starting from first track after A0-A2)
uint32_t index_offset = mds->data_blocks[3].offset_index;
mds->num_index_blocks = mds->num_data_blocks - mds->sessions[mds->num_sessions - 1].num_data_a0;
mds->index_blocks = malloc(mds->num_index_blocks * sizeof(IndexBlock));
for (int i = 0; i < mds->num_index_blocks; i++) {
memcpy(&mds->index_blocks[i].sectors_index0, data + index_offset, 4);
memcpy(&mds->index_blocks[i].sectors_index1, data + index_offset + 4, 4);
index_offset += 8;
}
// Parse Filename Blocks (assume from first track)
uint32_t filename_offset = mds->data_blocks[3].offset_filename;
mds->num_filename_blocks = mds->data_blocks[3].num_filenames;
mds->filename_blocks = malloc(mds->num_filename_blocks * sizeof(FilenameBlock));
for (int i = 0; i < mds->num_filename_blocks; i++) {
memcpy(&mds->filename_blocks[i].offset_string, data + filename_offset, 4);
uint32_t str_offset = mds->filename_blocks[i].offset_string;
int len = 0;
while (data[str_offset + len] != 0) len++;
mds->filename_blocks[i].filename = malloc(len + 1);
memcpy(mds->filename_blocks[i].filename, data + str_offset, len);
mds->filename_blocks[i].filename[len] = '\0';
filename_offset += 16;
}
// Read Errors
mds->read_errors = (mds->header.offset_read_errors == 0) ? "None" : "Present";
return mds;
}
void mds_print(MDS* mds) {
printf("Header:\n");
printf("- File ID: %s\n", mds->header.file_id);
printf("- Version: %02x,%02x\n", mds->header.version[0], mds->header.version[1]);
printf("- Media Type: %u\n", mds->header.media_type);
printf("- Number of sessions: %u\n", mds->header.num_sessions);
printf("- Unknown: %u\n", mds->header.unknown);
printf("- BCA Length: %u\n", mds->header.bca_length);
printf("- BCA Offset: %u\n", mds->header.bca_offset);
printf("- Disc Structures Offset: %u\n", mds->header.disc_structures_offset);
printf("- Offset to First Session Block: %u\n", mds->header.offset_first_session);
printf("- Offset to Read Errors: %u\n", mds->header.offset_read_errors);
printf("\nSession Blocks:\n");
for (int i = 0; i < mds->num_sessions; i++) {
printf(" Session %d:\n", i);
printf(" - Start Sector: %d\n", mds->sessions[i].start_sector);
printf(" - End Sector: %d\n", mds->sessions[i].end_sector);
printf(" - Session Number: %u\n", mds->sessions[i].session_num);
printf(" - Num Data Blocks (Any Point): %u\n", mds->sessions[i].num_data_any);
printf(" - Num Data Blocks (Point >= A0h): %u\n", mds->sessions[i].num_data_a0);
printf(" - First Track Number: %u\n", mds->sessions[i].first_track);
printf(" - Last Track Number: %u\n", mds->sessions[i].last_track);
printf(" - Offset to First Data Block: %u\n", mds->sessions[i].offset_first_data);
}
printf("\nData Blocks:\n");
for (int i = 0; i < mds->num_data_blocks; i++) {
printf(" Data Block %d:\n", i);
printf(" - Track Mode: %02x\n", mds->data_blocks[i].track_mode);
printf(" - Number of Subchannels: %u\n", mds->data_blocks[i].num_subchannels);
printf(" - ADR/Control: %02x\n", mds->data_blocks[i].adr_control);
printf(" - TrackNo: %u\n", mds->data_blocks[i].track_no);
printf(" - Point: %02x\n", mds->data_blocks[i].point);
printf(" - Minute: %u\n", mds->data_blocks[i].minute);
printf(" - Second: %u\n", mds->data_blocks[i].second);
printf(" - Frame: %u\n", mds->data_blocks[i].frame);
printf(" - Offset to Index Block: %u\n", mds->data_blocks[i].offset_index);
printf(" - Sector Size: %u\n", mds->data_blocks[i].sector_size);
printf(" - Unknown: %u\n", mds->data_blocks[i].unknown);
printf(" - Track Start Sector (PLBA): %u\n", mds->data_blocks[i].track_start_plba);
printf(" - Track Start Offset: %llu\n", mds->data_blocks[i].track_start_offset);
printf(" - Number of Filenames: %u\n", mds->data_blocks[i].num_filenames);
printf(" - Offset to Filename Block: %u\n", mds->data_blocks[i].offset_filename);
}
printf("\nIndex Blocks:\n");
for (int i = 0; i < mds->num_index_blocks; i++) {
printf(" Index Block %d:\n", i);
printf(" - Sectors Index 0: %u\n", mds->index_blocks[i].sectors_index0);
printf(" - Sectors Index 1: %u\n", mds->index_blocks[i].sectors_index1);
}
printf("\nFilename Blocks:\n");
for (int i = 0; i < mds->num_filename_blocks; i++) {
printf(" Filename Block %d:\n", i);
printf(" - Offset to Filename String: %u\n", mds->filename_blocks[i].offset_string);
printf(" - Filename: %s\n", mds->filename_blocks[i].filename);
}
printf("\nRead Errors: %s\n", mds->read_errors);
}
void mds_write(MDS* mds, const char* filepath) {
FILE* f = fopen(filepath, "wb");
if (f) {
fwrite(mds->data, 1, mds->data_size, f);
fclose(f);
printf("File written to %s\n", filepath);
}
}
void mds_free(MDS* mds) {
free(mds->sessions);
free(mds->data_blocks);
free(mds->index_blocks);
for (int i = 0; i < mds->num_filename_blocks; i++) {
free(mds->filename_blocks[i].filename);
}
free(mds->filename_blocks);
free(mds->data);
free(mds);
}
// Example usage
int main() {
MDS* mds = mds_open("example.mds");
if (mds) {
mds_print(mds);
mds_write(mds, "new.mds");
mds_free(mds);
}
return 0;
}