From cbd831e65809e41b4cadd55b896514e8a0480b87 Mon Sep 17 00:00:00 2001
From: bnnm <bananaman255@gmail.com>
Date: Fri, 23 Sep 2022 22:53:45 +0200
Subject: [PATCH] Add .bigrp [BM Zero 2 (SW), Gunvolt 3 (SW)]

---
 doc/FORMATS.md                   |    1 +
 src/coding/coding.h              |   10 +
 src/coding/ice_decoder.c         |  138 +++
 src/coding/ice_decoder_icelib.c  | 1433 ++++++++++++++++++++++++++++++
 src/coding/ice_decoder_icelib.h  |   67 ++
 src/decode.c                     |   22 +
 src/formats.c                    |    4 +
 src/libvgmstream.vcxproj         |    4 +
 src/libvgmstream.vcxproj.filters |   12 +
 src/meta/bigrp.c                 |  110 +++
 src/meta/meta.h                  |    2 +
 src/vgmstream.c                  |    1 +
 src/vgmstream.h                  |    3 +
 13 files changed, 1807 insertions(+)
 create mode 100644 src/coding/ice_decoder.c
 create mode 100644 src/coding/ice_decoder_icelib.c
 create mode 100644 src/coding/ice_decoder_icelib.h
 create mode 100644 src/meta/bigrp.c

diff --git a/doc/FORMATS.md b/doc/FORMATS.md
index 06ea7402..645d37e0 100644
--- a/doc/FORMATS.md
+++ b/doc/FORMATS.md
@@ -315,6 +315,7 @@ are used in few games.
 - Xiph CELT (FSB)
 - Musepack
 - FLAC
+- Inti Creates' Range / DCT codecs
 - Others
 
 Sometimes standard codecs come in non-standard layouts that aren't normally
diff --git a/src/coding/coding.h b/src/coding/coding.h
index 71368fd1..3621fe30 100644
--- a/src/coding/coding.h
+++ b/src/coding/coding.h
@@ -353,6 +353,16 @@ void seek_tac(tac_codec_data* data, int32_t num_sample);
 void free_tac(tac_codec_data* data);
 
 
+/* ice_decoder */
+typedef struct ice_codec_data ice_codec_data;
+
+ice_codec_data* init_ice(STREAMFILE* sf, int subsong);
+void decode_ice(ice_codec_data* data, sample_t* outbuf, int32_t samples_to_do);
+void reset_ice(ice_codec_data* data);
+void seek_ice(ice_codec_data* data, int32_t num_sample);
+void free_ice(ice_codec_data* data);
+
+
 #ifdef VGM_USE_VORBIS
 /* ogg_vorbis_decoder */
 typedef struct ogg_vorbis_codec_data ogg_vorbis_codec_data;
diff --git a/src/coding/ice_decoder.c b/src/coding/ice_decoder.c
new file mode 100644
index 00000000..3dd77e89
--- /dev/null
+++ b/src/coding/ice_decoder.c
@@ -0,0 +1,138 @@
+#include "coding.h"
+#include "ice_decoder_icelib.h"
+
+
+typedef struct {
+    STREAMFILE* sf;
+    int offset;
+} icelib_io_t;
+
+struct ice_codec_data {
+    STREAMFILE* sf;
+    int channels;
+    icesnd_handle_t* ctx;
+    icelib_io_t io;
+};
+
+static void icelib_set_callbacks(icesnd_callback_t* cb, STREAMFILE* sf, icelib_io_t* io);
+
+ice_codec_data* init_ice(STREAMFILE* sf, int subsong) {
+    ice_codec_data* data = NULL;
+
+    data = calloc(1, sizeof(ice_codec_data));
+    if (!data) goto fail;
+
+    data->sf = reopen_streamfile(sf, 0);
+    if (!data->sf) goto fail;
+
+    {
+        icesnd_callback_t cb = {0};
+        icesnd_info_t info = {0};
+        int err;
+
+        icelib_set_callbacks(&cb, data->sf, &data->io);
+
+        data->ctx = icesnd_init(subsong, &cb);
+        if (!data->ctx) goto fail;
+
+        err = icesnd_info(data->ctx, &info);
+        if (err < ICESND_RESULT_OK) goto fail;
+
+        data->channels = info.channels;
+    }
+
+    return data;
+fail:
+    free_ice(data);
+    return NULL;
+}
+
+void decode_ice(ice_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
+    int channels = data->channels;
+
+    while (samples_to_do > 0) {
+        int done = icesnd_decode(data->ctx, outbuf, samples_to_do);
+        if (done <= 0) goto decode_fail;
+
+        outbuf += done * channels;
+        samples_to_do -= done;
+    }
+
+    return;
+    
+decode_fail:
+    VGM_LOG("ICE: decode error\n");
+    memset(outbuf, 0, samples_to_do * channels * sizeof(sample_t));
+}
+
+void reset_ice(ice_codec_data* data) {
+    if (!data) return;
+
+    icesnd_reset(data->ctx, 0);
+}
+
+void seek_ice(ice_codec_data* data, int32_t num_sample) {
+    if (!data) return;
+
+    //todo discard (this should only be called when looping)
+    icesnd_reset(data->ctx, 1);
+}
+
+void free_ice(ice_codec_data* data) {
+    if (!data) return;
+
+    close_streamfile(data->sf);
+    icesnd_free(data->ctx);
+    free(data);
+}
+
+/* ************************* */
+
+static int icelib_read(void* dst, int size, int n, void* arg) {
+    icelib_io_t* io = arg;
+    int bytes_read, items_read;
+
+    bytes_read = read_streamfile(dst, io->offset, size * n, io->sf);
+    items_read = bytes_read / size;
+    io->offset += bytes_read;
+
+    return items_read;
+}
+
+static int icelib_seek(void* arg, int offset, int whence) {
+    icelib_io_t* io = arg;
+    int base_offset, new_offset;
+
+    switch (whence) {
+        case ICESND_SEEK_SET:
+            base_offset = 0;
+            break;
+        case ICESND_SEEK_CUR:
+            base_offset = io->offset;
+            break;
+        case ICESND_SEEK_END:
+            base_offset = get_streamfile_size(io->sf);
+            break;
+        default:
+            return -1;
+            break;
+    }
+
+    new_offset = base_offset + offset;
+    if (new_offset < 0 /*|| new_offset > get_streamfile_size(config->sf)*/) {
+        return -1; /* unseekable */
+    }
+    else {
+        io->offset = new_offset;
+        return 0;
+    }
+}
+
+static void icelib_set_callbacks(icesnd_callback_t* cb, STREAMFILE* sf, icelib_io_t* io) {
+    io->offset = 0;
+    io->sf = sf;
+
+    cb->arg = io;
+    cb->read = icelib_read;
+    cb->seek = icelib_seek;
+}
diff --git a/src/coding/ice_decoder_icelib.c b/src/coding/ice_decoder_icelib.c
new file mode 100644
index 00000000..1b6508aa
--- /dev/null
+++ b/src/coding/ice_decoder_icelib.c
@@ -0,0 +1,1433 @@
+/* Decodes Inti Creates' BIGRP files with custom codecs, found in games using their current 
+ * "Inti Creates Engine" ("ICE") / "Imperial Engine" ('IMP'?). Engine's name is said to be
+ * the latter (ICE being the earlier iteration) but debug info still shows the former.
+ * Reverse engineered from various exes (if you use this as a base credit it, please).
+ * 
+ * This code tries to follow the original closely for documentation purposes, with some extra 
+ * error control (original doesn't check zlib errors or buf sizes) plus a few extra structs/functions
+ * that were likely inline'd (such as bitreaders). */
+
+//TODO change to streaming decoder
+// Currently lib expects most data in memory. Due to how format is designed it's not the
+// easiest thing to change, to be fixed it later:
+// - data is divided into 2 blocks (intro+body) that are decoded separatedly
+//   (streaming should read up to block max)
+// - code data isn't divided into frames, just keeps reading from the file buf
+// - "range" decoder has linear data, and should be easy enough to stream, but it's rarely used.
+// - "dct" decoder has a big chunk (+30%) of codebook data at the beginning of each block, then
+//   code data *but* decoder reads simultaneously from both places. Files can be rather big
+//   (2 mins = 6mb, codebooks = ~1.5mb). Would need to pre-read all the codebooks (still big) then
+//   stream data, or seek around to codebooks (thrashes FILE buffers).
+
+
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "ice_decoder_icelib.h" 
+
+/* use miniz (API-compatible) to avoid adding external zlib just for this codec
+ * - https://github.com/richgel999/miniz */
+#include "../util/miniz.h" 
+//#include "zlib.h"
+
+#define ICESND_MAX_CHANNELS    2
+
+
+/* ************************************************************ */
+/* COMMON */
+/* ************************************************************ */
+
+static inline uint8_t get_u8(const uint8_t* p) {
+    uint8_t ret;
+    ret  = ((uint16_t)(const uint8_t)p[0]) << 0;
+    return ret;
+}
+
+static inline uint16_t get_u16le(const uint8_t* p) {
+    uint16_t ret;
+    ret  = ((uint16_t)(const uint8_t)p[0]) << 0;
+    ret |= ((uint16_t)(const uint8_t)p[1]) << 8;
+    return ret;
+}
+
+static inline uint32_t get_u32le(const uint8_t* p) {
+    uint32_t ret;
+    ret  = ((uint32_t)(const uint8_t)p[0]) << 0;
+    ret |= ((uint32_t)(const uint8_t)p[1]) << 8;
+    ret |= ((uint32_t)(const uint8_t)p[2]) << 16;
+    ret |= ((uint32_t)(const uint8_t)p[3]) << 24;
+    return ret;
+}
+
+/* bigrp entry info as read from header */
+typedef struct {
+    uint32_t hash1; /* usually matches filename, different files vary on bytes, seems internally used to identify files */
+    uint32_t hash2; /* group id? repeated in several files, doesn't seem used */
+    uint32_t codec; /* 00: range, 01: metadata, 02: midi, 03: DCT */
+
+    /* rest of header varies per codec (padded until ~0x40 for all) */
+    /* codec 01 entry: */
+    /* - offset */
+    /* - size */
+
+    /* codec 02 entry: */
+    /* - config? (big value, same for all entries) */
+    /* - midi offset */
+    /* - midi size */
+    /* - midi config? (~0x30C0, possibly some size) */
+    /* - midi config? (~0x4036, then divided by 180.0) */
+
+    /* codec 00/03 entry */
+    int sample_rate;
+    int channels;
+    int spf;        /* always 16, internally used for pcm size calculations */
+    int unknown;    /* some kind of low-ish value, volume? (seen 0x40~0x7F) */
+
+    int loop_flag;
+    int frame_codes; /* 0x64 in codec 00, 0x00 in codec 03 (possibly "RangeBlockSize") */
+
+    /* has one "intro" block then one "body" block; intro block may be zero in full loops/no loops */
+    uint32_t intro_samples;
+    uint32_t intro_zsize;
+    uint32_t intro_offset;
+    uint32_t body_samples;
+    uint32_t body_zsize;
+    uint32_t body_offset;
+
+    /* rest: padding until entry_size */
+
+} bigrp_entry_t;
+
+/* base bigrp header and extra config */
+typedef struct {
+    uint32_t head_size;
+    uint32_t entry_size;
+    int total_subsongs;
+    uint32_t dummy;
+
+} bigrp_header_t;
+
+/* block/data format (handled later):
+ * 
+ * codec 00 (range):
+ * - zlib block (32b deflated size + zlib data)
+ *
+ * codec 03 (dct)
+ * - codeinfo block (see codeinfo_parse)
+ * - zlib tables
+ * - data
+ *
+ * codec 01 (data?)
+ * - ?
+ *   Not parsed as audio in the IcePlayer init. Usually paired with midis, has several sections and some
+ *   point to entries using hash1, so probably soundfont config, but not all bigrp with midis have this.
+ *
+ * codec 02 (midi)
+ * - standard midi (MThd)
+ */
+ 
+
+/* OG code casts buffer to this struct, read in a more portable fashion */
+static int bigrp_entry_parse(bigrp_entry_t* entry, const uint8_t* buf, int buf_size) {
+
+    if (buf_size < 0x34)
+        goto fail;
+
+    entry->hash1        = get_u32le(buf + 0x00);
+    entry->hash2        = get_u32le(buf + 0x04);
+    entry->codec        = get_u32le(buf + 0x08); /* u8 in decoder, read fully to validate */
+
+    switch (entry->codec) {
+        case ICESND_CODEC_RANGE:
+        case ICESND_CODEC_DCT:
+            entry->sample_rate      = get_u32le(buf + 0x0c);
+            entry->channels         = get_u8   (buf + 0x10);
+            entry->spf              = get_u8   (buf + 0x11);
+            entry->unknown          = get_u16le(buf + 0x12);
+            entry->loop_flag        = get_u32le(buf + 0x14); /* u8 in decoder, read fully to validate */
+            entry->frame_codes      = get_u32le(buf + 0x18); /* codec 00 only (includes N channels) */
+
+            entry->intro_samples    = get_u32le(buf + 0x1c);
+            entry->intro_zsize      = get_u32le(buf + 0x20);
+            entry->intro_offset     = get_u32le(buf + 0x24);
+            entry->body_samples     = get_u32le(buf + 0x28);
+            entry->body_zsize       = get_u32le(buf + 0x2c);
+            entry->body_offset      = get_u32le(buf + 0x30);
+
+            if (entry->sample_rate < 2000 || entry->sample_rate > 48000) /* just in case */
+                goto fail;
+            if (entry->channels < 1 || entry->channels > 2 || entry->spf != 16) /* not seen */
+                goto fail;
+            if (entry->frame_codes != 0 && entry->frame_codes != 0x64)
+                goto fail;
+            if (entry->frame_codes % entry->channels != 0) /* assumed that N codes = N/chs samples */
+                goto fail;
+            if (entry->loop_flag != 0 && entry->loop_flag != 1)
+                goto fail;
+
+            /* probably wouldn't matter, same with other sizes */
+            if (entry->intro_samples == 0 && entry->body_samples == 0)
+                goto fail;
+
+            if (entry->channels > 1 && entry->codec == ICESND_CODEC_RANGE) /* not seen */
+                goto fail;
+            break;
+
+        case ICESND_CODEC_DATA:
+        case ICESND_CODEC_MIDI:
+        default:
+            goto fail;
+    }
+
+    return ICESND_RESULT_OK;
+fail:
+    return ICESND_ERROR_HEADER;
+}
+
+/* read main .bigrp header. Earlier games used standard Nintendo's BFGRP and BCGRP, 
+ * and this format seems kind of inspired by it, so presumably BIGRP = Binary Inti Group */
+static int bigrp_header_parse(bigrp_header_t* hdr, const uint8_t* buf, int buf_size, int subsong) {
+
+    if (buf_size < 0x0c)
+        goto fail;
+
+
+    /* read base header */
+    hdr->head_size = get_u32le(buf + 0x00);
+    hdr->entry_size = get_u32le(buf + 0x04);
+    hdr->total_subsongs = get_u32le(buf + 0x08);
+
+    if (hdr->head_size > buf_size)
+        goto fail;
+
+    if (hdr->head_size >= 0x10)
+        hdr->dummy = get_u32le(buf + 0x0c);
+    else
+        hdr->dummy = 0x00;
+
+    /* 0x0c: Bloodstained COTM (Vita/3DS), Mighty Gunvolt Burst (PC); 0x10: rest */
+    if (hdr->head_size != 0x0c && hdr->head_size != 0x10)
+        goto fail;
+    /* same (no changes, after 0x34 is padding) */
+    if (hdr->entry_size != 0x34 && hdr->entry_size != 0x40)
+        goto fail;
+    if (hdr->dummy != 0x00)
+        goto fail;
+
+    if (subsong < 1 || subsong > hdr->total_subsongs)
+        goto fail;
+
+    return ICESND_RESULT_OK;
+fail:
+    return ICESND_ERROR_HEADER;
+}
+
+/* original also tries to call's zlib free is needed (not set though) */
+static void zlib_end(z_stream* strm, int* p_zlib_init) {
+    if (*p_zlib_init) {
+        inflateEnd(strm);
+
+        /* original also tries to call's zlib free if needed (not set though) */
+
+        *p_zlib_init = 0;
+    }
+}
+
+static int zlib_init(z_stream* strm, int* p_zlib_init, const uint8_t* buf, int buf_size) {
+    int err;
+
+    zlib_end(strm, p_zlib_init);
+
+    strm->zalloc = 0;
+    strm->zfree = 0;
+    strm->opaque = 0;
+    strm->next_in = 0;
+    strm->avail_in = 0;
+    err = inflateInit(strm);
+    if (err < 0) return ICESND_ERROR_SETUP;
+    *p_zlib_init = 1;
+
+    /* zlib data starts with the decompressed size */
+    strm->next_in = buf + 0x04;
+    strm->avail_in = buf_size - 0x04;
+
+    return ICESND_RESULT_OK;
+}
+
+
+/* ************************************************************ */
+/* RANGE */
+/* ************************************************************ */
+
+/* Inti Creates's "range" decoder, internally IceSoundCodecDecoderRange class. Seems similar to Sony's
+ * "adaptive dynamic range coding" (ADRC) compression (not what's usually called "range coding").
+ *
+ * Data is zlibbed (though doesn't save much) then divided into VBR frames. Each frame has a 24-bit LE
+ * header that defines a "range" (min..max) and quantized codes' bits (often 6 or 7), then 100 codes.
+ * Unsigned codes just map to a relative value within the range, and final sample is range-min + range-value.
+ * Stereo alternates L-R header then L R L R ... samples
+ *
+ * For example, if a range = [0, 6000], and bits = 6:
+ * - code=0 > value=0 (min), code=63 > value=6000 (max), code=31 > value=2952 (in-between), ...
+ */
+
+#define RANGE_DECODE_BUFFER     0x800
+
+typedef struct {
+    const uint8_t* inbuf;
+    int inbuf_size;
+    int max_samples;
+    int frame_codes;
+    int samples_done;
+    uint32_t outbuf_pos;
+    uint8_t outbuf[RANGE_DECODE_BUFFER];
+    z_stream strm;
+    int codes_left;
+    int bitpos; /* within curr_byte */
+    int16_t range_min[ICESND_MAX_CHANNELS];
+    int16_t range_max[ICESND_MAX_CHANNELS];
+    uint16_t range_bits[ICESND_MAX_CHANNELS];
+    uint16_t range_mask[ICESND_MAX_CHANNELS];
+    uint8_t curr_byte;
+    uint8_t spf; /* not needed to decode though */
+    uint8_t channels;
+    int zlib_init;
+
+    /* extra */
+    uint32_t outbuf_max; /* original has all data in memory, but we read zlib in chunks that may be smaller */
+
+} range_handle_t;
+
+
+static range_handle_t* range_decoder_open() {
+    range_handle_t* ctx = NULL;
+
+    //ctx = calloc(1, sizeof(range_handle_t));
+    ctx = malloc(sizeof(range_handle_t));
+    if (!ctx) goto fail;
+
+    ctx->inbuf = NULL;
+    ctx->inbuf_size = 0;
+    ctx->max_samples = 0;
+    ctx->frame_codes = 0;
+    ctx->spf = 0;
+    ctx->zlib_init = 0;
+
+    return ctx;
+fail:
+    return NULL;
+}
+
+static void range_decoder_close(range_handle_t* ctx) {
+    if (!ctx)
+        return;
+
+    zlib_end(&ctx->strm, &ctx->zlib_init);
+    free(ctx);
+}
+
+static int range_decoder_reset(range_handle_t* ctx) {
+    int err;
+
+    err = zlib_init(&ctx->strm, &ctx->zlib_init, ctx->inbuf, ctx->inbuf_size);
+    if (err < ICESND_RESULT_OK) return err;
+
+    ctx->outbuf_pos = 0xFFFFFFFF; //sizeof(ctx->outbuf); /* force init */
+    ctx->samples_done = 0;
+    ctx->codes_left = 0;
+    ctx->bitpos = 0;
+
+    ctx->outbuf_max = 0;
+
+    return ICESND_RESULT_OK;
+}
+
+static int range_decoder_block_setup(range_handle_t* ctx, const uint8_t* buf, int buf_size, bigrp_entry_t* etr, int max_samples) {
+    int err;
+
+    ctx->inbuf = buf;
+    ctx->inbuf_size = buf_size;
+    ctx->max_samples = max_samples;
+
+    ctx->frame_codes = etr->frame_codes;
+    ctx->spf = etr->spf;
+    ctx->channels = etr->channels;
+
+    err = range_decoder_reset(ctx);
+    if (err < ICESND_RESULT_OK) return err;
+
+    return ICESND_RESULT_OK;
+}
+
+#if 0
+static void fill_zlib(z_stream* strm, data_t* data) {
+
+    /* zlib sets this to 0 once consumed */
+    if (strm->avail_in > 0)
+        return;
+
+    /* buffer is smaller than size, so this will be called N times */
+    if (data->data_left) {
+        int bytes = data->buf_size;
+        if (bytes > data->data_left)
+            bytes = data->data_left;
+
+        strm->next_in = data->buf;
+        strm->avail_in = data->cb.read(data->buf, 1, bytes, data->cb.arg);
+
+        data->data_left -= strm->avail_in;
+    }
+}
+#endif
+
+/* get next byte from the zlib stream */
+static void range_load_byte(range_handle_t* ctx) {
+
+    if (ctx->outbuf_pos >= ctx->outbuf_max) { /* OG: >= sizeof(ctx->outbuf) */
+        //fill_zlib(&ctx->strm, ctx->data);
+
+        ctx->strm.avail_out = sizeof(ctx->outbuf);
+        ctx->strm.next_out = ctx->outbuf;
+        inflate(&ctx->strm, Z_NO_FLUSH);
+        //if (err < Z_OK) return ICESND_ERROR_DECODER; /* OG: no error control (shouldn't matter) */
+
+        ctx->outbuf_pos = 0;
+        ctx->outbuf_max = sizeof(ctx->outbuf) - ctx->strm.avail_out;
+    }
+
+    ctx->curr_byte = ctx->outbuf[ctx->outbuf_pos];
+    ctx->outbuf_pos++;
+}
+
+/* read 24-bit header, decompressing bytes if necessary */
+static void range_load_header(range_handle_t* ctx, int ch) {
+    uint32_t frame_header;
+
+    range_load_byte(ctx);
+    frame_header = ctx->curr_byte;
+
+    range_load_byte(ctx);
+    frame_header = (ctx->curr_byte << 8) | frame_header;
+
+    range_load_byte(ctx);
+    frame_header = (ctx->curr_byte << 16) | frame_header;
+
+    /* get signed range and quantized bits for this frame */
+    ctx->range_min[ch] = (frame_header >> 3) << 5;  /* & 0xFFE0, upper 11 bits (signed) */
+    ctx->range_max[ch] = (frame_header >> 14) << 6; /* & 0xFFC0, upper 10 bits (signed) */ //(frame_header >> 8) & 0xffc0;
+    ctx->range_bits[ch] = (frame_header & 7) + 1;
+    ctx->range_mask[ch] = (1 << ctx->range_bits[ch]) - 1;
+}
+
+/* decode next bitstream's code, decompressing bytes if necessary */
+static int16_t range_get_sample(range_handle_t* ctx, int ch) {
+    int32_t code;
+    int16_t delta;
+    uint16_t mask = ctx->range_mask[ch];
+
+    /* get next code of N-bits (doesn't seem to use an actual bitstream class) */
+    if (ctx->bitpos == 0) {
+        range_load_byte(ctx);
+    }
+    code = (ctx->curr_byte >> (ctx->bitpos)) & mask; 
+
+    if (ctx->bitpos + ctx->range_bits[ch] > 8) {
+        range_load_byte(ctx);
+        code |= (ctx->curr_byte << (8 - ctx->bitpos)) & mask;
+
+        ctx->bitpos = (ctx->bitpos + ctx->range_bits[ch]) - 8;
+    }
+    else {
+        ctx->bitpos = (ctx->bitpos + ctx->range_bits[ch]) & 7;
+    }
+
+    /* calculate code's range value and final sample */
+    delta = code * (ctx->range_max[ch] - ctx->range_min[ch]) / mask;
+    return ctx->range_min[ch] + delta; /* no clamp */
+}
+
+/* decode N samples and copy to sbuf.
+ * Internally decodes N samples at a time, and if asked for non-multiple number of samples it'll
+ * stop and resume properly from last copied sample of those 16. Return 1 if no more samples left. */
+static int range_decoder_decode(range_handle_t* ctx, int16_t* sbuf, const int max_done, int* p_done) {
+    int ch;
+
+
+    *p_done = 0;
+
+    while (ctx->samples_done < ctx->max_samples) {
+
+        /* read frame header */
+        if (ctx->codes_left == 0) {
+            for (ch = 0; ch < ctx->channels; ch++) {
+                range_load_header(ctx, ch);
+            }
+
+            ctx->codes_left = ctx->frame_codes;
+            if (ctx->samples_done + ctx->frame_codes > ctx->max_samples)
+                ctx->codes_left = ctx->max_samples - ctx->samples_done;
+            ctx->bitpos = 0;
+        }
+
+        /* decode frame samples */
+        while (ctx->codes_left) {
+            for (ch = 0; ch < ctx->channels; ch++) {
+                *sbuf++ = range_get_sample(ctx, ch);
+            }
+
+            ctx->samples_done++;
+            ctx->codes_left--;
+
+            (*p_done)++;
+            if (*p_done >= max_done) /* samples left */
+                return ctx->samples_done >= ctx->max_samples; /* block done */
+        }
+    }
+
+    return ctx->samples_done >= ctx->max_samples; /* block done */
+}
+
+
+/* ************************************************************ */
+/* DCT */
+/* ************************************************************ */
+
+/* Inti Creates's "dct" decoder, internally IceSoundCodecDecoderDCT class, a pretty simple DCT codec.
+ *
+ * Header stores one codebook per band (max 16 * channels) of quantized bits, in zlibbed chunks of 4-bit nibbles.
+ * Data is just a bitstream of up to 16 bands (L then R) of variable bit codes (max 16-bit) that depend on previous
+ * 16 bands, dequantized using iDCT. Uses mid-side stereo but otherwise no other features like frames, scalefactors
+ * or overlaps/delay. Samples are encoded directly as +-32768, 16 at a time.
+ */
+
+#define DCT_MAX_BANDS 16
+#define DCT_MAX_TRANSFORM 8
+#define DCT_MAX_PREV 4
+#define DCT_MAX_PREV_MASK 0x3
+#define DCT_CODEBOOK_BUFFER 0x100
+
+typedef struct {
+    const uint8_t* buf;
+    int bitpos;
+    int bitstart;
+    int max_bits;
+} dct_bitreader_t;
+
+typedef struct {
+    uint32_t table_size;
+    uint8_t init_scale;
+    uint8_t bands;
+    uint8_t channels;
+    uint8_t unused;
+    uint32_t max_samples;
+    uint32_t cbk_offset[ICESND_MAX_CHANNELS][DCT_MAX_BANDS];
+    uint32_t cbk_size[ICESND_MAX_CHANNELS][DCT_MAX_BANDS];
+    uint32_t data_start;
+    uint32_t data_size;
+} dct_codeinfo_t;
+
+typedef struct {
+    z_stream strm;
+    uint8_t outbuf[DCT_CODEBOOK_BUFFER];
+    dct_bitreader_t br;
+    int zlib_init;
+} dct_codebook_t;
+
+typedef struct {
+    dct_codeinfo_t codeinfo_mem; /* OG code just casts a pointer to this, keep struct around */
+    dct_codeinfo_t* codeinfo;
+    int samples_done;
+    dct_codebook_t codebook[ICESND_MAX_CHANNELS][DCT_MAX_BANDS];
+    dct_bitreader_t br;
+    float transform[DCT_MAX_TRANSFORM][DCT_MAX_BANDS];
+    float unused[DCT_MAX_TRANSFORM][DCT_MAX_BANDS]; /* ? */
+    int16_t spectra[ICESND_MAX_CHANNELS][DCT_MAX_PREV][DCT_MAX_BANDS];
+    int spectra_curr;
+    int16_t sbuf_tmp[DCT_MAX_BANDS * ICESND_MAX_CHANNELS]; /* interleaved */
+    float scales[16];
+} dct_handle_t;
+
+
+/* ****************************** */
+/* IceSoundCodecDecoder(Bitreader) */
+
+/* OG bitreader uses u32 buf and reads u32le at a time (to simplify shifting), and aligns for 32-bit,
+ * since blocks pointer can start in the middle (whole file loaded in memory + non-padded blocks) */
+
+static void dct_bitreader_init(dct_bitreader_t* ctx) {
+    ctx->buf = NULL;
+    ctx->bitpos = 0;
+    ctx->bitstart = 0;
+    ctx->max_bits = 0;
+}
+
+static void dct_bitreader_set(dct_bitreader_t* ctx, const uint8_t* buf, int buf_size) {
+    //unsigned int* buf32 = (unsigned int *)(buf);
+    //ctx->buf32 = (uin32_t*)((uint32_t)buf32 & 0xFFFFFFFC); /* align to 32-bit boundary */
+    //ctx->bitstart = 8 * ((uint8_t)buf32 & 3); /* align to pointer start */
+
+    ctx->buf = buf;
+    ctx->bitstart = 8 * 0; /* non-aligned ok */
+    ctx->bitpos = ctx->bitstart;
+    ctx->max_bits = 8 * buf_size;
+}
+
+static int dct_bitreader_is_over(dct_bitreader_t* ctx) {
+    return ctx->bitpos >= ctx->bitstart + ctx->max_bits;
+}
+
+static uint32_t dct_bitreader_get(dct_bitreader_t* ctx, int bits) {
+    uint32_t code32;
+    uint8_t shift;
+    int pos;
+    uint32_t mask;
+
+    if (ctx->bitpos + bits > ctx->bitstart + ctx->max_bits) /* ? */
+        return 0;
+
+    pos = ctx->bitpos >> 3;
+    shift = ctx->bitpos & 0x7; /* within u8 */
+
+    code32 = ctx->buf[pos] >> shift;
+    if (bits + shift > 8) {
+        code32 |= ctx->buf[pos+1] << (8u - shift);
+        if (bits + shift > 16) {
+            code32 |= ctx->buf[pos+2] << (16u - shift);
+            if (bits + shift > 24) {
+                code32 |= ctx->buf[pos+3] << (24u - shift);
+                if (bits + shift > 32) {
+                    code32 |= ctx->buf[pos+4] << (32u - shift);
+                }
+            }
+        }
+    }
+
+    //pos = ctx->br.bitpos >> 5;
+    //shift = ctx->br.bitpos & 0x1f; /* within u32 */
+
+    //code32 = ctx->buf32[pos] >> shift;
+    //if (qbits + shift > 32) {
+    //    code32 |= ctx->buf32[pos+1] << (32u - shift);
+    //}
+
+    ctx->bitpos += bits;
+
+    mask = (((1 << bits)) - 1);
+    return code32 & mask;
+}
+
+/* ****************************** */
+/* IceSoundCodecDecoder(Codebook) */
+
+static void dct_codebook_init(dct_codebook_t* ctx) {
+    /* no alloc needed (part of dct_handle_t) */
+
+    dct_bitreader_init(&ctx->br);
+
+    ctx->zlib_init = 0;
+}
+
+static void dct_codebook_close(dct_codebook_t* ctx) {
+    if (!ctx)
+        return;
+
+    zlib_end(&ctx->strm, &ctx->zlib_init);
+    /* no free needed */
+}
+
+static int dct_codebook_reset(dct_codebook_t* ctx, const uint8_t* buf, int buf_size) {
+    int err;
+
+    err = zlib_init(&ctx->strm, &ctx->zlib_init, buf, buf_size);
+    if (err < ICESND_RESULT_OK) return err;
+
+    dct_bitreader_init(&ctx->br);
+
+    return ICESND_RESULT_OK;
+}
+
+/* Read next quantized value's bits from zlibbed codebook. Data is in LSB order and codes 4-bits, like:
+ *  0x10,0x32,0x54,0x76,0x98... = 0 1 2 3 4 5 6 7 8 9... */
+static uint8_t dct_codebook_get_qbits(dct_codebook_t* ctx) {
+    uint32_t qbits;
+
+    if (dct_bitreader_is_over(&ctx->br)) {
+        ctx->strm.avail_out = sizeof(ctx->outbuf);
+        ctx->strm.next_out = ctx->outbuf;
+        inflate(&ctx->strm, Z_NO_FLUSH);
+        //if (err < Z_OK) return ICESND_ERROR_DECODER; /* OG: no error control (shouldn't matter) */
+
+        dct_bitreader_set(&ctx->br, ctx->outbuf, sizeof(ctx->outbuf) - ctx->strm.avail_out);
+    }
+
+    qbits = dct_bitreader_get(&ctx->br, 4);
+
+    return qbits;
+}
+
+/* *********************** */
+/* IceSoundCodecDecoderDCT */
+
+static dct_handle_t* dct_decoder_open() {
+    int i, ch, band;
+    dct_handle_t* ctx = NULL;
+
+    //ctx = calloc(1, sizeof(dct_handle_t));
+    ctx = malloc(sizeof(dct_handle_t));
+    if (!ctx) goto fail;
+
+    ctx->codeinfo = NULL;
+
+    /* init all codebook's base values */
+    for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) {
+        for (band = 0; band < DCT_MAX_BANDS; band++) {
+            dct_codebook_t* codebook = &ctx->codebook[ch][band];
+
+            dct_codebook_init(codebook);
+        }
+    }
+
+    dct_bitreader_init(&ctx->br);
+
+    for (i = 0; i < DCT_MAX_BANDS; i++) {
+        ctx->scales[i] = 1.0f;
+    }
+
+    return ctx;
+fail:
+    return NULL;
+}
+
+static void dct_decoder_close(dct_handle_t* ctx) {
+    int ch, band;
+    
+
+    /* setup all codebook's zlib streams (even if not used, since we can close before anything is set) */
+    for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) {
+        for (band = 0; band < DCT_MAX_BANDS; band++) {
+            dct_codebook_t* codebook = &ctx->codebook[ch][band];
+
+            dct_codebook_close(codebook);
+        }
+    }
+
+    free(ctx);
+}
+
+static int dct_decoder_reset(dct_handle_t* ctx, const uint8_t* buf) {
+    int err;
+    int ch, band;
+    dct_codeinfo_t* ci = ctx->codeinfo;
+
+    /* OG code doesn't pass buf (since reset is a virtual method), but rather codeinfo doubles as a pointer to data start */
+
+    /* close all codebook's zlib streams */
+    for (ch = 0; ch < ci->channels; ch++) {
+        for (band = 0; band < ci->bands; band++) {
+            dct_codebook_t* codebook = &ctx->codebook[ch][band];
+
+            const uint8_t* cbk_start = buf + ci->cbk_offset[ch][band];
+            int cbk_size = ci->cbk_size[ch][band];
+
+            err = dct_codebook_reset(codebook, cbk_start, cbk_size);
+            if (err < ICESND_RESULT_OK) return err;
+        }
+    }
+
+    dct_bitreader_set(&ctx->br, buf + ci->data_start, ci->data_size);
+
+    memset(ctx->spectra, 0, sizeof(ctx->spectra));
+    ctx->samples_done = 0;
+    ctx->spectra_curr = 0;
+
+    return ICESND_RESULT_OK;
+}
+
+/* transform spectrum into samples (iDCT) */
+static void dct_decoder_transform(dct_handle_t* ctx, int16_t* sbuf_tmp, int channel, int pos) {
+    int i, band;
+    float fbuf[16] = {0}; /* no need to init as it's written in band 0 but gcc complains */
+    float f_curr;
+    dct_codeinfo_t* ci = ctx->codeinfo;
+
+
+    for (band = 0; band < ci->bands; band++) {
+        /* scales seems fixed to 1.0, maybe a remnant */
+        float coef = (float)ctx->spectra[channel][pos][band] * ctx->scales[band];
+
+        /* optimized butterfly ops? */
+        switch (band) {
+            case 0:     /* bits 0000 */
+                f_curr = ctx->transform[0][band] * coef;
+                fbuf[0]  = f_curr;
+                fbuf[1]  = f_curr;
+                fbuf[2]  = f_curr;
+                fbuf[3]  = f_curr;
+                fbuf[4]  = f_curr;
+                fbuf[5]  = f_curr;
+                fbuf[6]  = f_curr;
+                fbuf[7]  = f_curr;
+                fbuf[8]  = f_curr;
+                fbuf[9]  = f_curr;
+                fbuf[10] = f_curr;
+                fbuf[11] = f_curr;
+                fbuf[12] = f_curr;
+                fbuf[13] = f_curr;
+                fbuf[14] = f_curr;
+                fbuf[15] = f_curr;
+                break;
+
+            case 1:
+            case 3:
+            case 5:
+            case 7:
+            case 9:
+            case 11:
+            case 13:
+            case 15:    /* bits xxx1 */
+                f_curr = ctx->transform[0][band] * coef;
+                fbuf[0]  += f_curr;
+                fbuf[15] -= f_curr;
+
+                f_curr = ctx->transform[1][band] * coef;
+                fbuf[1]  += f_curr;
+                fbuf[14] -= f_curr;
+
+                f_curr = ctx->transform[2][band] * coef;
+                fbuf[2]  += f_curr;
+                fbuf[13] -= f_curr;
+
+                f_curr = ctx->transform[3][band] * coef;
+                fbuf[3]  += f_curr;
+                fbuf[12] -= f_curr;
+
+                f_curr = ctx->transform[4][band] * coef;
+                fbuf[4]  += f_curr;
+                fbuf[11] -= f_curr;
+
+                f_curr = ctx->transform[5][band] * coef;
+                fbuf[5]  += f_curr;
+                fbuf[10] -= f_curr;
+
+                f_curr = ctx->transform[6][band] * coef;
+                fbuf[6]  += f_curr;
+                fbuf[9]  -= f_curr;
+
+                f_curr = ctx->transform[7][band] * coef;
+                fbuf[7]  += f_curr;
+                fbuf[8]  -= f_curr;
+                break;
+
+            case 2u:
+            case 6u:
+            case 10:
+            case 14:    /* bits xx10 */
+                f_curr = ctx->transform[0][band] * coef;
+                fbuf[0]  += f_curr;
+                fbuf[7]  -= f_curr;
+                fbuf[8]  -= f_curr;
+                fbuf[15] += f_curr;
+
+                f_curr = ctx->transform[1][band] * coef;
+                fbuf[1]  += f_curr;
+                fbuf[6]  -= f_curr;
+                fbuf[9]  -= f_curr;
+                fbuf[14] += f_curr;
+
+                f_curr = ctx->transform[2][band] * coef;
+                fbuf[2]  += f_curr;
+                fbuf[5]  -= f_curr;
+                fbuf[10] -= f_curr;
+                fbuf[13] += f_curr;
+
+                f_curr = ctx->transform[3][band] * coef;
+                fbuf[3]  += f_curr;
+                fbuf[4]  -= f_curr;
+                fbuf[11] -= f_curr;
+                fbuf[12] += f_curr;
+                break;
+
+            case 4:
+            case 12:    /* bits x100 */
+                f_curr = ctx->transform[0][band] * coef;
+                fbuf[0]  += f_curr;
+                fbuf[3]  -= f_curr;
+                fbuf[4]  -= f_curr;
+                fbuf[7]  += f_curr;
+                fbuf[8]  += f_curr;
+                fbuf[11] -= f_curr;
+                fbuf[12] -= f_curr;
+                fbuf[15] += f_curr;
+
+                f_curr = ctx->transform[1][band] * coef;
+                fbuf[1]  += f_curr;
+                fbuf[2]  -= f_curr;
+                fbuf[5]  -= f_curr;
+                fbuf[6]  += f_curr;
+                fbuf[9]  += f_curr;
+                fbuf[10] -= f_curr;
+                fbuf[13] -= f_curr;
+                fbuf[14] += f_curr;
+                break;
+
+            case 8:     /* bits 1000 */
+                f_curr = ctx->transform[0][band] * coef;
+                fbuf[0]  += f_curr;
+                fbuf[1]  -= f_curr;
+                fbuf[2]  -= f_curr;
+                fbuf[3]  += f_curr;
+                fbuf[4]  += f_curr;
+                fbuf[5]  -= f_curr;
+                fbuf[6]  -= f_curr;
+                fbuf[7]  += f_curr;
+                fbuf[8]  += f_curr;
+                fbuf[9]  -= f_curr;
+                fbuf[10] -= f_curr;
+                fbuf[11] += f_curr;
+                fbuf[12] += f_curr;
+                fbuf[13] -= f_curr;
+                fbuf[14] -= f_curr;
+                fbuf[15] += f_curr;
+                break;
+            default:
+                break;
+        }
+    }
+
+    /* copy float samples to sbuf samples */
+    for (i = 0; i < DCT_MAX_BANDS; i++) {
+        float sample = roundf(fbuf[i]);
+        /* interleaved (L: 0,2,4,6,8... R:1,3,5,7...) */
+        sbuf_tmp[channel + ci->channels * i] = (int16_t)sample; /* no clamp */
+    }
+}
+
+/* read current code from the bitstream, in LE byte order */
+static int16_t dct_decoder_get_code(dct_handle_t* ctx, uint8_t qbits) {
+    uint32_t code32;
+    int16_t code16; /* also ok as int32 */
+
+    /* get code from bitstream */
+    if (qbits <= 0) { /* no resolution: 1-bit where 0 = 0 and 1 = -1 */
+        code32 = dct_bitreader_get(&ctx->br, 1);
+
+        code16 = (int16_t)((int16_t)code32 << 15) >> 15; /* sign extend */
+    }
+    else {
+        code32 = dct_bitreader_get(&ctx->br, qbits);
+
+        code16 = code32; /* qbits max 0..15 */
+        if (code16 < (1 << (qbits - 1))) /* negative encoding */
+            code16 = code16 - (1 << qbits);
+    }
+
+    return code16;
+}
+
+/* read codes for this channel */
+static void dct_decoder_dequantize(dct_handle_t* ctx, int channel, int pos) {
+    int band;
+    dct_codeinfo_t* ci = ctx->codeinfo;
+
+    int16_t* spectra = ctx->spectra[channel][pos];
+    int16_t* spectra_prev1 = ctx->spectra[channel][(pos - 1) & DCT_MAX_PREV_MASK];
+    int16_t* spectra_prev2 = ctx->spectra[channel][(pos - 2) & DCT_MAX_PREV_MASK];
+
+
+    for (band = 0; band < ci->bands; band++) {
+        uint8_t qbits; /* common 7~10 bits, sometimes 12 too */
+        int16_t code; /* also ok as int32 */
+
+        /* get next code's resolution and code */
+        qbits = dct_codebook_get_qbits(&ctx->codebook[channel][band]);
+        code = dct_decoder_get_code(ctx, qbits);
+
+        /* calc final value based on previous */
+        spectra[band] = code + (int16_t)(2 * spectra_prev1[band]) - spectra_prev2[band];
+    }
+}
+
+/* restore L/R bands based on mid channel + side differences, ratio 1.0 + copy to final buffer */
+static void dct_decoder_ms_stereo(dct_handle_t* ctx, int16_t* sbuf_tmp) {
+    int i;
+    dct_codeinfo_t* ci = ctx->codeinfo;
+
+    for (i = 0; i < DCT_MAX_BANDS; i++) {
+        int16_t sample_l = sbuf_tmp[0 + ci->channels * i];
+        int16_t sample_r = sbuf_tmp[1 + ci->channels * i];
+        ctx->sbuf_tmp[0 + ci->channels * i] = sample_l + sample_r;
+        ctx->sbuf_tmp[1 + ci->channels * i] = sample_l - sample_r;
+    }
+}
+
+/* decode N samples and copy to sbuf.
+ * Internally decodes 16 samples at a time, and if asked for non-multiple number of samples it'll
+ * stop and resume properly from last copied sample of those 16. Return 1 if no more samples left. */
+static int dct_decoder_decode(dct_handle_t* ctx, int16_t* sbuf, const int max_done, int* p_done) {
+    int ch;
+    int16_t sbuf_loc[DCT_MAX_BANDS * ICESND_MAX_CHANNELS]; /* interleaved */
+    int16_t* sbuf_tmp;
+    dct_codeinfo_t* ci = ctx->codeinfo;
+    int samples_left;
+
+
+    *p_done = 0;
+    samples_left = max_done;
+    if (samples_left > ci->max_samples - ctx->samples_done)
+        samples_left = ci->max_samples - ctx->samples_done;
+
+    /* 2ch uses a tmp buffer to handle MS stereo */
+    if (ci->channels == 1)
+        sbuf_tmp = ctx->sbuf_tmp;
+    else
+        sbuf_tmp = sbuf_loc;
+
+    while (ctx->samples_done < ci->max_samples) {
+
+        if (!samples_left)
+            return ctx->samples_done >= ci->max_samples;
+
+        /* decode 16 samples (every 16 samples) */
+        if ((ctx->samples_done & 0xF) == 0) {
+
+            for (ch = 0; ch < ci->channels; ch++) {
+                dct_decoder_dequantize(ctx, ch, ctx->spectra_curr);
+
+                dct_decoder_transform(ctx, sbuf_tmp, ch, ctx->spectra_curr);
+            }
+
+            ctx->spectra_curr = (ctx->spectra_curr + 1) & DCT_MAX_PREV_MASK; /* 0..3 and back to 0 */
+
+            if (ci->channels == 2)
+                dct_decoder_ms_stereo(ctx, sbuf_tmp);
+        }
+
+        /* copy to output sbuf */
+        {
+            int sample_start;
+            int samples_copied;
+
+            /* start could be non-zero if max_done is non-multiple of 16 */
+            sample_start = ctx->samples_done & 0xF;
+            samples_copied = 16 - sample_start;
+            if (samples_copied > samples_left)
+                samples_copied = samples_left;
+
+            /* copy to output sbuf */
+            memcpy(sbuf, &ctx->sbuf_tmp[sample_start * ci->channels], sizeof(int16_t) * ci->channels * samples_copied);
+            sbuf += samples_copied * ci->channels;
+
+            ctx->samples_done += samples_copied;
+            samples_left -= samples_copied;
+            *p_done += samples_copied;
+        }
+    }
+
+    return ctx->samples_done >= ci->max_samples; /* block done */
+}
+
+
+/* OG code casts buffer to this struct, read in a more portable fashion */
+static int dct_codeinfo_parse(dct_codeinfo_t* ci, const uint8_t* buf, int buf_size) {
+    int ch, i, pos;
+
+    if (buf_size < 0x114)
+        goto fail;
+
+    ci->table_size      = get_u32le(buf + 0x00);
+    ci->init_scale      = get_u8   (buf + 0x04);
+    ci->bands           = get_u8   (buf + 0x05);
+    ci->channels        = get_u8   (buf + 0x06);
+    ci->unused          = get_u8   (buf + 0x07);
+    ci->max_samples     = get_u32le(buf + 0x08);
+
+    pos = 0x0c;
+
+    for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) {
+        for (i = 0; i < DCT_MAX_BANDS; i++) {
+            ci->cbk_offset[ch][i] = get_u32le(buf + pos);
+            pos += 0x04;
+        }
+    }
+
+    for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) {
+        for (i = 0; i < DCT_MAX_BANDS; i++) {
+            ci->cbk_size[ch][i] = get_u32le(buf + pos);
+            pos += 0x04;
+        }
+    }
+
+    ci->data_start      = get_u32le(buf + 0x10c);
+    ci->data_size       = get_u32le(buf + 0x110);
+
+    if (ci->table_size > 0x114)
+        goto fail;
+    if (ci->bands < 1 || ci->bands > DCT_MAX_BANDS)
+        goto fail;
+    if (ci->channels < 1 || ci->channels > ICESND_MAX_CHANNELS)
+        goto fail;
+    if (ci->unused != 0x00)
+        goto fail;
+
+    if (buf_size < ci->data_start + ci->data_size)
+        goto fail;
+
+    return ICESND_RESULT_OK;
+fail:
+    return ICESND_ERROR_SETUP;
+}
+
+/* base DCT unique coefs, used below to init the full table (see opus' analysis.c) */
+static const float DCT_TRANSFORM_COEFS[16] = {
+    0.25f,       0.35185099f, 0.34676f,     0.33832899f,
+    0.32664099f, 0.31180599f, 0.29396901f,  0.27329999f,
+    0.25f,       0.224292f,   0.19642401f,  0.166664f,
+    0.135299f,   0.102631f,   0.068975002f, 0.034653999f,
+};
+
+static const float DCT_TRANSFORM_SCALES[16] = {
+     4.0,  6.0,  8.0, 10.0, 12.0, 12.0, 13.0, 15.0,
+    16.0, 16.0, 20.0, 24.0, 28.0, 35.0, 41.0, 41.0
+};
+
+static const int DCT_TRANSFORM_STEPS[16] = {
+    1, 8, 4, 8, 2, 8, 4, 8,
+    1, 8, 4, 8, 2, 8, 4, 8,
+};
+
+/* re-calculate DCT table, that depends on a current intro/body chunk's scale value */
+static int dct_decoder_block_setup(dct_handle_t* ctx, const uint8_t* buf, int buf_size, bigrp_entry_t* etr) {
+    int i;
+    int err;
+    float scale;
+    float dct_coefs[DCT_MAX_BANDS];
+    dct_codeinfo_t* ci = &ctx->codeinfo_mem;
+
+
+    /* portable init */
+    err = dct_codeinfo_parse(ci, buf, buf_size);
+    if (err < ICESND_RESULT_OK) return err;
+
+    /* pre-calculate scaled coefs (mini optimization?) */
+    scale = ci->init_scale;
+    for (i = 0; i < DCT_MAX_BANDS; i++) {
+        dct_coefs[i] = DCT_TRANSFORM_COEFS[i] * scale;
+    }
+
+    /* transform for N=16, k=0..8? */
+    for (i = 0; i < DCT_MAX_BANDS; i++) {
+        int steps = DCT_TRANSFORM_STEPS[i];
+        int step;
+        int pos = i;
+
+        for (step = 0; step < steps; step++) {
+            float coef;
+
+            switch ((pos >> 4) & 3) {
+                case 1:
+                    coef = -dct_coefs[16 - (pos & 0xF)];
+                    break;
+                case 2:
+                    coef = -dct_coefs[(pos & 0xF)];
+                    break;
+                case 3:
+                    coef = +dct_coefs[16 - (pos & 0xF)];
+                    break;
+                default:
+                    coef = +dct_coefs[(pos & 0xF)];
+                    break;
+            }
+            pos += 2 * i;
+
+            //ctx->transform[step][i] = coef; /* somehow assigned twice originally? */
+            ctx->transform[step][i] = DCT_TRANSFORM_SCALES[i] * coef;
+        }
+    }
+
+
+    /* rest of setup */
+    ctx->codeinfo = ci;
+
+    err = dct_decoder_reset(ctx, buf);
+    if (err < ICESND_RESULT_OK) return err;
+
+    return ICESND_RESULT_OK;
+}
+
+/* ************************************************************ */
+/* API */
+/* ************************************************************ */
+/* (not part of original code (but partially inspired by IceSSoundEng::IcePlayer) */
+
+#define ICESND_BIGRP_SIZE 0x10
+#define ICESND_ENTRY_SIZE 0x34
+#define ICESND_BUF_SIZE 0x10000
+
+
+struct icesnd_handle_t {
+    /* config*/
+    int target_subsong;
+    icesnd_callback_t cb;
+
+    /* state */
+    bigrp_header_t hdr;
+    bigrp_entry_t etr;
+
+    void* decoder;
+    int is_range;
+
+    int intro_init;
+    int body_init;
+    int intro_done;
+
+    /* absolute offset */
+    int intro_offset;
+    int body_offset;
+
+    uint8_t* blkbuf;
+    int blkbuf_size;
+};
+
+
+static int parse_header(icesnd_handle_t* ctx) {
+    int err;
+    uint8_t tmp[0x40];
+    const uint8_t* buf;
+    int buf_size;
+    uint32_t offset;
+
+
+    /* read common header size */
+    offset = 0x00;
+    if (ctx->cb.read) {
+        ctx->cb.seek(ctx->cb.arg, offset, SEEK_SET);
+        buf_size = ctx->cb.read(tmp, 1, 0x10, ctx->cb.arg);
+        buf = tmp;
+    }
+    else {
+        buf_size = ctx->cb.filebuf_size;
+        buf = ctx->cb.filebuf + offset;
+    }
+
+    err = bigrp_header_parse(&ctx->hdr, buf, buf_size, ctx->target_subsong);
+    if (err < ICESND_RESULT_OK) goto fail;
+
+    /* read target entry */
+    offset = ctx->hdr.head_size + ctx->hdr.entry_size * (ctx->target_subsong - 1);
+    if (ctx->cb.read) {
+        ctx->cb.seek(ctx->cb.arg, offset, SEEK_SET);
+        buf_size = ctx->cb.read(tmp, 1, ctx->hdr.entry_size, ctx->cb.arg);
+        buf = tmp;
+    }
+    else {
+        if (offset > ctx->cb.filebuf_size) goto fail;
+        buf = ctx->cb.filebuf + offset;
+        buf_size = ctx->cb.filebuf_size - offset;
+    }
+
+    err = bigrp_entry_parse(&ctx->etr, buf, buf_size);
+    if (err < ICESND_RESULT_OK) goto fail;
+
+    if (ctx->etr.codec == ICESND_CODEC_RANGE || ctx->etr.codec == ICESND_CODEC_DCT) {
+        ctx->intro_offset = offset + ctx->etr.intro_offset;
+        ctx->body_offset = offset + ctx->etr.body_offset;
+    }
+
+
+    //TODO fix library later
+    // see comment at top, but basically format is rather annoying to adapt as a streaming
+    // decoder, and ran out of time. For now it reads a whole blocks (intro/body) at once. Sorry!
+
+    /* prepare buf */
+    if (ctx->cb.read) {
+        int block_size = ctx->etr.body_zsize;
+        if (block_size < ctx->etr.intro_zsize)
+            block_size = ctx->etr.intro_zsize;
+        if (block_size % 0x10 != 0) /* pad just in case */
+            block_size = block_size + (0x10 - (block_size % 0x10));
+        
+        ctx->blkbuf_size = block_size;
+        ctx->blkbuf = malloc(block_size);
+        if (!ctx->blkbuf) goto fail;
+    }
+
+    return ICESND_RESULT_OK;
+fail:
+    return ICESND_ERROR_SETUP;
+}
+
+icesnd_handle_t* icesnd_init(int target_subsong, icesnd_callback_t* cb) {
+    icesnd_handle_t* ctx = NULL;
+    int err;
+
+    ctx = calloc(1, sizeof(icesnd_handle_t));
+    if (!ctx) goto fail;
+
+    ctx->target_subsong = target_subsong;
+    ctx->cb = *cb; /* memcpy */
+
+    if (!cb->filebuf && !(cb->read && cb->seek))
+        goto fail;
+
+    err = parse_header(ctx);
+    if (err < ICESND_RESULT_OK) goto fail;
+
+    ctx->is_range = ctx->etr.codec == 0x00;
+    if (ctx->is_range)
+        ctx->decoder = range_decoder_open();
+    else
+        ctx->decoder = dct_decoder_open();
+    if (!ctx->decoder) goto fail;
+
+    icesnd_reset(ctx, 0);
+
+    return ctx;
+fail:
+    icesnd_free(ctx);
+    return NULL;
+    //return ICESND_ERROR_SETUP;
+}
+
+void icesnd_free(icesnd_handle_t* ctx) {
+    if (!ctx)
+        return;
+
+    if (ctx->decoder) {
+        if (ctx->is_range)
+            range_decoder_close(ctx->decoder);
+        else
+            dct_decoder_close(ctx->decoder);
+    }
+
+    free(ctx->blkbuf);
+    free(ctx);
+}
+
+int icesnd_info(icesnd_handle_t* ctx, icesnd_info_t* info) {
+    if (!ctx)
+        goto fail;
+
+    info->total_subsongs = ctx->hdr.total_subsongs;
+    info->codec = ctx->etr.codec;
+    info->sample_rate = ctx->etr.sample_rate;
+    info->channels =  ctx->etr.channels;
+    info->loop_start = ctx->etr.intro_samples;
+    info->num_samples = ctx->etr.intro_samples + ctx->etr.body_samples;
+    info->loop_flag = ctx->etr.loop_flag;
+
+    return ICESND_RESULT_OK;
+fail:
+    return ICESND_ERROR_DECODE;
+}
+
+void icesnd_reset(icesnd_handle_t* ctx, int loop_start) {
+    if (!ctx || !ctx->decoder)
+        return;
+
+    ctx->intro_init = 0;
+    ctx->body_init = 0;
+    ctx->intro_done = 0;
+
+    /* skip intro block in some cases */
+    if (ctx->etr.intro_samples == 0 || loop_start != 0)
+        ctx->intro_done = 1;
+
+    /* no need to reset decoder as will be done when block is set, plus
+     * only reset properly when doing that */
+}
+
+static int setup_block(icesnd_handle_t* ctx, int intro) {
+    int err;
+    const uint8_t* buf;
+    int buf_size;
+    int block_offset = (intro ? ctx->intro_offset : ctx->body_offset);
+    int block_size = (intro ? ctx->etr.intro_zsize : ctx->etr.body_zsize);
+    int block_samples = (intro ? ctx->etr.intro_samples : ctx->etr.body_samples);
+
+    if (ctx->cb.read) {
+        /* could optimize by ignoring calls (intro > body > body...) but this kinda simulates streamings */
+        if (block_size > ctx->blkbuf_size) /* can't happen but anyway */
+            return ICESND_ERROR_DECODE;
+        ctx->cb.seek(ctx->cb.arg, block_offset, SEEK_SET);
+        buf_size = ctx->cb.read(ctx->blkbuf, 1, block_size, ctx->cb.arg);        
+        buf = ctx->blkbuf;
+    }
+    else {
+        if (ctx->cb.filebuf_size < block_offset + block_size)
+            return ICESND_ERROR_DECODE;
+        buf = ctx->cb.filebuf + block_offset;
+        buf_size = ctx->cb.filebuf_size - block_offset;
+    }
+
+
+    if (ctx->is_range) {
+        err = range_decoder_block_setup(ctx->decoder, buf, buf_size, &ctx->etr, block_samples);
+    }
+    else {
+        err = dct_decoder_block_setup(ctx->decoder, buf, buf_size, &ctx->etr); /* max_samples info is in codebook table */
+    }
+    if (err < ICESND_RESULT_OK) return err;
+
+    return ICESND_RESULT_OK;
+}
+
+int icesnd_decode(icesnd_handle_t* ctx, int16_t* sbuf, int max_samples) {
+    int err;
+    int samples_done, block_end;
+    int samples_decoded = 0;
+    if (!ctx)
+        goto fail;
+
+    while (max_samples > 0) {
+
+        if (!ctx->intro_done) {
+            if (!ctx->intro_init) {
+                err = setup_block(ctx, 1);
+                if (err < ICESND_RESULT_OK) return err;
+
+                ctx->intro_init = 1;
+            }
+        }
+        else {
+            if (!ctx->body_init) {
+                err = setup_block(ctx, 0);
+                ctx->body_init = 1;
+            }
+        }
+
+        if (ctx->is_range)
+            block_end = range_decoder_decode(ctx->decoder, sbuf, max_samples, &samples_done);
+        else
+            block_end = dct_decoder_decode(ctx->decoder, sbuf, max_samples, &samples_done);
+
+        max_samples -= samples_done;
+        samples_decoded += samples_done;
+        sbuf += samples_done * ctx->etr.channels;
+        //ctx->curr_sample += samples_done; /* original keeps this around to test if intro block is done */
+
+        if (block_end) {
+            /* after first block (could check if this is the first block but whatevs) */
+            ctx->intro_done = 1;
+
+            /* intro end, or after body end to allow loops on next calls */
+            if (ctx->etr.loop_flag)
+                ctx->body_init = 0;
+        }
+
+        /* could be possible on block end if not reset */
+        if (samples_done == 0)
+            break;
+
+        /* stop on on block boundary to ensure external caller may stop on loop end (could go on otherwise) */
+        if (block_end)
+            break;
+    }
+
+
+    return samples_decoded;
+fail:
+    return ICESND_ERROR_DECODE;
+}
diff --git a/src/coding/ice_decoder_icelib.h b/src/coding/ice_decoder_icelib.h
new file mode 100644
index 00000000..bcfb40cd
--- /dev/null
+++ b/src/coding/ice_decoder_icelib.h
@@ -0,0 +1,67 @@
+#ifndef _ICELIB_H_
+#define _ICELIB_H_
+#include <stdint.h>
+
+/* Decodes Inti Creates's "ICE" engine BIGRP sounds. */
+
+#define ICESND_CODEC_RANGE     0x00
+#define ICESND_CODEC_DATA      0x01
+#define ICESND_CODEC_MIDI      0x02
+#define ICESND_CODEC_DCT       0x03
+
+#define ICESND_RESULT_OK       0
+#define ICESND_ERROR_HEADER    -1
+#define ICESND_ERROR_SETUP     -2
+#define ICESND_ERROR_DECODE    -3
+
+typedef struct icesnd_handle_t icesnd_handle_t;
+
+#define ICESND_SEEK_SET    0
+#define ICESND_SEEK_CUR    1
+#define ICESND_SEEK_END    2
+
+typedef struct {
+    /* whole file in memory (for testing) */
+    const uint8_t* filebuf;
+    int filebuf_size;
+
+    /* custom IO */
+	void* arg;
+    int (*read)(void* dst, int size, int n, void* arg);
+	int (*seek)(void* arg, int offset, int whence);
+
+} icesnd_callback_t;
+
+/* Inits ICE lib with config.
+ * Original code expects all data in memory, but this allows setting read callbacks
+ * (making it feed-style was a bit complex due to how data is laid out) */
+icesnd_handle_t* icesnd_init(int target_subsong, icesnd_callback_t* cb);
+
+void icesnd_free(icesnd_handle_t* handle);
+
+/* resets the decoder. If loop_starts and file loops and 
+ * (format is not seekable but separated into intro+body blocks) */
+void icesnd_reset(icesnd_handle_t* handle, int loop_start);
+
+/* Decodes up to samples for N channels into sbuf (interleaved). Returns samples done, 
+ * 0 if not possible (non-looped files past end) or negative on error.
+ * May return less than wanted samples on block boundaries.
+ * 
+ * It's designed to decode an arbitrary number of samples, as data isn't divided into frames (original
+ * player does sample_rate/60.0 at a time). Codec 0 is aligned to 100 samples and codec 3 to 16 though. */
+int icesnd_decode(icesnd_handle_t* handle, int16_t* sbuf, int max_samples);
+
+typedef struct {
+    int total_subsongs;
+    int codec;
+    int sample_rate;
+    int channels;
+    int loop_start;
+    int num_samples;
+    int loop_flag;
+} icesnd_info_t;
+
+/* loads info */
+int icesnd_info(icesnd_handle_t* handle, icesnd_info_t* info);
+
+#endif
diff --git a/src/decode.c b/src/decode.c
index bdd53cef..aec74b23 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -39,6 +39,11 @@ void free_codec(VGMSTREAM* vgmstream) {
         free_tac(vgmstream->codec_data);
     }
 
+    if (vgmstream->coding_type == coding_ICE_RANGE ||
+        vgmstream->coding_type == coding_ICE_DCT) {
+        free_ice(vgmstream->codec_data);
+    }
+
     if (vgmstream->coding_type == coding_UBI_ADPCM) {
         free_ubi_adpcm(vgmstream->codec_data);
     }
@@ -140,6 +145,11 @@ void seek_codec(VGMSTREAM* vgmstream) {
         seek_tac(vgmstream->codec_data, vgmstream->loop_current_sample);
     }
 
+    if (vgmstream->coding_type == coding_ICE_RANGE ||
+        vgmstream->coding_type == coding_ICE_DCT) {
+        seek_ice(vgmstream->codec_data, vgmstream->loop_current_sample);
+    }
+
     if (vgmstream->coding_type == coding_UBI_ADPCM) {
         seek_ubi_adpcm(vgmstream->codec_data, vgmstream->loop_current_sample);
     }
@@ -246,6 +256,11 @@ void reset_codec(VGMSTREAM* vgmstream) {
         reset_tac(vgmstream->codec_data);
     }
 
+    if (vgmstream->coding_type == coding_ICE_RANGE ||
+        vgmstream->coding_type == coding_ICE_DCT) {
+        reset_ice(vgmstream->codec_data);
+    }
+
     if (vgmstream->coding_type == coding_UBI_ADPCM) {
         reset_ubi_adpcm(vgmstream->codec_data);
     }
@@ -535,6 +550,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
             return 0; /* 1024 - delay/padding (which can be bigger than 1024) */
         case coding_TAC:
             return 0; /* 1024 - delay/padding */
+        case coding_ICE_RANGE:
+        case coding_ICE_DCT:
+            return 0; /* ~100 (range), ~16 (DCT) */
 #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
         case coding_MP4_AAC:
             return ((mp4_aac_codec_data*)vgmstream->codec_data)->samples_per_frame;
@@ -1075,6 +1093,10 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
         case coding_TAC:
             decode_tac(vgmstream, buffer, samples_to_do);
             break;
+        case coding_ICE_RANGE:
+        case coding_ICE_DCT:
+            decode_ice(vgmstream->codec_data, buffer, samples_to_do);
+            break;
 #ifdef VGM_USE_FFMPEG
         case coding_FFmpeg:
             decode_ffmpeg(vgmstream, buffer, samples_to_do, vgmstream->channels);
diff --git a/src/formats.c b/src/formats.c
index 5520ba36..e9d95659 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -105,6 +105,7 @@ static const char* extension_list[] = {
     "bgm",
     "bgw",
     "bh2pcm",
+    "bigrp",
     "bik",
     "bika", //fake extension for .bik (to be removed)
     "bik2",
@@ -861,6 +862,8 @@ static const coding_info coding_info_list[] = {
         {coding_RELIC,              "Relic Codec"},
         {coding_CRI_HCA,            "CRI HCA"},
         {coding_TAC,                "tri-Ace Codec"},
+        {coding_ICE_RANGE,          "Inti Creates Range Codec"},
+        {coding_ICE_DCT,            "Inti Creates DCT Codec"},
 
 #ifdef VGM_USE_VORBIS
         {coding_OGG_VORBIS,         "Ogg Vorbis"},
@@ -1415,6 +1418,7 @@ static const meta_info meta_info_list[] = {
         {meta_TT_AD,                "Traveller's Tales AUDIO_DATA header"},
         {meta_SNDZ,                 "Sony SNDZ header"},
         {meta_VAB,                  "Sony VAB header"},
+        {meta_BIGRP,                "Inti Creates .BIGRP header"},
 };
 
 void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index a66f39a9..ed81595c 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -83,6 +83,7 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClInclude Include="coding\hca_decoder_clhca.h" />
+    <ClInclude Include="coding\ice_decoder_icelib.h" />
     <ClInclude Include="coding\mpeg_bitreader.h" />
     <ClInclude Include="coding\mpeg_decoder.h" />
     <ClInclude Include="coding\vorbis_bitreader.h" />
@@ -328,6 +329,7 @@
     <ClCompile Include="meta\baf.c" />
     <ClCompile Include="meta\bgw.c" />
     <ClCompile Include="meta\bik.c" />
+    <ClCompile Include="meta\bigrp.c" />
     <ClCompile Include="meta\bkhd.c" />
     <ClCompile Include="meta\bmp_konami.c" />
     <ClCompile Include="meta\bnk_relic.c" />
@@ -643,6 +645,8 @@
     <ClCompile Include="coding\g7221_decoder_lib.c" />
     <ClCompile Include="coding\hca_decoder.c" />
     <ClCompile Include="coding\hca_decoder_clhca.c" />
+    <ClCompile Include="coding\ice_decoder.c" />
+    <ClCompile Include="coding\ice_decoder_icelib.c" />
     <ClCompile Include="coding\ima_decoder.c" />
     <ClCompile Include="coding\imuse_decoder.c" />
     <ClCompile Include="coding\l5_555_decoder.c" />
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index ff066c19..c0fd68fe 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -266,6 +266,9 @@
     <ClInclude Include="coding\hca_decoder_clhca.h">
       <Filter>coding\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="coding\hca_decoder_icelib.h">
+      <Filter>coding\Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="coding\mpeg_bitreader.h">
       <Filter>coding\Header Files</Filter>
     </ClInclude>
@@ -1369,6 +1372,12 @@
     <ClCompile Include="coding\hca_decoder_clhca.c">
       <Filter>coding\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="coding\ice_decoder.c">
+      <Filter>coding\Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="coding\ice_decoder_icelib.c">
+      <Filter>coding\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="coding\ima_decoder.c">
       <Filter>coding\Source Files</Filter>
     </ClCompile>
@@ -1879,6 +1888,9 @@
     <ClCompile Include="meta\bik.c">
       <Filter>meta\Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="meta\bigrp.c">
+      <Filter>meta\Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="meta\bkhd.c">
       <Filter>meta\Source Files</Filter>
     </ClCompile>
diff --git a/src/meta/bigrp.c b/src/meta/bigrp.c
new file mode 100644
index 00000000..38502044
--- /dev/null
+++ b/src/meta/bigrp.c
@@ -0,0 +1,110 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "../coding/ice_decoder_icelib.h"
+
+
+/* .BIGRP - from Inti Creates "ICE"/"Imperial" engine [Blaster Master Zero 2 (SW), Gunvolt 3 (SW)] */
+VGMSTREAM* init_vgmstream_bigrp(STREAMFILE* sf) {
+    VGMSTREAM* vgmstream = NULL;
+    uint32_t header_size, entry_size, stream_size;
+    int total_subsongs, target_subsong = sf->stream_index;
+    int codec, channels, loop_flag, sample_rate;
+    int32_t loop_start, num_samples;
+
+
+    /* checks */
+
+    /* simple checks to avoid opening the lib if possible
+     * early games use smaller sizes [Bloodstained COTM (Vita), Mighty Gunvolt Burst (PC)] */
+    header_size = read_u32le(0x00,sf);
+    if (read_u32le(0x00,sf) != 0x0c && read_u32le(0x00,sf) != 0x10)
+        goto fail;
+    entry_size = read_u32le(0x04,sf);
+    if (entry_size != 0x34 && entry_size != 0x40)
+        goto fail;
+
+    if (!check_extensions(sf, "bigrp"))
+        goto fail;
+
+    if (target_subsong == 0) target_subsong = 1;
+    total_subsongs = read_s32le(0x08,sf);
+    if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail;
+
+
+    /* could read all this from the lib, meh */
+    {
+        uint32_t offset = header_size + entry_size * (target_subsong - 1);
+
+        /* 00: hash */
+        /* 04: group? */
+        codec   = read_u32le(offset + 0x08, sf);
+
+        switch(codec) {
+            case 0x00: /* range [BMZ2 (SW), Bloodstained COTM (Vita), BMZ1 (PS4)] */
+            case 0x03:
+                sample_rate = read_s32le(offset + 0x0c, sf);
+                channels    = read_u8   (offset + 0x10, sf);
+                /* 0x11: spf */
+                /* 0x12: unknown (volume?) */
+                loop_flag   = read_u32le(offset + 0x14, sf);
+                /* 0x18: frame codes */
+                loop_start  = read_s32le(offset + 0x1c, sf);
+                stream_size = read_u32le(offset + 0x20, sf); /* intro block */
+                /* 0x24: intro offset */
+                num_samples = read_s32le(offset + 0x28, sf) + loop_start;
+                stream_size = read_u32le(offset + 0x2c, sf) + stream_size; /* body block */
+                /* 0x30: body offset */
+                break;
+
+            default:
+                /* dummy to avoid breaking some files that mix codecs with midi */
+                channels =  1;
+                sample_rate = 48000;
+                loop_flag = 0;
+                loop_start = 0;
+                num_samples = sample_rate;
+                stream_size = 0;
+                break;
+        }
+    }
+
+
+    /* build the VGMSTREAM */
+    vgmstream = allocate_vgmstream(channels, loop_flag);
+    if (!vgmstream) goto fail;
+
+    vgmstream->meta_type = meta_BIGRP;
+    vgmstream->sample_rate = sample_rate;
+    vgmstream->num_samples = num_samples;
+    vgmstream->loop_start_sample = loop_start;
+    vgmstream->loop_end_sample = num_samples;
+    vgmstream->num_streams = total_subsongs;
+    vgmstream->stream_size = stream_size;
+
+    switch(codec) {
+        case 0x00:
+        case 0x03:
+            vgmstream->codec_data = init_ice(sf, target_subsong);
+            if (!vgmstream->codec_data) goto fail;
+            vgmstream->coding_type = codec == 0x00 ? coding_ICE_RANGE : coding_ICE_DCT;
+            vgmstream->layout_type = layout_none;
+            break;
+
+        case 0x01:
+        case 0x02:
+            vgmstream->coding_type = coding_SILENCE;
+            snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "[%s]", (codec == 0x01 ? "data" : "midi"));
+            break;
+
+        default:
+            goto fail;
+    }
+
+    //if (!vgmstream_open_stream(vgmstream, sf, 0x00))
+    //    goto fail;
+    return vgmstream;
+
+fail:
+    close_vgmstream(vgmstream);
+    return NULL;
+}
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 28ad4377..deaedab0 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -991,4 +991,6 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf);
 
 VGMSTREAM* init_vgmstream_vab(STREAMFILE* sf);
 
+VGMSTREAM* init_vgmstream_bigrp(STREAMFILE* sf);
+
 #endif /*_META_H*/
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 14974ac6..58f7187e 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -525,6 +525,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
     init_vgmstream_bw_riff_mp3,
     init_vgmstream_sndz,
     init_vgmstream_vab,
+    init_vgmstream_bigrp,
 
     /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
     init_vgmstream_agsc,
diff --git a/src/vgmstream.h b/src/vgmstream.h
index b28e266c..962db34e 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -185,6 +185,8 @@ typedef enum {
     coding_RELIC,           /* Relic Codec (DCT-based) */
     coding_CRI_HCA,         /* CRI High Compression Audio (MDCT-based) */
     coding_TAC,             /* tri-Ace Codec (MDCT-based) */
+    coding_ICE_RANGE,       /* Inti Creates "range" codec */
+    coding_ICE_DCT,         /* Inti Creates "DCT" codec */
 
 #ifdef VGM_USE_VORBIS
     coding_OGG_VORBIS,      /* Xiph Vorbis with Ogg layer (MDCT-based) */
@@ -765,6 +767,7 @@ typedef enum {
     meta_TT_AD,
     meta_SNDZ,
     meta_VAB,
+    meta_BIGRP,
 
 } meta_t;