Task 317: .ISO File Format
Task 317: .ISO File Format
1. File Format Specifications for the .ISO File Format
The .ISO file format refers to disc image files conforming to the ISO 9660 standard (also known as ECMA-119), which defines a file system for optical disc media such as CD-ROMs. This standard ensures cross-platform compatibility for reading data on CDs, DVDs, and similar media. The specification is maintained by the International Organization for Standardization (ISO) and is publicly available through documents like ECMA-119. Key aspects include a sector-based structure with a logical block size typically of 2048 bytes, support for hierarchical directories, and metadata stored in volume descriptors.
2. List of Properties Intrinsic to the File System
Based on the ISO 9660 specification, the following are key properties intrinsic to the file system, primarily derived from the Primary Volume Descriptor (PVD) located at sector 16 (byte offset 32768). These represent metadata about the volume, structure, and content organization. Properties are listed with their typical offsets (from the start of the PVD), sizes, and descriptions:
- System Identifier: Offset 8, 32 bytes (A-characters: A-Z, 0-9, space, underscore). Identifies the system that can act on the system area (sectors 0-15).
- Volume Identifier: Offset 40, 32 bytes (D-characters: A-Z, 0-9, space). The name or label of the volume.
- Volume Space Size: Offset 80, 8 bytes (32-bit integer in both little-endian and big-endian). Total number of logical blocks (sectors) in the volume.
- Volume Set Size: Offset 120, 4 bytes (16-bit integer in both endian). Number of volumes in the set (usually 1 for single-disc images).
- Volume Sequence Number: Offset 124, 4 bytes (16-bit integer in both endian). Sequence number of this volume in the set (usually 1).
- Logical Block Size: Offset 128, 4 bytes (16-bit integer in both endian). Size of each logical block in bytes (typically 2048).
- Path Table Size: Offset 132, 8 bytes (32-bit integer in both endian). Size in bytes of the path table, which summarizes the directory hierarchy.
- Location of Type-L Path Table: Offset 140, 4 bytes (32-bit little-endian). Logical block address (LBA) of the little-endian path table.
- Location of Optional Type-L Path Table: Offset 144, 4 bytes (32-bit little-endian). LBA of an optional little-endian path table (0 if none).
- Location of Type-M Path Table: Offset 148, 4 bytes (32-bit big-endian). LBA of the big-endian path table.
- Location of Optional Type-M Path Table: Offset 152, 4 bytes (32-bit big-endian). LBA of an optional big-endian path table (0 if none).
- Volume Set Identifier: Offset 190, 128 bytes (D-characters). Identifier for the entire volume set.
- Publisher Identifier: Offset 318, 128 bytes (A-characters). Name of the publisher or creator of the volume.
- Data Preparer Identifier: Offset 446, 128 bytes (A-characters). Name of the person or entity that prepared the data.
- Application Identifier: Offset 574, 128 bytes (A-characters). Identifier for the application used to create the volume.
- Copyright File Identifier: Offset 702, 37 bytes (A-characters). Name of a file containing copyright information (or empty).
- Abstract File Identifier: Offset 739, 37 bytes (A-characters). Name of a file containing an abstract (or empty).
- Bibliographic File Identifier: Offset 776, 37 bytes (A-characters). Name of a file containing bibliographic information (or empty).
- Volume Creation Date and Time: Offset 813, 17 bytes (ASCII: YYYYMMDDHHMMSSCC; followed by GMT offset byte). Date and time when the volume was created.
- Volume Modification Date and Time: Offset 830, 17 bytes (same format). Last modification date and time.
- Volume Expiration Date and Time: Offset 847, 17 bytes (same format). Date after which the volume is considered expired (often all zeros).
- Volume Effective Date and Time: Offset 864, 17 bytes (same format). Date from which the volume is effective (often all zeros).
- File Structure Version: Offset 881, 1 byte. Version of the file structure (typically 1).
These properties are trimmed of trailing spaces when displayed. Additional structures like the root directory record (embedded at offset 156 in the PVD) provide details on the root directory's location and attributes, but the above focus on volume-level intrinsics.
3. Two Direct Download Links for .ISO Files
Here are two direct download links for legitimate .ISO files (open-source operating system installation images):
- Ubuntu 24.04.3 Desktop AMD64: https://releases.ubuntu.com/24.04/ubuntu-24.04.3-desktop-amd64.iso
- Debian 13.1.0 AMD64 Netinstall: https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.1.0-amd64-netinst.iso
These are from official release servers and are safe for download.
4. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .ISO Property Dump
Below is an embeddable HTML snippet with JavaScript (suitable for a Ghost blog post or similar). It creates a drag-and-drop area where users can drop an .ISO file. The script reads the file as an ArrayBuffer, parses the Primary Volume Descriptor starting at byte 32768, extracts the properties listed in part 2, and dumps them to the screen. It assumes little-endian for multi-endian fields (common in practice) and handles basic validation.
5. Python Class for .ISO Handling
Below is a Python class that can open an .ISO file, decode and read the Primary Volume Descriptor, print the properties to console, and includes a basic write method to modify and save a property (e.g., Volume Identifier). It uses struct
for unpacking.
import struct
import os
class ISOHandler:
def __init__(self, filepath):
self.filepath = filepath
self.sector_size = 2048
self.pvd_offset = 16 * self.sector_size # 32768
self.properties = {}
def read_properties(self):
with open(self.filepath, 'rb') as f:
f.seek(self.pvd_offset)
pvd_data = f.read(self.sector_size)
# Validate PVD
type_code, identifier = struct.unpack_from('<B5s', pvd_data, 0)
if type_code != 1 or identifier != b'CD001':
raise ValueError("Invalid ISO file")
# Extract properties (little-endian where multi-endian)
self.properties = {
'System Identifier': self._get_string(pvd_data, 8, 32).strip(),
'Volume Identifier': self._get_string(pvd_data, 40, 32).strip(),
'Volume Space Size': struct.unpack_from('<I', pvd_data, 80)[0],
'Volume Set Size': struct.unpack_from('<H', pvd_data, 120)[0],
'Volume Sequence Number': struct.unpack_from('<H', pvd_data, 124)[0],
'Logical Block Size': struct.unpack_from('<H', pvd_data, 128)[0],
'Path Table Size': struct.unpack_from('<I', pvd_data, 132)[0],
'Location of Type-L Path Table': struct.unpack_from('<I', pvd_data, 140)[0],
'Location of Optional Type-L Path Table': struct.unpack_from('<I', pvd_data, 144)[0],
'Location of Type-M Path Table': struct.unpack_from('>I', pvd_data, 148)[0], # BE
'Location of Optional Type-M Path Table': struct.unpack_from('>I', pvd_data, 152)[0], # BE
'Volume Set Identifier': self._get_string(pvd_data, 190, 128).strip(),
'Publisher Identifier': self._get_string(pvd_data, 318, 128).strip(),
'Data Preparer Identifier': self._get_string(pvd_data, 446, 128).strip(),
'Application Identifier': self._get_string(pvd_data, 574, 128).strip(),
'Copyright File Identifier': self._get_string(pvd_data, 702, 37).strip(),
'Abstract File Identifier': self._get_string(pvd_data, 739, 37).strip(),
'Bibliographic File Identifier': self._get_string(pvd_data, 776, 37).strip(),
'Volume Creation Date and Time': self._get_date(pvd_data, 813),
'Volume Modification Date and Time': self._get_date(pvd_data, 830),
'Volume Expiration Date and Time': self._get_date(pvd_data, 847),
'Volume Effective Date and Time': self._get_date(pvd_data, 864),
'File Structure Version': struct.unpack_from('<B', pvd_data, 881)[0]
}
def print_properties(self):
if not self.properties:
self.read_properties()
print("ISO Properties:")
for key, value in self.properties.items():
print(f"{key}: {value}")
def write_property(self, key, new_value):
if key not in self.properties:
raise ValueError("Invalid property key")
with open(self.filepath, 'r+b') as f:
f.seek(self.pvd_offset)
pvd_data = bytearray(f.read(self.sector_size))
# Example for Volume Identifier (adjust offsets/sizes for other keys)
if key == 'Volume Identifier':
new_value = new_value.ljust(32, ' ').encode('ascii')[:32]
pvd_data[40:72] = new_value
# Add similar for other string/int properties...
f.seek(self.pvd_offset)
f.write(pvd_data)
print(f"Updated {key} to {new_value}")
def _get_string(self, data, offset, length):
return data[offset:offset + length].decode('ascii', errors='ignore')
def _get_date(self, data, offset):
date_str = self._get_string(data, offset, 16)
gmt_offset = struct.unpack_from('<b', data, offset + 16)[0] * 15 # Minutes
return f"{date_str[0:4]}-{date_str[4:6]}-{date_str[6:8]} {date_str[8:10]}:{date_str[10:12]}:{date_str[12:14]}.{date_str[14:16]} GMT{'+' if gmt_offset >= 0 else ''}{gmt_offset}"
# Example usage:
# handler = ISOHandler('example.iso')
# handler.print_properties()
# handler.write_property('Volume Identifier', 'NewVolumeName')
6. Java Class for .ISO Handling
Below is a Java class that can open an .ISO file, decode and read the PVD, print properties to console, and includes a write method to modify a property (e.g., Volume Identifier) and save.
import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class ISOHandler {
private String filepath;
private int sectorSize = 2048;
private long pvdOffset = 16L * sectorSize; // 32768
private ByteBuffer buffer;
public ISOHandler(String filepath) {
this.filepath = filepath;
}
public void readProperties() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
raf.seek(pvdOffset);
byte[] pvdData = new byte[sectorSize];
raf.readFully(pvdData);
buffer = ByteBuffer.wrap(pvdData).order(ByteOrder.LITTLE_ENDIAN);
// Validate
int type = buffer.get(0) & 0xFF;
String id = getString(1, 5);
if (type != 1 || !id.equals("CD001")) {
throw new IllegalArgumentException("Invalid ISO file");
}
}
}
public void printProperties() throws IOException {
if (buffer == null) readProperties();
System.out.println("ISO Properties:");
System.out.println("System Identifier: " + getString(8, 32).trim());
System.out.println("Volume Identifier: " + getString(40, 32).trim());
System.out.println("Volume Space Size: " + buffer.getInt(80));
System.out.println("Volume Set Size: " + buffer.getShort(120));
System.out.println("Volume Sequence Number: " + buffer.getShort(124));
System.out.println("Logical Block Size: " + buffer.getShort(128));
System.out.println("Path Table Size: " + buffer.getInt(132));
System.out.println("Location of Type-L Path Table: " + buffer.getInt(140));
System.out.println("Location of Optional Type-L Path Table: " + buffer.getInt(144));
buffer.order(ByteOrder.BIG_ENDIAN); // Switch for BE
System.out.println("Location of Type-M Path Table: " + buffer.getInt(148));
System.out.println("Location of Optional Type-M Path Table: " + buffer.getInt(152));
buffer.order(ByteOrder.LITTLE_ENDIAN); // Reset
System.out.println("Volume Set Identifier: " + getString(190, 128).trim());
System.out.println("Publisher Identifier: " + getString(318, 128).trim());
System.out.println("Data Preparer Identifier: " + getString(446, 128).trim());
System.out.println("Application Identifier: " + getString(574, 128).trim());
System.out.println("Copyright File Identifier: " + getString(702, 37).trim());
System.out.println("Abstract File Identifier: " + getString(739, 37).trim());
System.out.println("Bibliographic File Identifier: " + getString(776, 37).trim());
System.out.println("Volume Creation Date and Time: " + getDate(813));
System.out.println("Volume Modification Date and Time: " + getDate(830));
System.out.println("Volume Expiration Date and Time: " + getDate(847));
System.out.println("Volume Effective Date and Time: " + getDate(864));
System.out.println("File Structure Version: " + (buffer.get(881) & 0xFF));
}
public void writeProperty(String key, String newValue) throws IOException {
if (buffer == null) readProperties();
try (FileChannel channel = FileChannel.open(Paths.get(filepath), StandardOpenOption.READ, StandardOpenOption.WRITE)) {
channel.position(pvdOffset);
// Example for Volume Identifier
if (key.equals("Volume Identifier")) {
byte[] bytes = newValue.getBytes("ASCII");
buffer.position(40);
buffer.put(bytes, 0, Math.min(32, bytes.length));
for (int i = bytes.length; i < 32; i++) buffer.put((byte) ' ');
}
// Add similar for others...
buffer.rewind();
channel.write(buffer);
}
System.out.println("Updated " + key + " to " + newValue);
}
private String getString(int offset, int length) {
byte[] bytes = new byte[length];
buffer.position(offset);
buffer.get(bytes);
return new String(bytes);
}
private String getDate(int offset) {
String dateStr = getString(offset, 16);
byte gmtOffsetByte = buffer.get(offset + 16);
int gmtOffset = (gmtOffsetByte << 24 >> 24) * 15; // Sign extend
return String.format("%s-%s-%s %s:%s:%s.%s GMT%s%d",
dateStr.substring(0,4), dateStr.substring(4,6), dateStr.substring(6,8),
dateStr.substring(8,10), dateStr.substring(10,12), dateStr.substring(12,14), dateStr.substring(14,16),
(gmtOffset >= 0 ? "+" : ""), gmtOffset);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// ISOHandler handler = new ISOHandler("example.iso");
// handler.printProperties();
// handler.writeProperty("Volume Identifier", "NewVolumeName");
// }
}
7. JavaScript Class for .ISO Handling
Below is a JavaScript class (Node.js compatible, requires fs
module) that can open an .ISO file, decode and read the PVD, print properties to console, and includes a write method to modify a property and save.
const fs = require('fs');
class ISOHandler {
constructor(filepath) {
this.filepath = filepath;
this.sectorSize = 2048;
this.pvdOffset = 16 * this.sectorSize; // 32768
this.buffer = null;
}
readProperties() {
const data = fs.readFileSync(this.filepath);
this.buffer = new DataView(data.buffer, data.byteOffset, data.byteLength);
// Validate
const type = this.buffer.getUint8(this.pvdOffset);
const id = this.getString(this.pvdOffset + 1, 5);
if (type !== 1 || id !== 'CD001') {
throw new Error('Invalid ISO file');
}
}
printProperties() {
if (!this.buffer) this.readProperties();
console.log('ISO Properties:');
console.log('System Identifier:', this.getString(this.pvdOffset + 8, 32).trim());
console.log('Volume Identifier:', this.getString(this.pvdOffset + 40, 32).trim());
console.log('Volume Space Size:', this.buffer.getUint32(this.pvdOffset + 80, true));
console.log('Volume Set Size:', this.buffer.getUint16(this.pvdOffset + 120, true));
console.log('Volume Sequence Number:', this.buffer.getUint16(this.pvdOffset + 124, true));
console.log('Logical Block Size:', this.buffer.getUint16(this.pvdOffset + 128, true));
console.log('Path Table Size:', this.buffer.getUint32(this.pvdOffset + 132, true));
console.log('Location of Type-L Path Table:', this.buffer.getUint32(this.pvdOffset + 140, true));
console.log('Location of Optional Type-L Path Table:', this.buffer.getUint32(this.pvdOffset + 144, true));
console.log('Location of Type-M Path Table:', this.buffer.getUint32(this.pvdOffset + 148, false)); // BE
console.log('Location of Optional Type-M Path Table:', this.buffer.getUint32(this.pvdOffset + 152, false)); // BE
console.log('Volume Set Identifier:', this.getString(this.pvdOffset + 190, 128).trim());
console.log('Publisher Identifier:', this.getString(this.pvdOffset + 318, 128).trim());
console.log('Data Preparer Identifier:', this.getString(this.pvdOffset + 446, 128).trim());
console.log('Application Identifier:', this.getString(this.pvdOffset + 574, 128).trim());
console.log('Copyright File Identifier:', this.getString(this.pvdOffset + 702, 37).trim());
console.log('Abstract File Identifier:', this.getString(this.pvdOffset + 739, 37).trim());
console.log('Bibliographic File Identifier:', this.getString(this.pvdOffset + 776, 37).trim());
console.log('Volume Creation Date and Time:', this.getDate(this.pvdOffset + 813));
console.log('Volume Modification Date and Time:', this.getDate(this.pvdOffset + 830));
console.log('Volume Expiration Date and Time:', this.getDate(this.pvdOffset + 847));
console.log('Volume Effective Date and Time:', this.getDate(this.pvdOffset + 864));
console.log('File Structure Version:', this.buffer.getUint8(this.pvdOffset + 881));
}
writeProperty(key, newValue) {
if (!this.buffer) this.readProperties();
const fileData = fs.readFileSync(this.filepath);
const pvdData = new Uint8Array(fileData.subarray(this.pvdOffset, this.pvdOffset + this.sectorSize));
// Example for Volume Identifier
if (key === 'Volume Identifier') {
const encoder = new TextEncoder();
const bytes = encoder.encode(newValue.padEnd(32, ' ')).slice(0, 32);
pvdData.set(bytes, 40);
}
// Add similar for others...
fileData.set(pvdData, this.pvdOffset);
fs.writeFileSync(this.filepath, fileData);
console.log(`Updated ${key} to ${newValue}`);
}
getString(offset, length) {
let str = '';
for (let i = 0; i < length; i++) {
str += String.fromCharCode(this.buffer.getUint8(offset + i));
}
return str;
}
getDate(offset) {
const dateStr = this.getString(offset, 16);
const gmtOffset = this.buffer.getInt8(offset + 16) * 15;
return `${dateStr.slice(0,4)}-${dateStr.slice(4,6)}-${dateStr.slice(6,8)} ${dateStr.slice(8,10)}:${dateStr.slice(10,12)}:${dateStr.slice(12,14)}.${dateStr.slice(14,16)} GMT${gmtOffset >= 0 ? '+' : ''}${gmtOffset}`;
}
}
// Example usage:
// const handler = new ISOHandler('example.iso');
// handler.printProperties();
// handler.writeProperty('Volume Identifier', 'NewVolumeName');
8. C Class for .ISO Handling
Below is a C++ class that can open an .ISO file, decode and read the PVD, print properties to console, and includes a write method to modify a property and save. Compile with g++ -o iso_handler iso_handler.cpp
.
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdint>
#include <iomanip>
class ISOHandler {
private:
std::string filepath;
const int sector_size = 2048;
const long pvd_offset = 16 * sector_size; // 32768
uint8_t pvd_data[2048];
std::string get_string(long offset, int length) {
std::string str(reinterpret_cast<char*>(pvd_data + offset), length);
return str;
}
std::string get_date(long offset) {
std::string date_str = get_string(offset, 16);
int8_t gmt_offset_byte = static_cast<int8_t>(pvd_data[offset + 16]);
int gmt_offset = gmt_offset_byte * 15;
std::stringstream ss;
ss << date_str.substr(0,4) << "-" << date_str.substr(4,2) << "-" << date_str.substr(6,2) << " "
<< date_str.substr(8,2) << ":" << date_str.substr(10,2) << ":" << date_str.substr(12,2) << "."
<< date_str.substr(14,2) << " GMT" << (gmt_offset >= 0 ? "+" : "") << gmt_offset;
return ss.str();
}
uint32_t get_uint32_le(long offset) {
return *reinterpret_cast<uint32_t*>(pvd_data + offset);
}
uint32_t get_uint32_be(long offset) {
uint32_t val;
memcpy(&val, pvd_data + offset, 4);
return __builtin_bswap32(val);
}
uint16_t get_uint16_le(long offset) {
return *reinterpret_cast<uint16_t*>(pvd_data + offset);
}
public:
ISOHandler(const std::string& fp) : filepath(fp) {}
void read_properties() {
std::ifstream file(filepath, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file");
}
file.seekg(pvd_offset);
file.read(reinterpret_cast<char*>(pvd_data), sector_size);
file.close();
// Validate
uint8_t type = pvd_data[0];
std::string id = get_string(1, 5);
if (type != 1 || id != "CD001") {
throw std::runtime_error("Invalid ISO file");
}
}
void print_properties() {
read_properties(); // Ensure read
std::cout << "ISO Properties:" << std::endl;
std::cout << "System Identifier: " << get_string(8, 32) << std::endl;
std::cout << "Volume Identifier: " << get_string(40, 32) << std::endl;
std::cout << "Volume Space Size: " << get_uint32_le(80) << std::endl;
std::cout << "Volume Set Size: " << get_uint16_le(120) << std::endl;
std::cout << "Volume Sequence Number: " << get_uint16_le(124) << std::endl;
std::cout << "Logical Block Size: " << get_uint16_le(128) << std::endl;
std::cout << "Path Table Size: " << get_uint32_le(132) << std::endl;
std::cout << "Location of Type-L Path Table: " << get_uint32_le(140) << std::endl;
std::cout << "Location of Optional Type-L Path Table: " << get_uint32_le(144) << std::endl;
std::cout << "Location of Type-M Path Table: " << get_uint32_be(148) << std::endl;
std::cout << "Location of Optional Type-M Path Table: " << get_uint32_be(152) << std::endl;
std::cout << "Volume Set Identifier: " << get_string(190, 128) << std::endl;
std::cout << "Publisher Identifier: " << get_string(318, 128) << std::endl;
std::cout << "Data Preparer Identifier: " << get_string(446, 128) << std::endl;
std::cout << "Application Identifier: " << get_string(574, 128) << std::endl;
std::cout << "Copyright File Identifier: " << get_string(702, 37) << std::endl;
std::cout << "Abstract File Identifier: " << get_string(739, 37) << std::endl;
std::cout << "Bibliographic File Identifier: " << get_string(776, 37) << std::endl;
std::cout << "Volume Creation Date and Time: " << get_date(813) << std::endl;
std::cout << "Volume Modification Date and Time: " << get_date(830) << std::endl;
std::cout << "Volume Expiration Date and Time: " << get_date(847) << std::endl;
std::cout << "Volume Effective Date and Time: " << get_date(864) << std::endl;
std::cout << "File Structure Version: " << static_cast<int>(pvd_data[881]) << std::endl;
}
void write_property(const std::string& key, const std::string& new_value) {
read_properties(); // Ensure read
if (key == "Volume Identifier") {
std::string padded = new_value;
padded.resize(32, ' ');
memcpy(pvd_data + 40, padded.c_str(), 32);
}
// Add similar for others...
std::fstream file(filepath, std::ios::in | std::ios::out | std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file for writing");
}
file.seekp(pvd_offset);
file.write(reinterpret_cast<char*>(pvd_data), sector_size);
file.close();
std::cout << "Updated " << key << " to " << new_value << std::endl;
}
};
// Example usage:
// int main() {
// ISOHandler handler("example.iso");
// handler.print_properties();
// handler.write_property("Volume Identifier", "NewVolumeName");
// return 0;
// }