#include "meta.h"
#include "../coding/coding.h"
#include "../util/endianness.h"

typedef enum { MFX, MFX_BANK, SFX_BANK, SBNK, FORM } musx_form;
typedef enum { PSX, DSP, XBOX, IMA, DAT, NGCA, PCM } musx_codec;
typedef struct {
    int big_endian;
    int version;
    size_t file_size;

    int total_subsongs;
    int is_old;

    off_t tables_offset;
    off_t loops_offset;
    off_t stream_offset;
    size_t stream_size;
    off_t coefs_offset;

    musx_form form;
    musx_codec codec;
    uint32_t platform;

    int channels;
    int sample_rate;
    int loop_flag;
    int32_t loop_start;
    int32_t loop_end;
    int32_t num_samples;
    int32_t loop_start_sample;
    int32_t loop_end_sample;
} musx_header;

static int parse_musx(STREAMFILE* sf, musx_header* musx);

/* MUSX - from Eurocom's games */
VGMSTREAM* init_vgmstream_musx(STREAMFILE* sf) {
    VGMSTREAM* vgmstream = NULL;
    off_t start_offset;
    musx_header musx = {0};


    /* checks */
    /* .sfx: actual extension (extracted from bigfiles with sometimes encrypted names)
     * .musx: header id */
    if (!check_extensions(sf, "sfx,musx"))
        goto fail;
    if (!is_id32be(0x00,sf, "MUSX"))
        goto fail;

    if (!parse_musx(sf, &musx))
        goto fail;


    start_offset = musx.stream_offset;

    /* build the VGMSTREAM */
    vgmstream = allocate_vgmstream(musx.channels, musx.loop_flag);
    if (!vgmstream) goto fail;

    vgmstream->meta_type = meta_MUSX;
    vgmstream->sample_rate = musx.sample_rate;
    vgmstream->num_streams = musx.total_subsongs;
    vgmstream->stream_size = musx.stream_size;

    switch (musx.codec) {
        case PSX:
            vgmstream->num_samples = ps_bytes_to_samples(musx.stream_size, musx.channels);
            vgmstream->loop_start_sample = ps_bytes_to_samples(musx.loop_start, musx.channels);
            vgmstream->loop_end_sample = ps_bytes_to_samples(musx.loop_end, musx.channels);

            vgmstream->coding_type = coding_PSX;
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = 0x80;
            break;

        case DAT:
            vgmstream->num_samples = dat4_ima_bytes_to_samples(musx.stream_size, musx.channels);
            vgmstream->loop_start_sample = dat4_ima_bytes_to_samples(musx.loop_start, musx.channels);
            vgmstream->loop_end_sample = dat4_ima_bytes_to_samples(musx.loop_end, musx.channels);

            vgmstream->coding_type = coding_DAT4_IMA;
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = 0x20;
            break;

        case IMA:
            vgmstream->num_samples = ima_bytes_to_samples(musx.stream_size, musx.channels);
            vgmstream->loop_start_sample = musx.loop_start / 4; /* weird but needed */
            vgmstream->loop_end_sample = musx.loop_end / 4;

            vgmstream->coding_type = coding_DVI_IMA_int;
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = 0x01;
            break;

        case XBOX:
            vgmstream->num_samples = xbox_ima_bytes_to_samples(musx.stream_size, musx.channels);
            vgmstream->loop_start_sample = xbox_ima_bytes_to_samples(musx.loop_start, musx.channels);
            vgmstream->loop_end_sample = xbox_ima_bytes_to_samples(musx.loop_end, musx.channels);

            vgmstream->coding_type = coding_XBOX_IMA;
            vgmstream->layout_type = layout_none;
            break;

        case DSP:
            vgmstream->num_samples = dsp_bytes_to_samples(musx.stream_size, musx.channels);
            vgmstream->loop_start_sample = dsp_bytes_to_samples(musx.loop_start, musx.channels);
            vgmstream->loop_end_sample = dsp_bytes_to_samples(musx.loop_end, musx.channels);

            vgmstream->coding_type = coding_NGC_DSP;
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = 0x08;

            dsp_read_coefs(vgmstream,sf,musx.coefs_offset+0x1c, 0x60, musx.big_endian);
            dsp_read_hist(vgmstream,sf,musx.coefs_offset+0x40, 0x60, musx.big_endian);
            break;

        case PCM:
            //vgmstream->num_samples = pcm_bytes_to_samples(musx.stream_size, musx.channels, 16);
            //vgmstream->loop_start_sample = pcm_bytes_to_samples(musx.loop_start, musx.channels, 16);
            //vgmstream->loop_end_sample = pcm_bytes_to_samples(musx.loop_end, musx.channels, 16);

            vgmstream->coding_type = musx.big_endian ? coding_PCM16BE : coding_PCM16LE; /* only seen on PC but just in case */
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = 0x02;
            break;

        case NGCA:
            if (!is_id32be(start_offset,sf, "NGCA"))
                goto fail;
            /* 0x04: size (stereo full-interleaves 2 NGCA headers but sizes count all) */
            /* 0x08: channels */
            /* 0x0c: coefs */
            musx.coefs_offset = start_offset;
            start_offset += 0x40;
            //musx.stream_size -= 0x40 * musx.channels; /* needed for interleave */

            //vgmstream->num_samples = dsp_bytes_to_samples(musx.stream_size - 0x40 * musx.channels, musx.channels);
            //vgmstream->loop_start_sample = dsp_bytes_to_samples(musx.loop_start + 0x40, musx.channels);
            //vgmstream->loop_end_sample = dsp_bytes_to_samples(musx.loop_end - 0x40 * musx.channels, musx.channels);

            vgmstream->coding_type = coding_NGC_DSP;
            vgmstream->layout_type = layout_interleave;
            vgmstream->interleave_block_size = musx.stream_size / musx.channels;

            dsp_read_coefs(vgmstream, sf, musx.coefs_offset+0x0c, vgmstream->interleave_block_size, musx.big_endian);
            //dsp_read_hist(vgmstream, sf, musx.coefs_offset+0x30, 0x00, musx.big_endian); /* used? */

            break;

        default:
            goto fail;
    }

    if (musx.num_samples)
        vgmstream->num_samples = musx.num_samples;
    if (musx.loop_flag && musx.loop_start_sample)
        vgmstream->loop_start_sample = musx.loop_start_sample;
    if (musx.loop_flag && musx.loop_end_sample)
        vgmstream->loop_end_sample = musx.loop_end_sample;

    if (!vgmstream_open_stream(vgmstream,sf,start_offset))
        goto fail;
    return vgmstream;

fail:
    close_vgmstream(vgmstream);
    return NULL;
}


static int parse_musx_stream(STREAMFILE* sf, musx_header* musx) {
    uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
    int default_channels, default_sample_rate;

    if (musx->big_endian) {
        read_u32 = read_u32be;
    } else {
        read_u32 = read_u32le;
    }


    /* autodetect for older versions that have no info */
    if (musx->platform == 0) {
        if (musx->big_endian) {
            musx->platform = get_id32be("GC02"); /* (fake) */
        }
        else {
            int channels = musx->channels;
            off_t offset = musx->stream_offset;
            size_t max = 0x5000;
            if (max > musx->stream_size)
                max = musx->stream_size;
            if (!channels)
                channels = 2;

            /* since engine seems to hardcode codecs no apparent way to detect in some cases
             * [Sphinx and the Cursed Mummy (multi), Buffy the Vampire Slayer: Chaos Bleeds (multi)] */
            if (ps_check_format(sf, offset, max)) {
                musx->platform = get_id32be("PS2_");
            } else if (xbox_check_format(sf, offset, max, channels)) {
                musx->platform = get_id32be("XB02"); /* (fake) */
            }
            else {
                musx->platform = get_id32be("PC02"); /* (fake) */
            }
        }
    }


    /* defaults */
    switch(musx->platform) {

        case 0x5053325F: /* "PS2_" */
            default_channels = 2;
            default_sample_rate = 32000;
            musx->codec = PSX;
            break;

        case 0x47435F5F: /* "GC__" */
            default_channels = 2;
            default_sample_rate = 32000;
            musx->codec = DAT;
            break;

        case 0x47433032: /* "GC02" */
            default_channels = 2;
            default_sample_rate = 32000;
            if (musx->coefs_offset)
                musx->codec = DSP;
            else
                musx->codec = IMA;
            break;

        case 0x58425F5F: /* "XB__" */
            default_channels = 2;
            default_sample_rate = 44100;
            musx->codec = DAT;
            break;

        case 0x58423032: /* "XB02" */
            default_channels = 2;
            default_sample_rate = 44100;
            musx->codec = XBOX;
            break;

        case 0x5053505F: /* "PSP_" */
            default_channels = 2;
            default_sample_rate = 32768;
            musx->codec = PSX;
            break;

        case 0x5749495F: /* "WII_" */
            default_channels = 2;
            default_sample_rate = 44100;
            musx->codec = DAT;
            break;

        case 0x5053335F: /* "PS3_" */
            default_channels = 2;
            default_sample_rate = 44100;
            musx->codec = DAT;
            break;

        case 0x58455F5F: /* "XE__" */
            default_channels = 2;
            default_sample_rate = 32000;
            musx->codec = DAT;
            break;

        case 0x50435F5F: /* "PC__" */
            default_channels = 2;
            default_sample_rate = 44100;
            musx->codec = DAT;
            break;

        case 0x50433032: /* "PC02" */
            default_channels = 2;
            default_sample_rate = 32000;
            musx->codec = IMA;
            break;

        default:
            VGM_LOG("MUSX: unknown platform %x\n", musx->platform);
            goto fail;
    }

    if (musx->channels == 0)
        musx->channels = default_channels;
    if (musx->sample_rate == 0)
        musx->sample_rate = default_sample_rate;


    /* parse loops and other info */
    if (musx->tables_offset && musx->loops_offset) {
        int i, cues2_count;
        off_t cues2_offset;

        /* cue/stream position table thing */
        /* 0x00: cues1 entries (entry size 0x34 or 0x18)
         * 0x04: cues2 entries (entry size 0x20 or 0x14)
         * 0x08: header size (always 0x14)
         * 0x0c: cues2 start
         * 0x10: volume? (usually <= 100) */

        /* find loops (cues1 also seems to have this info but this looks ok) */
        cues2_count = read_u32(musx->loops_offset+0x04, sf);
        cues2_offset = musx->loops_offset + read_u32(musx->loops_offset+0x0c, sf);
        for (i = 0; i < cues2_count; i++) {
            uint32_t type, offset1, offset2;

            if (musx->is_old) {
                offset1 = read_u32(cues2_offset + i*0x20 + 0x04, sf);
                type    = read_u32(cues2_offset + i*0x20 + 0x08, sf);
                offset2 = read_u32(cues2_offset + i*0x20 + 0x14, sf);
            } else {
                offset1 = read_u32(cues2_offset + i*0x14 + 0x04, sf);
                type    = read_u32(cues2_offset + i*0x14 + 0x08, sf);
                offset2 = read_u32(cues2_offset + i*0x14 + 0x0c, sf);
            }

            /* other types (0x0a, 0x09) look like section/end markers, 0x06/07 only seems to exist once */
            if (type == 0x06 || type == 0x07) { /* loop / goto */
                musx->loop_start = offset2;
                musx->loop_end = offset1;
                musx->loop_flag = 1;
                break;
            }
        }
    }
    else if (musx->loops_offset && read_u32be(musx->loops_offset, sf) != 0xABABABAB) {
        /* parse loop table (loop starts are -1 if non-looping)
         * 0x00: version?
         * 0x04: flags? (&1=loops)
         * 0x08: loop start offset?
         * 0x0c: loop end offset?
         * 0x10: loop end sample
         * 0x14: loop start sample
         * 0x18: loop end offset
         * 0x1c: loop start offset */
        musx->loop_end_sample   = read_s32le(musx->loops_offset+0x10, sf);
        musx->loop_start_sample = read_s32le(musx->loops_offset+0x14, sf);
        musx->loop_end          = read_s32le(musx->loops_offset+0x18, sf);
        musx->loop_start        = read_s32le(musx->loops_offset+0x1c, sf);
        musx->num_samples       = musx->loop_end_sample; /* preferable even if not looping as some files have padding */
        musx->loop_flag         = (musx->loop_start_sample >= 0);
    }

    /* fix some v10 platform (like PSP) sizes */
    if (musx->stream_size == 0) {
        musx->stream_size = musx->file_size - musx->stream_offset;

        /* always padded to nearest 0x800 sector */
        if (musx->stream_size > 0x800) {
            uint8_t buf[0x800];
            int pos;
            off_t offset = musx->stream_offset + musx->stream_size - 0x800;

            if (read_streamfile(buf, offset, sizeof(buf), sf) != 0x800)
                goto fail;

            pos = 0x800 - 0x04;
            while (pos > 0) {
                if (get_u32be(buf + pos) != 0xABABABAB)
                    break;
                musx->stream_size -= 0x04;
                pos -= 0x04;
            }
        }
    }

    return 1;
fail:
    return 0;
}

//TODO: check possible info here:
// https://sphinxandthecursedmummy.fandom.com/wiki/SFX
static int parse_musx(STREAMFILE* sf, musx_header* musx) {
    int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
    uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
    uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
    int target_subsong = sf->stream_index;


    /* base header is LE */
    /* 0x04: file ID (referenced in external .h, sometimes files are named [prefix](id)[suffix].sfx too) */
    musx->version = read_u32le(0x08,sf);
    musx->file_size = read_u32le(0x0c,sf);

    switch(musx->version) {
        case 201:   /* Sphinx and the Cursed Mummy (PS2), Buffy the Vampire Slayer: Chaos Bleeds (PS2/GC/Xbox) */
        case 1:     /* Athens 2004 (PS2) */
            musx->platform = 0; /* guess later */
            musx->tables_offset = 0x10;
            musx->big_endian = guess_endian32(0x10, sf);
            musx->is_old = 1;
            break;

        case 4:     /* Spyro: A Hero's Tail (PS2/Xbox/GC), Athens 2004 (PC) */
        case 5:     /* Predator: Concrete Jungle (PS2/Xbox), Robots (GC) */
        case 6:     /* Batman Begins (GC), Ice Age 2 (PS2/PC) */
            musx->platform = read_u32be(0x10,sf);
            /* 0x14: some id/hash? */
            /* 0x18: platform number? */
            /* 0x1c: null */
            musx->tables_offset = 0x20;
            musx->big_endian = (musx->platform == get_id32be("GC__"));
            break;

        case 10:    /* 007: Quantum of Solace (PS2), Pirates of the Caribbean: At World's End (PSP), GoldenEye 007 (Wii), Rio (PS3) */
            musx->platform = read_u32be(0x10,sf);
            /* 0x14: null */
            /* 0x18: platform number? */
            /* 0x1c: null */
            /* 0x20: hash */
            musx->tables_offset = 0; /* no tables */
            musx->big_endian = (
                musx->platform == get_id32be("GC__") ||
                musx->platform == get_id32be("XE__") ||
                musx->platform == get_id32be("PS3_") ||
                musx->platform == get_id32be("WII_")
            );
            break;

        default:
            VGM_LOG("MUSX: unknown version\n");
            goto fail;
    }

    if (musx->big_endian) {
        read_s32 = read_s32be;
        read_u32 = read_u32be;
        read_u16 = read_u16be;
    } else {
        read_s32 = read_s32le;
        read_u32 = read_u32le;
        read_u16 = read_u16le;
    }


    /* MUSX has multiple forms which seem external/implicit info so we need some crude autodetection */
    if (musx->tables_offset) {
        off_t table1_offset, table2_offset /*, table3_offset, table4_offset*/;
        size_t /*table1_size, table2_size, table3_size,*/ table4_size;

        table1_offset   = read_u32(musx->tables_offset+0x00, sf);
      //table1_size     = read_u32(musx->tables_offset+0x04, sf);
        table2_offset   = read_u32(musx->tables_offset+0x08, sf);
      //table2_size     = read_u32(musx->tables_offset+0x0c, sf);
      //table3_offset   = read_u32(musx->tables_offset+0x10, sf);
      //table3_size     = read_u32(musx->tables_offset+0x14, sf);
      //table4_offset   = read_u32(musx->tables_offset+0x18, sf);
        table4_size     = read_u32(musx->tables_offset+0x1c, sf);

        if (table2_offset == 0 || table2_offset == 0xABABABAB) {
            /* possibly a collection of IDs */
            VGM_LOG("MUSX: unsupported ID type\n");
            goto fail;
        }
        else if (table4_size != 0 && table4_size != 0xABABABAB) {
            /* sfx banks have table1=id table? table2=header table, table=DSP coefs table (optional), table4=data */
            musx->form = SFX_BANK;
        }
        else if (read_u32(table1_offset+0x00, sf) < 0x9 &&
                 read_u32(table1_offset+0x08, sf) == 0x14 &&
                 read_u32(table1_offset+0x10, sf) <= 100) {
            /* streams have a info table with a certain format */
            musx->form = MFX;
        }
        else if (read_u32(table1_offset+0x00, sf) == 0 &&
                 read_u32(table1_offset+0x04, sf) > read_u32(table1_offset+0x00, sf) &&
                 read_u32(table1_offset+0x08, sf) > read_u32(table1_offset+0x04, sf)) {
            /* a list of offsets starting from 0, each stream then has info table at offset */
            musx->form = MFX_BANK;
        }
        else {
            VGM_LOG("MUSX: unsupported unknown type\n");
            goto fail;
        }
    }
    else {
        if (is_id32be(0x800,sf, "SBNK")) {
            /* SFX_BANK-like sound bank found on v10 Wii/PC games [Dead Space Extraction (Wii), G-Force (PC)] */
            musx->form = SBNK;
        }
        else if (is_id32be(0x800,sf, "FORM")) {
            /* RIFF-like sound bank found on v10 PSP games */
            musx->form = FORM;
        }
        else if (is_id32be(0x800,sf, "ESPD")) {
            /* projectdetails.sfx */
            goto fail;
        }
        else {
            /* simplest music type*/
            musx->form = MFX;
        }
    }


    /* parse known forms */
    switch(musx->form) {
        case MFX:

            if (musx->tables_offset) {
                musx->loops_offset  = read_u32(musx->tables_offset+0x00, sf);

                musx->stream_offset = read_u32(musx->tables_offset+0x08, sf);
                musx->stream_size   = read_u32(musx->tables_offset+0x0c, sf);
            }
            else {
                if (read_u32be(0x30, sf) != 0xABABABAB) {
                    uint32_t miniheader = read_u32be(0x40, sf);
                    switch(miniheader) {
                        case 0x44415434: /* "DAT4" */
                        case 0x44415435: /* "DAT5" */
                        case 0x44415438: /* "DAT8" */
                            /* found on PS3/Wii (but not always?) */
                            musx->stream_size = read_u32le(0x44, sf);
                            musx->channels    = read_u32le(0x48, sf);
                            musx->sample_rate = read_u32le(0x4c, sf);
                            musx->loops_offset = 0x50;
                            break;
                        default:
                            if (read_u32be(0x30, sf) == 0x00 &&
                                read_u32be(0x34, sf) == 0x00) {
                                /* no subheader [Spider-Man 4 (Wii)] */
                                musx->loops_offset = 0x00;
                            } else {
                                /* loop info only [G-Force (PS3)] */
                                musx->loops_offset = 0x30;
                            }
                            break;
                    }
                }

                musx->stream_offset = 0x800;
                musx->stream_size   = 0; /* read later */
            }

            if (!parse_musx_stream(sf, musx))
                goto fail;
            break;

        case MFX_BANK: {
            off_t target_offset, base_offset, data_offset;

            musx->total_subsongs = read_u32(musx->tables_offset+0x04, sf) / 0x04; /* size */
            if (target_subsong == 0) target_subsong = 1;
            if (target_subsong > musx->total_subsongs || musx->total_subsongs <= 0) goto fail;

            base_offset = read_u32(musx->tables_offset+0x00, sf);
            data_offset = read_u32(musx->tables_offset+0x08, sf);

            target_offset = read_u32(base_offset + (target_subsong - 1)*0x04, sf) + data_offset;

            /* 0x00: id? */
            musx->stream_offset = read_u32(target_offset + 0x04, sf) + data_offset;
            musx->stream_size   = read_u32(target_offset + 0x08, sf);
            musx->loops_offset  = target_offset + 0x0c;

            /* this looks correct for PS2 and Xbox, not sure about GC */
            musx->channels = 1;
            musx->sample_rate = 22050;

            if (!parse_musx_stream(sf, musx))
                goto fail;
            break;
        }

        case SFX_BANK: {
            off_t target_offset, head_offset, coef_offset, data_offset;
            size_t coef_size;

          //unk_offset  = read_u32(musx->tables_offset+0x00, sf); /* ids and cue-like config? */
            head_offset = read_u32(musx->tables_offset+0x08, sf);
            coef_offset = read_u32(musx->tables_offset+0x10, sf);
            coef_size   = read_u32(musx->tables_offset+0x14, sf);
            data_offset = read_u32(musx->tables_offset+0x18, sf);

            musx->total_subsongs = read_u32(head_offset+0x00, sf);
            if (target_subsong == 0) target_subsong = 1;
            if (target_subsong > musx->total_subsongs || musx->total_subsongs <= 0) goto fail;


            if (musx->is_old) {
                target_offset = head_offset + 0x04 + (target_subsong - 1)*0x28;

                /* 0x00: flag? */
                musx->stream_offset = read_u32(target_offset + 0x04, sf) + data_offset;
                musx->stream_size   = read_u32(target_offset + 0x08, sf);
                musx->sample_rate   = read_u32(target_offset + 0x0c, sf);
                /* 0x10: size? */
                /* 0x14: channels? */
                /* 0x18: always 4? */
                musx->coefs_offset  = read_u32(target_offset + 0x1c, sf) + coef_offset; /* may be set even for non-GC */
                /* 0x20: null? */
                /* 0x24: sub-id? */
                musx->channels = 1;
            }
            else {
                target_offset = head_offset + 0x04 + (target_subsong - 1)*0x20;

                /* 0x00: flag? */
                musx->stream_offset = read_u32(target_offset + 0x04, sf) + data_offset;
                musx->stream_size   = read_u32(target_offset + 0x08, sf);
                musx->sample_rate   = read_u32(target_offset + 0x0c, sf);
                /* 0x10: size? */
                musx->coefs_offset  = read_u32(target_offset + 0x14, sf) + coef_offset; /* may be set even for non-GC */
                /* 0x18: null? */
                /* 0x1c: sub-id? */
                musx->channels = 1;
            }

            if (coef_size == 0)
                musx->coefs_offset = 0; /* disable for later detection */

            if (!parse_musx_stream(sf, musx))
                goto fail;
            break;
        }

        case SBNK: {
            off_t target_offset, head_offset, data_offset;
            uint8_t codec = 0;
            uint32_t version;

            if (!is_id32be(0x800 + 0x00, sf, "SBNK"))
                goto fail;

            version = read_u32(0x800 + 0x04, sf);
            if (version == 0x2A) {
                /* - Goldeneye 007 (X360) */
                /* 0x08: "COM " */
                /* 0x0c: file ID (same as base file) */
                /* 0x10: some ID? */
                musx->tables_offset = 0x814;
            }
            else {
                /* - v0x12 (all others) */
                /* 0x08: file ID (same as base file) */
                /* 0x0c: some ID? */
                musx->tables_offset = 0x810;
            }
                
            /* 0x00: table1 count */
            /* 0x04: table1 offset (from here) */
            /* 0x08: table2 count */
            /* 0x0c: table2 offset (from here) */

            /* 0x10: table3 count */
            /* 0x14: table3 offset (from here) */
            /* 0x18: table4 count */
            /* 0x1c: table4 offset (from here) */

            /* 0x20: table5 count (waves) */
            /* 0x24: table5 offset (from here) */
            /* 0x28: table6 count */
            /* 0x2c: table6 offset (from here) */

            /* 0x30: table7 count */
            /* 0x34: table7 offset (from here) */
            /* 0x38: data size */
            /* 0x3c: data offset (absolute in older versions) */

            musx->total_subsongs = read_u32(musx->tables_offset+0x20, sf);
            if (target_subsong == 0) target_subsong = 1;
            if (target_subsong > musx->total_subsongs || musx->total_subsongs <= 0) goto fail;

            if (version == 0x2A) {
                ;VGM_LOG("MUSX: unknown version format\n");
                goto fail;
                /* subheader is 0x10 but variable?, offset table may be table 7? 
                  - 0x00: ID?
                  - 0x04: samples?
                  - 0x08: sample rate
                  - 0x0a: codec?
                  - 0x0b: channels
                  - 0x0c: size?
                */
            }
            else {
                head_offset = read_u32(musx->tables_offset+0x24, sf) + musx->tables_offset + 0x24;
                data_offset = read_u32(musx->tables_offset+0x3c, sf);

                target_offset = head_offset + (target_subsong - 1) * 0x1c;
                //;VGM_LOG("MUSX: ho=%lx, do=%lx, to=%lx\n", head_offset, data_offset, target_offset);

                /* 0x00: subfile ID */
                musx->num_samples       = read_s32(target_offset + 0x04, sf);
                musx->loop_start_sample = read_s32(target_offset + 0x08, sf); /* -1 if no loop */
                musx->sample_rate       = read_u16(target_offset + 0x0c, sf);
                codec                   = read_u8 (target_offset + 0x0e, sf);
                musx->channels          = read_u8 (target_offset + 0x0f, sf);
                musx->stream_offset     = read_u32(target_offset + 0x10, sf) + data_offset;
                musx->stream_size       = read_u32(target_offset + 0x14, sf);
                musx->loop_start        = read_s32(target_offset + 0x18, sf);
            }

            musx->loop_end_sample   = musx->num_samples;
            musx->loop_flag         = (musx->loop_start_sample >= 0);

            switch(codec) {
                case 0x11: musx->codec = DAT; break;
                case 0x13: musx->codec = NGCA; break;
                case 0x14: musx->codec = PCM; break;
                default: 
                    VGM_LOG("MUSX: unknown SBNK codec %x\n", codec);
                    goto fail;
            }

            break;
        }

        default:
            VGM_LOG("MUSX: unsupported form\n");
            goto fail;
    }


    return 1;
fail:
    return 0;
}
