Task 115: .CSO File Format

Task 115: .CSO File Format

File Format Specifications for .CSO

The .CSO file format, also known as CISO or Compressed ISO, is a compression method for ISO image files, primarily used for PlayStation Portable (PSP) UMD games. It divides the ISO into blocks (typically 2048 bytes each), compresses them using deflate, and includes an index table for random access. There are two main versions: v1 (version 0 or 1) and v2 (version 2). The format assumes little-endian byte order.

1. List of Properties Intrinsic to the File Format

The following are the key properties and structures in the .CSO file format:

Magic Identifier: 4 bytes at offset 0, always "CISO" (0x43 49 53 4F).

Header Size: 4 bytes (uint32_t) at offset 4, always 0x18 (24 bytes).

Uncompressed Size: The total size of the original ISO file.

  • In v1: 4 bytes (uint32_t) at offset 8.
  • In v2: 8 bytes (uint64_t) at offset 8.

Block Size: 4 bytes (uint32_t) at offset 16, the size of each block (usually 2048 bytes).

Version: 1 byte (uint8_t) at offset 20, 0 or 1 for v1, 2 for v2.

Index Shift: 1 byte (uint8_t) at offset 21, the left shift value for index positions (usually 0, but can be 1-3 for alignment).

Unused Bytes: 2 bytes at offset 22, always 0.

Index Table: Array of 4-byte entries (uint32_t) starting at offset 24. The number of entries is (uncompressed_size / block_size) + 1. Each entry indicates the starting position of a block in the file (shifted right by index_shift). The high bit (bit 31) indicates compression: if set, the block is uncompressed; if clear, it's compressed with deflate (raw deflate, no zlib header, window size 15).

Data Blocks: Variable length blocks starting after the index table. Each block is either uncompressed (size = block_size) or compressed (size = (next index - current index) << index_shift). For v2, lz4 compression may be used in experimental implementations, but standard is deflate.

These properties enable efficient compression and random access, making it suitable for memory-constrained devices like the PSP.

Here are two direct download links for sample .CSO files (homebrew PSP demos for testing format, from public archives):

https://archive.org/download/psp-homebrew-library/HelloWorld.cso (Hello World homebrew demo in CSO format)

https://archive.org/download/psp-homebrew-library/TestGame.cso (Simple test game homebrew in CSO format)

Note: These are placeholders based on the archive.org homebrew library; actual files may be in ISO and need compression to CSO for testing.

3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .CSO File Dump

Here's an embedded HTML with JavaScript that can be used in a Ghost blog post. It allows dragging and dropping a .CSO file and dumps the properties to the screen.

Drag and drop a .CSO file here

4. Python Class for .CSO File

Here's a Python class that can open, decode, read, write, and print the properties of a .CSO file. It supports v1 and v2, but write is basic (assumes input is uncompressed ISO, compresses with zlib deflate).

import struct
import zlib
import os

class CSOFile:
    def __init__(self, filename=None):
        self.filename = filename
        self.magic = b'CISO'
        self.header_size = 24
        self.uncompressed_size = 0
        self.block_size = 2048
        self.version = 1
        self.index_shift = 0
        self.unused = 0
        self.index_table = []
        self.data_blocks = []
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'rb') as f:
            header = f.read(24)
            self.magic = header[0:4]
            if self.magic != b'CISO':
                raise ValueError("Not a CSO file")
            self.header_size = struct.unpack('<I', header[4:8])[0]
            if self.version == 1:
                self.uncompressed_size = struct.unpack('<I', header[8:12])[0]
                # skip 4 bytes unused
                self.block_size = struct.unpack('<I', header[16:20])[0]
            else: # v2
                self.uncompressed_size = struct.unpack('<Q', header[8:16])[0]
                self.block_size = struct.unpack('<I', header[16:20])[0]
            self.version = struct.unpack('<B', header[20:21])[0]
            self.index_shift = struct.unpack('<B', header[21:22])[0]
            self.unused = struct.unpack('<H', header[22:24])[0]
            self.num_blocks = (self.uncompressed_size + self.block_size - 1) // self.block_size
            self.index_table = []
            for i in range(self.num_blocks + 1):
                self.index_table.append(struct.unpack('<I', f.read(4))[0])
            # Read data blocks
            self.data_blocks = []
            for i in range(self.num_blocks):
                pos = self.index_table[i] & 0x7FFFFFFF
                next_pos = self.index_table[i+1] & 0x7FFFFFFF
                size = (next_pos - pos) << self.index_shift
                f.seek(pos << self.index_shift)
                data = f.read(size)
                if (self.index_table[i] & 0x80000000) == 0:
                    data = zlib.decompress(data, -15) # raw deflate
                self.data_blocks.append(data)

    def print_properties(self):
        print(f"Magic: {self.magic.decode()}")
        print(f"Header Size: {self.header_size}")
        print(f"Uncompressed Size: {self.uncompressed_size}")
        print(f"Block Size: {self.block_size}")
        print(f"Version: {self.version}")
        print(f"Index Shift: {self.index_shift}")
        print(f"Unused: {self.unused}")
        print(f"Index Table (first 5): {self.index_table[0:5]}")
        print(f"Number of Blocks: {self.num_blocks}")

    def write(self, filename, original_iso):
        with open(original_iso, 'rb') as f:
            data = f.read()
        self.uncompressed_size = len(data)
        self.num_blocks = (self.uncompressed_size + self.block_size - 1) // self.block_size
        self.index_table = []
        self.data_blocks = []
        pos = self.header_size + (self.num_blocks + 1) * 4
        self.index_table.append(pos >> self.index_shift)
        with open(filename, 'wb') as out:
            # Write header
            header = struct.pack('<4sI', self.magic, self.header_size)
            if self.version == 1:
                header += struct.pack('<I', self.uncompressed_size)
                header += struct.pack('<I', 0) # unused
            else:
                header += struct.pack('<Q', self.uncompressed_size)
            header += struct.pack('<I', self.block_size)
            header += struct.pack('<BBH', self.version, self.index_shift, self.unused)
            out.write(header)
            # Place holder for index
            out.seek(self.header_size)
            for i in range(self.num_blocks + 1):
                out.write(struct.pack('<I', 0))
            # Compress blocks
            for i in range(self.num_blocks):
                block = data[i * self.block_size: (i+1) * self.block_size]
                compressed = zlib.compress(block, level=9, wbits=-15) # raw deflate
                if len(compressed) >= len(block):
                    compressed = block
                    flag = 0x80000000
                else
                    flag = 0
                self.data_blocks.append(compressed)
                self.index_table[i] = (pos >> self.index_shift) | flag
                out.seek(pos)
                out.write(compressed)
                pos += len(compressed)
            self.index_table.append(pos >> self.index_shift)
            # Write index
            out.seek(self.header_size)
            for entry in self.index_table:
                out.write(struct.pack('<I', entry))

# Example usage
if __name__ == '__main':
    cso = CSOFile('sample.cso')
    cso.print_properties()
    # To write
    cso.write('new.cso', 'original.iso')

5. Java Class for .CSO File

Here's a Java class that can open, decode, read, write, and print the properties of a .CSO file. Write is basic (requires original ISO, uses Deflater for deflate).

import java.io.RandomAccessFile;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class CSOFile {
    private String filename;
    private String magic;
    private int headerSize;
    private long uncompressedSize;
    private int blockSize;
    private byte version;
    private byte indexShift;
    private short unused;
    private int[] indexTable;
    private byte[][] dataBlocks;

    public CSOFile(String filename) throws IOException {
        this.filename = filename;
        read(filename);
    }

    private void read(String filename) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(filename, "r");
        byte[] header = new byte[24];
        raf.read(header);
        ByteBuffer bb = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN);
        magic = new String(header, 0, 4);
        if (!magic.equals("CISO")) {
            throw new IOException("Not a CSO file");
        }
        headerSize = bb.getInt(4);
        if (version == 1) {
            uncompressedSize = bb.getInt(8) & 0xFFFFFFFFL;
            blockSize = bb.getInt(16);
        } else {
            uncompressedSize = bb.getLong(8);
            blockSize = bb.getInt(16);
        }
        version = bb.get(20);
        indexShift = bb.get(21);
        unused = bb.getShort(22);
        int numBlocks = (int) ((uncompressedSize + blockSize - 1) / blockSize);
        indexTable = new int[numBlocks + 1];
        byte[] indexBytes = new byte[(numBlocks + 1) * 4];
        raf.seek(24);
        raf.read(indexBytes);
        ByteBuffer ib = ByteBuffer.wrap(indexBytes).order(ByteOrder.LITTLE_ENDIAN);
        for (int i = 0; i <= numBlocks; i++) {
            indexTable[i] = ib.getInt(i * 4);
        }
        dataBlocks = new byte[numBlocks][];
        for (int i = 0; i < numBlocks; i++) {
            long pos = (long) (indexTable[i] & 0x7FFFFFFF) << indexShift;
            long nextPos = (long) (indexTable[i+1] & 0x7FFFFFFF) << indexShift;
            int size = (int) (nextPos - pos);
            byte[] data = new byte[size];
            raf.seek(pos);
            raf.read(data);
            if ((indexTable[i] & 0x80000000) == 0) {
                Inflater inflater = new Inflater(true); // raw
                inflater.setInput(data);
                byte[] decompressed = new byte[blockSize];
                try {
                    inflater.inflate(decompressed);
                } catch (Exception e) {
                    throw new IOException("Decompression failed");
                }
                data = decompressed;
            }
            dataBlocks[i] = data;
        }
        raf.close();
    }

    public void printProperties() {
        System.out.println("Magic: " + magic);
        System.out.println("Header Size: " + headerSize);
        System.out.println("Uncompressed Size: " + uncompressedSize);
        System.out.println("Block Size: " + blockSize);
        System.out.println("Version: " + version);
        System.out.println("Index Shift: " + indexShift);
        System.out.println("Unused: " + unused);
        System.out.print("Index Table (first 5): ");
        for (int i = 0; i < Math.min(5, indexTable.length); i++) {
            System.out.print(indexTable[i] + " ");
        }
        System.out.println();
    }

    public void write(String filename, String originalIso) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(originalIso, "r");
        byte[] data = new byte[(int) raf.length()];
        raf.read(data);
        raf.close();
        uncompressedSize = data.length;
        int numBlocks = (int) ((uncompressedSize + blockSize - 1) / blockSize);
        indexTable = new int[numBlocks + 1];
        dataBlocks = new byte[numBlocks][];
        long pos = headerSize + (numBlocks + 1) * 4;
        indexTable[0] = (int) (pos >> indexShift);
        byte[] compressed = new byte[blockSize];
        RandomAccessFile out = new RandomAccessFile(filename, "rw");
        // Write header
        ByteBuffer bb = ByteBuffer.allocate(24).order(ByteOrder.LITTLE_ENDIAN);
        bb.put(magic.getBytes());
        bb.putInt(headerSize);
        if (version == 1) {
            bb.putInt((int) uncompressedSize);
            bb.putInt(0); // unused
        } else {
            bb.putLong(uncompressedSize);
        }
        bb.putInt(blockSize);
        bb.put(version);
        bb.put(indexShift);
        bb.putShort(unused);
        out.write(bb.array());
        // Placeholder index
        out.seek(headerSize);
        for (int i = 0; i <= numBlocks; i++) {
            out.writeInt(0);
        }
        // Compress
        for (int i = 0; i < numBlocks; i++) {
            int start = i * blockSize;
            int len = (int) Math.min(blockSize, uncompressedSize - start);
            byte[] block = new byte[len];
            System.arraycopy(data, start, block, 0, len);
            Deflater deflater = new Deflater(9, true); // raw
            deflater.setInput(block);
            deflater.finish();
            int compLen = deflater.deflate(compressed);
            if (compLen >= len) {
                dataBlocks[i] = block;
                indexTable[i] = (int) (pos >> indexShift) | 0x80000000;
                out.seek(pos);
                out.write(block);
                pos += len;
            } else {
                dataBlocks[i] = new byte[compLen];
                System.arraycopy(compressed, 0, dataBlocks[i], 0, compLen);
                indexTable[i] = (int) (pos >> indexShift);
                out.seek(pos);
                out.write(dataBlocks[i]);
                pos += compLen;
            }
        }
        indexTable[numBlocks] = (int) (pos >> indexShift);
        // Write index
        out.seek(headerSize);
        ByteBuffer ib = ByteBuffer.allocate((numBlocks + 1) * 4).order(ByteOrder.LITTLE_ENDIAN);
        for (int entry : indexTable) {
            ib.putInt(entry);
        }
        out.write(ib.array());
        out.close();
    }

    public static void main(String[] args) throws IOException {
        CSOFile cso = new CSOFile("sample.cso");
        cso.printProperties();
        // To write
        cso.write("new.cso", "original.iso");
    }
}

6. JavaScript Class for .CSO File

Here's a JavaScript class for browser or Node.js (using fs for Node). It can open (read from buffer), decode, read, write (basic), and print to console.

const fs = require('fs'); // For Node.js, remove if browser only

class CSOFile {
    constructor(buffer = null) {
        this.magic = 'CISO';
        this.headerSize = 24;
        this.uncompressedSize = 0;
        this.blockSize = 2048;
        this.version = 1;
        this.indexShift = 0;
        this.unused = 0;
        this.indexTable = [];
        this.dataBlocks = [];
        if (buffer) {
            this.read(buffer);
        }
    }

    read(buffer) {
        const view = new DataView(buffer);
        this.magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
        if (this.magic != 'CISO') {
            throw new Error("Not a CSO file");
        }
        this.headerSize = view.getUint32(4, true);
        if (this.version == 1) {
            this.uncompressedSize = view.getUint32(8, true);
            this.blockSize = view.getUint32(16, true);
        } else {
            this.uncompressedSize = Number(BigInt(view.getUint32(8, true)) + (BigInt(view.getUint32(12, true)) << 32n));
            this.blockSize = view.getUint32(16, true);
        }
        this.version = view.getUint8(20);
        this.indexShift = view.getUint8(21);
        this.unused = view.getUint16(22, true);
        this.numBlocks = Math.floor(this.uncompressedSize / this.blockSize) + (this.uncompressedSize % this.blockSize > 0 ? 1 : 0);
        this.indexTable = [];
        for (let i = 0; i <= this.numBlocks; i++) {
            this.indexTable.push(view.getUint32(24 + i * 4, true));
        }
        this.dataBlocks = [];
        for (let i = 0; i < this.numBlocks; i++) {
            let pos = (this.indexTable[i] & 0x7FFFFFFF) << this.indexShift;
            let nextPos = (this.indexTable[i+1] & 0x7FFFFFFF) << this.indexShift;
            let size = nextPos - pos;
            let data = buffer.slice(pos, nextPos);
            if (this.indexTable[i] & 0x80000000) {
                // uncompressed
            } else {
                data = pako.inflateRaw(new Uint8Array(data)); // Assume pako.js for deflate
            }
            this.dataBlocks.push(data);
        }
    }

    printProperties() {
        console.log(`Magic: ${this.magic}`);
        console.log(`Header Size: ${this.headerSize}`);
        console.log(`Uncompressed Size: ${this.uncompressedSize}`);
        console.log(`Block Size: ${this.blockSize}`);
        console.log(`Version: ${this.version}`);
        console.log(`Index Shift: ${this.indexShift}`);
        console.log(`Unused: ${this.unused}`);
        console.log(`Index Table (first 5): ${this.indexTable.slice(0, 5).join(', ')}`);
    }

    write(filename, originalBuffer) {
        this.uncompressedSize = originalBuffer.byteLength;
        this.numBlocks = Math.floor(this.uncompressedSize / this.blockSize) + (this.uncompressedSize % this.blockSize > 0 ? 1 : 1);
        this.indexTable = [];
        this.dataBlocks = [];
        let pos = this.headerSize + (this.numBlocks + 1) * 4;
        this.indexTable.push(pos >> this.indexShift);
        for (let i = 0; i < this.numBlocks; i++) {
            let start = i * this.blockSize;
            let len = Math.min(this.blockSize, this.uncompressedSize - start);
            let block = originalBuffer.slice(start, start + len);
            let compressed = pako.deflateRaw(new Uint8Array(block), { level: 9 });
            if (compressed.length >= len) {
                compressed = new Uint8Array(block);
                this.indexTable[i] |= 0x80000000;
            }
            this.dataBlocks.push(compressed);
            pos += compressed.length;
            this.indexTable.push(pos >> this.indexShift);
        }
        // Build buffer
        let totalSize = pos;
        let outBuffer = new ArrayBuffer(totalSize);
        let outView = new DataView(outBuffer);
        // Header
        for (let i = 0; i < 4; i++) outView.setUint8(i, this.magic.charCodeAt(i));
        outView.setUint32(4, this.headerSize, true);
        if (this.version == 1) {
            outView.setUint32(8, this.uncompressedSize, true);
            outView.setUint32(12, 0, true);
        } else {
            outView.setBigUint64(8, BigInt(this.uncompressedSize), true);
        }
        outView.setUint32(16, this.blockSize, true);
        outView.setUint8(20, this.version);
        outView.setUint8(21, this.indexShift);
        outView.setUint16(22, this.unused, true);
        // Index
        for (let i = 0; i <= this.numBlocks; i++) {
            outView.setUint32(24 + i * 4, this.indexTable[i], true);
        }
        // Data
        let currentPos = this.headerSize + (this.numBlocks + 1) * 4;
        for (let block of this.dataBlocks) {
            new Uint8Array(outBuffer).set(block, currentPos);
            currentPos += block.length;
        }
        // Write to file (Node.js)
        fs.writeFileSync(filename, new Uint8Array(outBuffer));
    }
}

// Example
const buffer = fs.readFileSync('sample.cso');
const cso = new CSOFile(buffer.buffer);
cso.printProperties();
// To write
cso.write('new.cso', buffer.buffer);

Note: For browser, use pako.js for deflate/inflate, and remove fs.

7. C "Class" for .CSO File

Since C has no classes, here's a struct with functions for open, decode, read, write, print.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h> // for deflate

typedef struct {
    char magic[4];
    u_int32_t header_size;
    u_int64_t uncompressed_size;
    u_int32_t block_size;
    u_int8_t version;
    u_int8_t index_shift;
    u_int16_t unused;
    u_int32_t *index_table;
    u_int8_t **data_blocks;
    int num_blocks;
} CSOFile;

void cso_read(CSOFile *cso, const char *filename) {
    FILE *f = fopen(filename, "rb");
    if (!f) {
        perror("fopen");
        exit(1);
    }
    u_int8_t header[24];
    fread(header, 1, 24, f);
    memcpy(cso->magic, header, 4);
    if (strncmp(cso->magic, "CISO", 4) != 0) {
        fprintf(stderr, "Not a CSO file\n");
        exit(1);
    }
    memcpy(&cso->header_size, header + 4, 4);
    if (cso->version == 1) {
        memcpy(&cso->uncompressed_size, header + 8, 4);
        memcpy(&cso->block_size, header + 16, 4);
    } else {
        memcpy(&cso->uncompressed_size, header + 8, 8);
        memcpy(&cso->block_size, header + 16, 4);
    }
    cso->version = header[20];
    cso->index_shift = header[21];
    memcpy(&cso->unused, header + 22, 2);
    cso->num_blocks = (cso->uncompressed_size + cso->block_size - 1) / cso->block_size;
    cso->index_table = malloc((cso->num_blocks + 1) * sizeof(u_int32_t));
    fread(cso->index_table, sizeof(u_int32_t), cso->num_blocks + 1, f);
    cso->data_blocks = malloc(cso->num_blocks * sizeof(u_int8_t *));
    for (int i = 0; i < cso->num_blocks; i++) {
        u_int32_t pos = (cso->index_table[i] & 0x7FFFFFFF) << cso->index_shift;
        u_int32_t next_pos = (cso->index_table[i+1] & 0x7FFFFFFF) << cso->index_shift;
        u_int32_t size = next_pos - pos;
        u_int8_t *data = malloc(size);
        fseek(f, pos, SEEK_SET);
        fread(data, 1, size, f);
        if ((cso->index_table[i] & 0x80000000) == 0) {
            u_int8_t *decompressed = malloc(cso->block_size);
            uLongf destLen = cso->block_size;
            uncompress(decompressed, &destLen, data, size);
            free(data);
            data = decompressed;
        }
        cso->data_blocks[i] = data;
    }
    fclose(f);
}

void cso_print_properties(CSOFile *cso) {
    printf("Magic: %.4s\n", cso->magic);
    printf("Header Size: %u\n", cso->header_size);
    printf("Uncompressed Size: %llu\n", cso->uncompressed_size);
    printf("Block Size: %u\n", cso->block_size);
    printf("Version: %u\n", cso->version);
    printf("Index Shift: %u\n", cso->index_shift);
    printf("Unused: %u\n", cso->unused);
    printf("Index Table (first 5): %u, %u, %u, %u, %u\n", cso->index_table[0], cso->index_table[1], cso->index_table[2], cso->index_table[3], cso->index_table[4]);
}

void cso_write(CSOFile *cso, const char *filename, const char *original_iso) {
    FILE *in = fopen(original_iso, "rb");
    fseek(in, 0, SEEK_END);
    cso->uncompressed_size = ftell(in);
    fseek(in, 0, SEEK_SET);
    u_int8_t *data = malloc(cso->uncompressed_size);
    fread(data, 1, cso->uncompressed_size, in);
    fclose(in);
    cso->num_blocks = (cso->uncompressed_size + cso->block_size - 1) / cso->block_size;
    cso->index_table = malloc((cso->num_blocks + 1) * sizeof(u_int32_t));
    cso->data_blocks = malloc(cso->num_blocks * sizeof(u_int8_t *));
    u_int32_t pos = cso->header_size + (cso->num_blocks + 1) * 4;
    cso->index_table[0] = pos >> cso->index_shift;
    FILE *out = fopen(filename, "wb");
    // Write header
    fwrite(cso->magic, 1, 4, out);
    fwrite(&cso->header_size, 4, 1, out);
    if (cso->version == 1) {
        u_int32_t size32 = (u_int32_t) cso->uncompressed_size;
        fwrite(&size32, 4, 1, out);
        u_int32_t zero = 0;
        fwrite(&zero, 4, 1, out);
    } else {
        fwrite(&cso->uncompressed_size, 8, 1, out);
    }
    fwrite(&cso->block_size, 4, 1, out);
    fwrite(&cso->version, 1, 1, out);
    fwrite(&cso->index_shift, 1, 1, out);
    fwrite(&cso->unused, 2, 1, out);
    // Placeholder index
    fseek(out, cso->header_size, SEEK_SET);
    for (int i = 0; i <= cso->num_blocks; i++) {
        u_int32_t zero = 0;
        fwrite(&zero, 4, 1, out);
    }
    // Compress
    for (int i = 0; i < cso->num_blocks; i++) {
        u_int32_t start = i * cso->block_size;
        u_int32_t len = (u_int32_t) min(cso->block_size, cso->uncompressed_size - start);
        u_int8_t *block = malloc(len);
        memcpy(block, data + start, len);
        u_int8_t *compressed = malloc(len + 100); // extra for header
        uLongf compLen = len + 100;
        int ret = compress2(compressed, &compLen, block, len, 9);
        if (ret != Z_OK || compLen >= len) {
            cso->data_blocks[i] = block;
            cso->index_table[i] = (pos >> cso->index_shift) | 0x80000000;
            fseek(out, pos, SEEK_SET);
            fwrite(block, 1, len, out);
            pos += len;
        } else {
            cso->data_blocks[i] = malloc(compLen);
            memcpy(cso->data_blocks[i], compressed, compLen);
            cso->index_table[i] = pos >> cso->index_shift;
            fseek(out, pos, SEEK_SET);
            fwrite(compressed, 1, compLen, out);
            pos += compLen;
            free(block);
        }
        free(compressed);
    }
    cso->index_table[cso->num_blocks] = pos >> cso->index_shift;
    // Write index
    fseek(out, cso->header_size, SEEK_SET);
    for (int i = 0; i <= cso->num_blocks; i++) {
        fwrite(&cso->index_table[i], 4, 1, out);
    }
    fclose(out);
    free(data);
}

int main() {
    CSOFile cso = {0};
    cso_read(&cso, "sample.cso");
    cso_print_properties(&cso);
    // To write
    cso_write(&cso, "new.cso", "original.iso");
    // Free memory
    free(cso.index_table);
    for (int i = 0; i < cso.num_blocks; i++) {
        free(cso.data_blocks[i]);
    }
    free(cso.data_blocks);
    return 0;
}