Task 031: .ANI File Format

Task 031: .ANI File Format

1. List of Properties for .ANI File Format

The .ANI file format is a RIFF-based container for animated cursors in Windows. It is not intrinsic to any specific file system (e.g., NTFS or FAT) but is a file structure format. The "properties" refer to the structural elements, chunks, and fields within the file. Based on the specifications, here is a comprehensive list of all intrinsic properties (fields and chunks):

RIFF Header (8 bytes + type):

  • Signature: 4 bytes ('RIFF')
  • Size: 4 bytes (little-endian uint32, size of the entire file minus 8 bytes)
  • Type: 4 bytes ('ACON')

anih Chunk (animation header, required, typically 44 bytes including chunk header):

  • Chunk ID: 4 bytes ('anih')
  • Chunk Size: 4 bytes (little-endian uint32, typically 36)
  • cbSize: 4 bytes (little-endian uint32, size of the anih structure, always 36)
  • cFrames: 4 bytes (little-endian uint32, number of unique frames/icons)
  • cSteps: 4 bytes (little-endian uint32, number of steps in the animation cycle)
  • cx: 4 bytes (little-endian uint32, width in pixels)
  • cy: 4 bytes (little-endian uint32, height in pixels)
  • cBitCount: 4 bytes (little-endian uint32, bits per pixel)
  • cPlanes: 4 bytes (little-endian uint32, number of color planes, typically 1)
  • JifRate: 4 bytes (little-endian uint32, default frame rate in jiffies, where 1 jiffy = 1/60 second)
  • Flags: 4 bytes (little-endian uint32, bit flags; bit 0: icon data present, bit 1: sequence data present)

INAM Chunk (optional, variable size):

  • Chunk ID: 4 bytes ('INAM')
  • Chunk Size: 4 bytes (little-endian uint32, length of the name string)
  • Name: Variable bytes (ASCII string, title of the animation)

IART Chunk (optional, variable size):

  • Chunk ID: 4 bytes ('IART')
  • Chunk Size: 4 bytes (little-endian uint32, length of the author string)
  • Author: Variable bytes (ASCII string, author of the animation)

rate Chunk (optional, variable size):

  • Chunk ID: 4 bytes ('rate')
  • Chunk Size: 4 bytes (little-endian uint32, 4 * cSteps)
  • Rates: Array of 4-byte little-endian uint32 (one per step, frame display rate in jiffies; if absent, use JifRate)

seq Chunk (optional, variable size; note the trailing space in ID):

  • Chunk ID: 4 bytes ('seq ')
  • Chunk Size: 4 bytes (little-endian uint32, 4 * cSteps)
  • Sequence: Array of 4-byte little-endian uint32 (one per step, frame indices referencing the order of frames; if absent, use sequential order)

LIST Chunk (required, variable size, contains frames):

  • Chunk ID: 4 bytes ('LIST')
  • Chunk Size: 4 bytes (little-endian uint32, size of the list minus 8 bytes)
  • List Type: 4 bytes ('fram')
  • Subchunks: Multiple 'icon' chunks
  • For each icon chunk (variable size):
  • Chunk ID: 4 bytes ('icon')
  • Chunk Size: 4 bytes (little-endian uint32, size of the icon data)
  • Icon Data: Variable bytes (complete .ICO or .CUR format data for the frame, including its own header)

Chunks can appear in any order after the RIFF header, but typically anih comes first, followed by optional metadata, rate/seq, then the LIST fram. All sizes are even-padded if odd. The format supports transparency and multiple color depths.

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

.ANI Properties Dumper
Drag and drop a .ANI file here

4. Python Class for .ANI Handling

import struct
import os

class ANIFile:
    def __init__(self, filename):
        self.filename = filename
        self.riff_sig = b''
        self.riff_size = 0
        self.riff_type = b''
        self.anih = {}
        self.inam = ''
        self.iart = ''
        self.rate = []
        self.seq = []
        self.frames = []  # List of bytes for each icon data
        self.read()

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        pos = 0
        self.riff_sig = data[pos:pos+4]; pos += 4
        self.riff_size = struct.unpack('<I', data[pos:pos+4])[0]; pos += 4
        self.riff_type = data[pos:pos+4]; pos += 4

        while pos < len(data):
            chunk_id = data[pos:pos+4]; pos += 4
            chunk_size = struct.unpack('<I', data[pos:pos+4])[0]; pos += 4
            chunk_data = data[pos:pos+chunk_size]
            if chunk_id == b'anih':
                self.anih['cbSize'] = struct.unpack('<I', chunk_data[0:4])[0]
                self.anih['cFrames'] = struct.unpack('<I', chunk_data[4:8])[0]
                self.anih['cSteps'] = struct.unpack('<I', chunk_data[8:12])[0]
                self.anih['cx'] = struct.unpack('<I', chunk_data[12:16])[0]
                self.anih['cy'] = struct.unpack('<I', chunk_data[16:20])[0]
                self.anih['cBitCount'] = struct.unpack('<I', chunk_data[20:24])[0]
                self.anih['cPlanes'] = struct.unpack('<I', chunk_data[24:28])[0]
                self.anih['JifRate'] = struct.unpack('<I', chunk_data[28:32])[0]
                self.anih['flags'] = struct.unpack('<I', chunk_data[32:36])[0]
            elif chunk_id == b'INAM':
                self.inam = chunk_data.decode('ascii').rstrip('\x00')
            elif chunk_id == b'IART':
                self.iart = chunk_data.decode('ascii').rstrip('\x00')
            elif chunk_id == b'rate':
                self.rate = [struct.unpack('<I', chunk_data[i:i+4])[0] for i in range(0, chunk_size, 4)]
            elif chunk_id == b'seq ':
                self.seq = [struct.unpack('<I', chunk_data[i:i+4])[0] for i in range(0, chunk_size, 4)]
            elif chunk_id == b'LIST':
                list_type = chunk_data[0:4]
                if list_type == b'fram':
                    sub_pos = 4
                    while sub_pos < chunk_size:
                        sub_id = chunk_data[sub_pos:sub_pos+4]; sub_pos += 4
                        sub_size = struct.unpack('<I', chunk_data[sub_pos:sub_pos+4])[0]; sub_pos += 4
                        if sub_id == b'icon':
                            self.frames.append(chunk_data[sub_pos:sub_pos+sub_size])
                        sub_pos += sub_size
                        if sub_size % 2 != 0: sub_pos += 1
            pos += chunk_size
            if chunk_size % 2 != 0: pos += 1

    def print_properties(self):
        print('RIFF Signature:', self.riff_sig.decode())
        print('RIFF Size:', self.riff_size)
        print('RIFF Type:', self.riff_type.decode())
        print('anih:', self.anih)
        print('INAM:', self.inam)
        print('IART:', self.iart)
        print('rate:', self.rate)
        print('seq:', self.seq)
        print('Number of frames:', len(self.frames))

    def write(self, new_filename):
        data = b'RIFF' + struct.pack('<I', self.riff_size) + b'ACON'
        # anih
        anih_data = struct.pack('<9I', self.anih['cbSize'], self.anih['cFrames'], self.anih['cSteps'], self.anih['cx'], self.anih['cy'],
                                self.anih['cBitCount'], self.anih['cPlanes'], self.anih['JifRate'], self.anih['flags'])
        data += b'anih' + struct.pack('<I', len(anih_data)) + anih_data
        # INAM
        if self.inam:
            inam_data = self.inam.encode('ascii') + b'\x00'
            data += b'INAM' + struct.pack('<I', len(inam_data)) + inam_data
        # IART
        if self.iart:
            iart_data = self.iart.encode('ascii') + b'\x00'
            data += b'IART' + struct.pack('<I', len(iart_data)) + iart_data
        # rate
        if self.rate:
            rate_data = b''.join(struct.pack('<I', r) for r in self.rate)
            data += b'rate' + struct.pack('<I', len(rate_data)) + rate_data
        # seq
        if self.seq:
            seq_data = b''.join(struct.pack('<I', s) for s in self.seq)
            data += b'seq ' + struct.pack('<I', len(seq_data)) + seq_data
        # LIST fram
        fram_data = b'fram'
        for frame in self.frames:
            fram_data += b'icon' + struct.pack('<I', len(frame)) + frame
        list_size = len(fram_data)
        data += b'LIST' + struct.pack('<I', list_size) + fram_data
        # Update riff_size
        new_riff_size = len(data) - 8
        data = data[:4] + struct.pack('<I', new_riff_size) + data[8:]
        with open(new_filename, 'wb') as f:
            f.write(data)

# Example usage:
# ani = ANIFile('input.ani')
# ani.print_properties()
# ani.write('output.ani')

5. Java Class for .ANI Handling

import java.io.*;
import java.nio.*;
import java.util.*;

public class ANIFile {
    private String filename;
    private byte[] riffSig = new byte[4];
    private int riffSize;
    private byte[] riffType = new byte[4];
    private Map<String, Integer> anih = new HashMap<>();
    private String inam = "";
    private String iart = "";
    private List<Integer> rate = new ArrayList<>();
    private List<Integer> seq = new ArrayList<>();
    private List<byte[]> frames = new ArrayList<>();

    public ANIFile(String filename) {
        this.filename = filename;
        read();
    }

    private void read() {
        try (RandomAccessFile file = new RandomAccessFile(filename, "r")) {
            ByteBuffer buffer = ByteBuffer.allocate((int) file.length()).order(ByteOrder.LITTLE_ENDIAN);
            file.getChannel().read(buffer);
            buffer.flip();
            int pos = 0;

            buffer.position(pos); buffer.get(riffSig); pos += 4;
            riffSize = buffer.getInt(pos); pos += 4;
            buffer.position(pos); buffer.get(riffType); pos += 4;

            while (pos < buffer.capacity()) {
                byte[] chunkId = new byte[4];
                buffer.position(pos); buffer.get(chunkId); pos += 4;
                int chunkSize = buffer.getInt(pos); pos += 4;
                String id = new String(chunkId);

                if (id.equals("anih")) {
                    anih.put("cbSize", buffer.getInt(pos)); pos += 4;
                    anih.put("cFrames", buffer.getInt(pos)); pos += 4;
                    anih.put("cSteps", buffer.getInt(pos)); pos += 4;
                    anih.put("cx", buffer.getInt(pos)); pos += 4;
                    anih.put("cy", buffer.getInt(pos)); pos += 4;
                    anih.put("cBitCount", buffer.getInt(pos)); pos += 4;
                    anih.put("cPlanes", buffer.getInt(pos)); pos += 4;
                    anih.put("JifRate", buffer.getInt(pos)); pos += 4;
                    anih.put("flags", buffer.getInt(pos)); pos += 4;
                } else if (id.equals("INAM")) {
                    byte[] strBytes = new byte[chunkSize];
                    buffer.position(pos); buffer.get(strBytes);
                    inam = new String(strBytes).trim();
                    pos += chunkSize;
                } else if (id.equals("IART")) {
                    byte[] strBytes = new byte[chunkSize];
                    buffer.position(pos); buffer.get(strBytes);
                    iart = new String(strBytes).trim();
                    pos += chunkSize;
                } else if (id.equals("rate")) {
                    for (int i = 0; i < chunkSize / 4; i++) {
                        rate.add(buffer.getInt(pos)); pos += 4;
                    }
                } else if (id.equals("seq ")) {
                    for (int i = 0; i < chunkSize / 4; i++) {
                        seq.add(buffer.getInt(pos)); pos += 4;
                    }
                } else if (id.equals("LIST")) {
                    byte[] listType = new byte[4];
                    buffer.position(pos); buffer.get(listType); pos += 4;
                    if (new String(listType).equals("fram")) {
                        int subPos = pos;
                        int listEnd = pos + chunkSize - 4;
                        while (subPos < listEnd) {
                            byte[] subId = new byte[4];
                            buffer.position(subPos); buffer.get(subId); subPos += 4;
                            int subSize = buffer.getInt(subPos); subPos += 4;
                            if (new String(subId).equals("icon")) {
                                byte[] frameData = new byte[subSize];
                                buffer.position(subPos); buffer.get(frameData);
                                frames.add(frameData);
                            }
                            subPos += subSize;
                            if (subSize % 2 != 0) subPos++;
                        }
                        pos = subPos;
                    } else {
                        pos += chunkSize;
                    }
                } else {
                    pos += chunkSize;
                }
                if (chunkSize % 2 != 0) pos++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void printProperties() {
        System.out.println("RIFF Signature: " + new String(riffSig));
        System.out.println("RIFF Size: " + riffSize);
        System.out.println("RIFF Type: " + new String(riffType));
        System.out.println("anih: " + anih);
        System.out.println("INAM: " + inam);
        System.out.println("IART: " + iart);
        System.out.println("rate: " + rate);
        System.out.println("seq: " + seq);
        System.out.println("Number of frames: " + frames.size());
    }

    public void write(String newFilename) {
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.LITTLE_ENDIAN); // Arbitrary large size

            buffer.put(riffSig);
            buffer.putInt(riffSize); // Placeholder, update later
            buffer.put(riffType);

            // anih
            buffer.put("anih".getBytes());
            buffer.putInt(36);
            buffer.putInt(anih.get("cbSize"));
            buffer.putInt(anih.get("cFrames"));
            buffer.putInt(anih.get("cSteps"));
            buffer.putInt(anih.get("cx"));
            buffer.putInt(anih.get("cy"));
            buffer.putInt(anih.get("cBitCount"));
            buffer.putInt(anih.get("cPlanes"));
            buffer.putInt(anih.get("JifRate"));
            buffer.putInt(anih.get("flags"));

            // INAM
            if (!inam.isEmpty()) {
                byte[] inamData = (inam + "\0").getBytes();
                buffer.put("INAM".getBytes());
                buffer.putInt(inamData.length);
                buffer.put(inamData);
            }

            // IART
            if (!iart.isEmpty()) {
                byte[] iartData = (iart + "\0").getBytes();
                buffer.put("IART".getBytes());
                buffer.putInt(iartData.length);
                buffer.put(iartData);
            }

            // rate
            if (!rate.isEmpty()) {
                buffer.put("rate".getBytes());
                buffer.putInt(rate.size() * 4);
                for (int r : rate) buffer.putInt(r);
            }

            // seq
            if (!seq.isEmpty()) {
                buffer.put("seq ".getBytes());
                buffer.putInt(seq.size() * 4);
                for (int s : seq) buffer.putInt(s);
            }

            // LIST fram
            ByteBuffer framBuffer = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.LITTLE_ENDIAN);
            framBuffer.put("fram".getBytes());
            for (byte[] frame : frames) {
                framBuffer.put("icon".getBytes());
                framBuffer.putInt(frame.length);
                framBuffer.put(frame);
            }
            framBuffer.flip();
            int framSize = framBuffer.limit();
            buffer.put("LIST".getBytes());
            buffer.putInt(framSize);
            buffer.put(framBuffer);

            buffer.flip();
            int totalSize = buffer.limit();
            buffer.putInt(4, totalSize - 8); // Update riffSize
            fos.write(buffer.array(), 0, totalSize);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Example usage:
    // public static void main(String[] args) {
    //     ANIFile ani = new ANIFile("input.ani");
    //     ani.printProperties();
    //     ani.write("output.ani");
    // }
}

6. JavaScript Class for .ANI Handling (Node.js)

const fs = require('fs');

class ANIFile {
  constructor(filename) {
    this.filename = filename;
    this.riffSig = '';
    this.riffSize = 0;
    this.riffType = '';
    this.anih = {};
    this.inam = '';
    this.iart = '';
    this.rate = [];
    this.seq = [];
    this.frames = []; // Array of Buffers for icon data
    this.read();
  }

  read() {
    const data = fs.readFileSync(this.filename);
    let pos = 0;
    this.riffSig = data.slice(pos, pos + 4).toString(); pos += 4;
    this.riffSize = data.readUInt32LE(pos); pos += 4;
    this.riffType = data.slice(pos, pos + 4).toString(); pos += 4;

    while (pos < data.length) {
      const chunkId = data.slice(pos, pos + 4).toString(); pos += 4;
      const chunkSize = data.readUInt32LE(pos); pos += 4;
      const chunkData = data.slice(pos, pos + chunkSize);

      if (chunkId === 'anih') {
        this.anih.cbSize = chunkData.readUInt32LE(0);
        this.anih.cFrames = chunkData.readUInt32LE(4);
        this.anih.cSteps = chunkData.readUInt32LE(8);
        this.anih.cx = chunkData.readUInt32LE(12);
        this.anih.cy = chunkData.readUInt32LE(16);
        this.anih.cBitCount = chunkData.readUInt32LE(20);
        this.anih.cPlanes = chunkData.readUInt32LE(24);
        this.anih.JifRate = chunkData.readUInt32LE(28);
        this.anih.flags = chunkData.readUInt32LE(32);
      } else if (chunkId === 'INAM') {
        this.inam = chunkData.toString('ascii').replace(/\0.*$/, '');
      } else if (chunkId === 'IART') {
        this.iart = chunkData.toString('ascii').replace(/\0.*$/, '');
      } else if (chunkId === 'rate') {
        for (let i = 0; i < chunkSize; i += 4) {
          this.rate.push(chunkData.readUInt32LE(i));
        }
      } else if (chunkId === 'seq ') {
        for (let i = 0; i < chunkSize; i += 4) {
          this.seq.push(chunkData.readUInt32LE(i));
        }
      } else if (chunkId === 'LIST') {
        const listType = chunkData.slice(0, 4).toString();
        if (listType === 'fram') {
          let subPos = 4;
          while (subPos < chunkSize) {
            const subId = chunkData.slice(subPos, subPos + 4).toString(); subPos += 4;
            const subSize = chunkData.readUInt32LE(subPos); subPos += 4;
            if (subId === 'icon') {
              this.frames.push(chunkData.slice(subPos, subPos + subSize));
            }
            subPos += subSize;
            if (subSize % 2 !== 0) subPos++;
          }
        }
      }
      pos += chunkSize;
      if (chunkSize % 2 !== 0) pos++;
    }
  }

  printProperties() {
    console.log('RIFF Signature:', this.riffSig);
    console.log('RIFF Size:', this.riffSize);
    console.log('RIFF Type:', this.riffType);
    console.log('anih:', this.anih);
    console.log('INAM:', this.inam);
    console.log('IART:', this.iart);
    console.log('rate:', this.rate);
    console.log('seq:', this.seq);
    console.log('Number of frames:', this.frames.length);
  }

  write(newFilename) {
    let buffers = [];
    buffers.push(Buffer.from('RIFF'));
    const sizePlaceholder = Buffer.alloc(4);
    buffers.push(sizePlaceholder);
    buffers.push(Buffer.from('ACON'));

    // anih
    const anihBuffer = Buffer.alloc(44);
    anihBuffer.write('anih', 0, 4);
    anihBuffer.writeUInt32LE(36, 4);
    anihBuffer.writeUInt32LE(this.anih.cbSize, 8);
    anihBuffer.writeUInt32LE(this.anih.cFrames, 12);
    anihBuffer.writeUInt32LE(this.anih.cSteps, 16);
    anihBuffer.writeUInt32LE(this.anih.cx, 20);
    anihBuffer.writeUInt32LE(this.anih.cy, 24);
    anihBuffer.writeUInt32LE(this.anih.cBitCount, 28);
    anihBuffer.writeUInt32LE(this.anih.cPlanes, 32);
    anihBuffer.writeUInt32LE(this.anih.JifRate, 36);
    anihBuffer.writeUInt32LE(this.anih.flags, 40);
    buffers.push(anihBuffer);

    // INAM
    if (this.inam) {
      const inamData = Buffer.from(this.inam + '\0');
      const inamHeader = Buffer.alloc(8);
      inamHeader.write('INAM', 0, 4);
      inamHeader.writeUInt32LE(inamData.length, 4);
      buffers.push(inamHeader);
      buffers.push(inamData);
    }

    // IART
    if (this.iart) {
      const iartData = Buffer.from(this.iart + '\0');
      const iartHeader = Buffer.alloc(8);
      iartHeader.write('IART', 0, 4);
      iartHeader.writeUInt32LE(iartData.length, 4);
      buffers.push(iartHeader);
      buffers.push(iartData);
    }

    // rate
    if (this.rate.length > 0) {
      const rateData = Buffer.alloc(this.rate.length * 4);
      this.rate.forEach((r, i) => rateData.writeUInt32LE(r, i * 4));
      const rateHeader = Buffer.alloc(8);
      rateHeader.write('rate', 0, 4);
      rateHeader.writeUInt32LE(rateData.length, 4);
      buffers.push(rateHeader);
      buffers.push(rateData);
    }

    // seq
    if (this.seq.length > 0) {
      const seqData = Buffer.alloc(this.seq.length * 4);
      this.seq.forEach((s, i) => seqData.writeUInt32LE(s, i * 4));
      const seqHeader = Buffer.alloc(8);
      seqHeader.write('seq ', 0, 4);
      seqHeader.writeUInt32LE(seqData.length, 4);
      buffers.push(seqHeader);
      buffers.push(seqData);
    }

    // LIST fram
    let framBuffers = [];
    framBuffers.push(Buffer.from('fram'));
    this.frames.forEach(frame => {
      const iconHeader = Buffer.alloc(8);
      iconHeader.write('icon', 0, 4);
      iconHeader.writeUInt32LE(frame.length, 4);
      framBuffers.push(iconHeader);
      framBuffers.push(frame);
    });
    const framData = Buffer.concat(framBuffers);
    const listHeader = Buffer.alloc(8);
    listHeader.write('LIST', 0, 4);
    listHeader.writeUInt32LE(framData.length, 4);
    buffers.push(listHeader);
    buffers.push(framData);

    const fullData = Buffer.concat(buffers);
    fullData.writeUInt32LE(fullData.length - 8, 4); // Update riffSize
    fs.writeFileSync(newFilename, fullData);
  }
}

// Example usage:
// const ani = new ANIFile('input.ani');
// ani.printProperties();
// ani.write('output.ani');

7. C Code for .ANI Handling (Using Struct as "Class")

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    char riff_sig[5];
    uint32_t riff_size;
    char riff_type[5];
    struct {
        uint32_t cbSize;
        uint32_t cFrames;
        uint32_t cSteps;
        uint32_t cx;
        uint32_t cy;
        uint32_t cBitCount;
        uint32_t cPlanes;
        uint32_t JifRate;
        uint32_t flags;
    } anih;
    char *inam;
    char *iart;
    uint32_t *rate;
    uint32_t rate_count;
    uint32_t *seq;
    uint32_t seq_count;
    uint8_t **frames;
    uint32_t *frame_sizes;
    uint32_t frame_count;
} ANIFile;

void ani_read(ANIFile *ani, const char *filename) {
    FILE *f = fopen(filename, "rb");
    if (!f) return;
    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);

    uint32_t pos = 0;
    memcpy(ani->riff_sig, data + pos, 4); ani->riff_sig[4] = '\0'; pos += 4;
    ani->riff_size = *(uint32_t*)(data + pos); pos += 4;
    memcpy(ani->riff_type, data + pos, 4); ani->riff_type[4] = '\0'; pos += 4;

    while (pos < size) {
        char chunk_id[5];
        memcpy(chunk_id, data + pos, 4); chunk_id[4] = '\0'; pos += 4;
        uint32_t chunk_size = *(uint32_t*)(data + pos); pos += 4;
        uint8_t *chunk_data = data + pos;

        if (strcmp(chunk_id, "anih") == 0) {
            ani->anih.cbSize = *(uint32_t*)(chunk_data + 0);
            ani->anih.cFrames = *(uint32_t*)(chunk_data + 4);
            ani->anih.cSteps = *(uint32_t*)(chunk_data + 8);
            ani->anih.cx = *(uint32_t*)(chunk_data + 12);
            ani->anih.cy = *(uint32_t*)(chunk_data + 16);
            ani->anih.cBitCount = *(uint32_t*)(chunk_data + 20);
            ani->anih.cPlanes = *(uint32_t*)(chunk_data + 24);
            ani->anih.JifRate = *(uint32_t*)(chunk_data + 28);
            ani->anih.flags = *(uint32_t*)(chunk_data + 32);
        } else if (strcmp(chunk_id, "INAM") == 0) {
            ani->inam = strndup((char*)chunk_data, chunk_size);
        } else if (strcmp(chunk_id, "IART") == 0) {
            ani->iart = strndup((char*)chunk_data, chunk_size);
        } else if (strcmp(chunk_id, "rate") == 0) {
            ani->rate_count = chunk_size / 4;
            ani->rate = malloc(chunk_size);
            memcpy(ani->rate, chunk_data, chunk_size);
        } else if (strcmp(chunk_id, "seq ") == 0) {
            ani->seq_count = chunk_size / 4;
            ani->seq = malloc(chunk_size);
            memcpy(ani->seq, chunk_data, chunk_size);
        } else if (strcmp(chunk_id, "LIST") == 0) {
            char list_type[5];
            memcpy(list_type, chunk_data, 4); list_type[4] = '\0';
            if (strcmp(list_type, "fram") == 0) {
                uint32_t sub_pos = 4;
                while (sub_pos < chunk_size) {
                    char sub_id[5];
                    memcpy(sub_id, chunk_data + sub_pos, 4); sub_id[4] = '\0'; sub_pos += 4;
                    uint32_t sub_size = *(uint32_t*)(chunk_data + sub_pos); sub_pos += 4;
                    if (strcmp(sub_id, "icon") == 0) {
                        ani->frame_count++;
                        ani->frames = realloc(ani->frames, ani->frame_count * sizeof(uint8_t*));
                        ani->frame_sizes = realloc(ani->frame_sizes, ani->frame_count * sizeof(uint32_t));
                        ani->frames[ani->frame_count - 1] = malloc(sub_size);
                        memcpy(ani->frames[ani->frame_count - 1], chunk_data + sub_pos, sub_size);
                        ani->frame_sizes[ani->frame_count - 1] = sub_size;
                    }
                    sub_pos += sub_size;
                    if (sub_size % 2 != 0) sub_pos++;
                }
            }
        }
        pos += chunk_size;
        if (chunk_size % 2 != 0) pos++;
    }
    free(data);
}

void ani_print_properties(ANIFile *ani) {
    printf("RIFF Signature: %s\n", ani->riff_sig);
    printf("RIFF Size: %u\n", ani->riff_size);
    printf("RIFF Type: %s\n", ani->riff_type);
    printf("anih cbSize: %u\n", ani->anih.cbSize);
    printf("anih cFrames: %u\n", ani->anih.cFrames);
    printf("anih cSteps: %u\n", ani->anih.cSteps);
    printf("anih cx: %u\n", ani->anih.cx);
    printf("anih cy: %u\n", ani->anih.cy);
    printf("anih cBitCount: %u\n", ani->anih.cBitCount);
    printf("anih cPlanes: %u\n", ani->anih.cPlanes);
    printf("anih JifRate: %u\n", ani->anih.JifRate);
    printf("anih flags: %u\n", ani->anih.flags);
    if (ani->inam) printf("INAM: %s\n", ani->inam);
    if (ani->iart) printf("IART: %s\n", ani->iart);
    if (ani->rate_count > 0) {
        printf("rate: ");
        for (uint32_t i = 0; i < ani->rate_count; i++) printf("%u ", ani->rate[i]);
        printf("\n");
    }
    if (ani->seq_count > 0) {
        printf("seq: ");
        for (uint32_t i = 0; i < ani->seq_count; i++) printf("%u ", ani->seq[i]);
        printf("\n");
    }
    printf("Number of frames: %u\n", ani->frame_count);
}

void ani_write(ANIFile *ani, const char *new_filename) {
    FILE *f = fopen(new_filename, "wb");
    if (!f) return;

    fwrite("RIFF", 1, 4, f);
    uint32_t placeholder = 0;
    fwrite(&placeholder, sizeof(uint32_t), 1, f); // riff_size
    fwrite("ACON", 1, 4, f);

    // anih
    fwrite("anih", 1, 4, f);
    uint32_t anih_size = 36;
    fwrite(&anih_size, sizeof(uint32_t), 1, f);
    fwrite(&ani->anih, sizeof(ani->anih), 1, f);

    // INAM
    if (ani->inam) {
        fwrite("INAM", 1, 4, f);
        uint32_t str_size = strlen(ani->inam) + 1;
        fwrite(&str_size, sizeof(uint32_t), 1, f);
        fwrite(ani->inam, 1, str_size - 1, f);
        fputc(0, f);
    }

    // IART
    if (ani->iart) {
        fwrite("IART", 1, 4, f);
        uint32_t str_size = strlen(ani->iart) + 1;
        fwrite(&str_size, sizeof(uint32_t), 1, f);
        fwrite(ani->iart, 1, str_size - 1, f);
        fputc(0, f);
    }

    // rate
    if (ani->rate_count > 0) {
        fwrite("rate", 1, 4, f);
        uint32_t data_size = ani->rate_count * 4;
        fwrite(&data_size, sizeof(uint32_t), 1, f);
        fwrite(ani->rate, 4, ani->rate_count, f);
    }

    // seq
    if (ani->seq_count > 0) {
        fwrite("seq ", 1, 4, f);
        uint32_t data_size = ani->seq_count * 4;
        fwrite(&data_size, sizeof(uint32_t), 1, f);
        fwrite(ani->seq, 4, ani->seq_count, f);
    }

    // LIST fram
    fpos_t list_pos;
    fgetpos(f, &list_pos);
    fwrite("LIST", 1, 4, f);
    uint32_t list_size_placeholder = 0;
    fwrite(&list_size_placeholder, sizeof(uint32_t), 1, f);
    fwrite("fram", 1, 4, f);
    uint32_t fram_content_size = 4; // 'fram'
    for (uint32_t i = 0; i < ani->frame_count; i++) {
        fwrite("icon", 1, 4, f);
        fwrite(&ani->frame_sizes[i], sizeof(uint32_t), 1, f);
        fwrite(ani->frames[i], 1, ani->frame_sizes[i], f);
        fram_content_size += 8 + ani->frame_sizes[i];
    }
    fpos_t end_pos;
    fgetpos(f, &end_pos);
    uint32_t list_size = fram_content_size;
    fsetpos(f, &list_pos);
    fseek(f, 4, SEEK_CUR); // Skip 'LIST'
    fwrite(&list_size, sizeof(uint32_t), 1, f);
    fsetpos(f, &end_pos);

    // Update riff_size
    fseek(f, 0, SEEK_END);
    long total_size = ftell(f);
    uint32_t riff_size = total_size - 8;
    fseek(f, 4, SEEK_SET);
    fwrite(&riff_size, sizeof(uint32_t), 1, f);

    fclose(f);
}

void ani_free(ANIFile *ani) {
    free(ani->inam);
    free(ani->iart);
    free(ani->rate);
    free(ani->seq);
    for (uint32_t i = 0; i < ani->frame_count; i++) free(ani->frames[i]);
    free(ani->frames);
    free(ani->frame_sizes);
}

// Example usage:
// int main() {
//     ANIFile ani = {0};
//     ani_read(&ani, "input.ani");
//     ani_print_properties(&ani);
//     ani_write(&ani, "output.ani");
//     ani_free(&ani);
//     return 0;
// }