#include "meta.h" #include "../util/endianness.h" #define MAX_CHANNELS 4 typedef struct { bool loop_flag; int channels; int sample_rate; int32_t num_samples; int32_t loop_start; int32_t loop_end; int16_t coefs[MAX_CHANNELS][16]; int total_subsongs; uint32_t stream_offset; uint32_t stream_size; bool dummy; } redspark_header_t; static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new); /* RedSpark - Games with audio by RedSpark Ltd. (Minoru Akao) [MadWorld (Wii), Imabikisou (Wii), Mario & Luigi: Dream Team (3DS)] */ VGMSTREAM* init_vgmstream_redspark(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; bool is_new; /* checks*/ uint32_t head_id = read_u32be(0x00, sf); if (head_id == get_id32be("RedS")) { is_new = true; /* M&L */ } else if (head_id > 0x15800000 && head_id < 0x1B800000) { is_new = false; /* others: header is encrypted but in predictable ranges to fail faster (will do extra checks later) */ } else { return NULL; } if (!check_extensions(sf, "rsd")) return NULL; redspark_header_t h = {0}; if (!parse_header(&h, sf, is_new)) return NULL; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(h.channels, h.loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_REDSPARK; vgmstream->sample_rate = h.sample_rate; vgmstream->num_samples = h.num_samples; vgmstream->loop_start_sample = h.loop_start; vgmstream->loop_end_sample = h.loop_end; vgmstream->num_streams = h.total_subsongs; vgmstream->stream_size = h.stream_size; vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x08; for (int ch = 0; ch < h.channels; ch++) { for (int i = 0; i < 16; i++) { vgmstream->ch[ch].adpcm_coef[i] = h.coefs[ch][i]; } } if (h.dummy) { vgmstream->num_samples = h.sample_rate; vgmstream->coding_type = coding_SILENCE; } if (!vgmstream_open_stream(vgmstream, sf, h.stream_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; } static uint32_t rotlwi(uint32_t x, uint32_t r) { return (x << r) | (x >> (32 - r)); } static uint32_t decrypt_chunk(uint8_t* buf, int buf_size, uint32_t key) { uint32_t data; int pos = 0; if (key == 0) { /* initial key seems to only vary slightly between files though (doesn't seem to depend on size/name) */ key = get_u32be(buf + 0x00) ^ get_id32be("RedS"); data = get_u32be(buf + 0x00) ^ key; put_u32be(&buf[0], data); key = rotlwi(key, 11); pos = 0x04; } for (int i = pos; i < buf_size; i += 0x04) { key = rotlwi(key, 3) + key; data = get_u32be(buf + i) ^ key; put_u32be(buf + i, data); } return key; /* to resume decrypting if needed */ } #define HEADER_MAX 0x2800 /* seen 0x2420 in one bank */ /* header is encrypted except in M&L 3DS so decrypt + handle in buffer; format * base 0x30 header + subheader with tables/headers depending on type */ static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new) { uint8_t buf[HEADER_MAX]; /* LE on 3DS */ get_u16_t get_u16 = !is_new ? get_u16be : get_u16le; get_u32_t get_u32 = !is_new ? get_u32be : get_u32le; uint32_t curr_key = 0x00; int target_subsong = sf->stream_index; /* base header: 00 "RedSpark" 08 chunk size (usually data size, smaller for subspark) 0c type/chunk? 00010000=streams, 00000000=bus/se 10 ? (low number, usually same for multiple files) 14 file id or null 18 data offset 1c bank flag 1e 0909=stream, 0404=stream (MW) or RedSpark-within aka 'subspark' (Imabikisou), 0000=bus/se config (no audio) 20 data size (usually file size, or header + subheader size for subsparks) 24-30 null/reserved */ int base_size = 0x30; if (read_streamfile(buf, 0x00, base_size,sf) != base_size) return false; if (!is_new) curr_key = decrypt_chunk(buf, 0x30, curr_key); /* get base header */ if (get_u64be(buf + 0x00) != get_id64be("RedSpark")) return false; uint32_t data_offset = get_u32(buf + 0x18); uint32_t data_size = get_u32(buf + 0x20); int bank_flag = get_u16(buf + 0x1c); int type = get_u16(buf + 0x1e); if (data_offset >= HEADER_MAX) return false; /* get subheader and prepare offsets */ int redspark_pos; int sub_offset, sub_size; int head_pos; if (data_offset == 0x30) { /* 'subspark', seen in a few sfx in Imabikisou (earlier version of banks) */ /* at data offset (not encrypted): 00 always 017F0100 (LE?) 04-10: null 10 file id 13 string? size 14 encrypted string? (ends with 0) at subchunk_size: another RedSpark with bank subflag */ /* base sub-RedSpark + reset flags */ redspark_pos = data_size; if (read_streamfile(buf + redspark_pos, data_size, base_size, sf) != base_size) return false; if (!is_new) curr_key = decrypt_chunk(buf + redspark_pos, base_size, 0); /* new header */ /* setup rest of header (handled below) */ uint32_t subdata_offset = get_u32(buf + redspark_pos + 0x18); bank_flag = get_u16(buf + redspark_pos + 0x1c); type = get_u16(buf + redspark_pos + 0x1e); data_offset = redspark_pos + subdata_offset; if (data_offset >= HEADER_MAX) return false; sub_offset = redspark_pos + base_size; sub_size = data_offset - sub_offset; head_pos = sub_offset; } else { redspark_pos = 0x00; sub_offset = base_size; sub_size = data_offset - sub_offset; head_pos = sub_offset; } /* read + decrypt rest of header */ if (read_streamfile(buf + sub_offset, sub_offset, sub_size, sf) != sub_size) return false; if (!is_new) decrypt_chunk(buf + sub_offset, sub_size, curr_key); //VGM_LOGB(buf, data_offset, 0); /* bus/se config */ if (type == 0x0000) { /* 30 number of entries (after data offset) per entry 00 channel offset (after all entries) per channel at the above offset 00 channel config (similar to channel config in streams but extended) */ vgm_logi("RedSpark: file has no audio\n"); return false; } /* main info */ uint32_t coef_offset; if (bank_flag) { /* bank with N subsongs (seen in M&L sfx packs and in subsparks) */ /* 00 data size 0c entries (only 1 is possible) 0e entries again 20+ table per entry: 00 absolute offset to header per header 00 null? 04 stream size 08 sample rate (null in subspark) 0c nibbles (old) or samples (new), (null in subspark so uses loops) 10 stream offset (from data_offset) 14 loop end (num samples if non-looped) 18 loop start of -1 it not looped 20 config? 24 config? 28 coefs + hists 5c channel config? */ h->total_subsongs = get_u16(buf + head_pos + 0x0c); if (!check_subsongs(&target_subsong, h->total_subsongs)) return false; int target_pos = head_pos + 0x20 + (target_subsong - 1) * 0x04; if (target_pos + 0x04 >= HEADER_MAX) return false; target_pos = get_u32(buf + target_pos) + redspark_pos; if (target_pos + 0x70 >= HEADER_MAX) return false; h->stream_size = get_u32(buf + target_pos + 0x04); h->sample_rate = get_u32(buf + target_pos + 0x08); h->num_samples = get_u32(buf + target_pos + 0x0c); h->stream_offset = get_u32(buf + target_pos + 0x10) + data_offset; h->loop_end = get_u32(buf + target_pos + 0x14); h->loop_start = get_u32(buf + target_pos + 0x18); coef_offset = target_pos + 0x28; h->channels = 1; h->loop_flag = (h->loop_start != -1); /* TODO: many files sound kind of odd */ if (h->num_samples == 0) h->num_samples = h->loop_end; if (h->sample_rate == 0) h->sample_rate = 32000; /* empty entry */ if (h->stream_size == 0) h->dummy = true; } else { /* stream */ /* 00 data size (after data offset) 04 null 08 null 0c sample rate 10 frames (old) or samples (new) 14 some chunk? (0x10000, 0xc000) 18 null 1c null 1e channels (usually 1-2, sometimes 4 in MW) 1f num loop cues (2 == loop) 20 cues again? 21 volume? 22 null 24+ variable per channel 00 config per channel, size 0x08 (number/panning/volume/etc?) per loop point 00 chunk size? 04 value per channel 00 dsp coefs + hists (size 0x2E) if cues: 00 offset? 04 null per cue: 00 name size 01 string ("Loop Start" / "Loop End") */ h->sample_rate = get_u32(buf + head_pos + 0x0c); h->num_samples = get_u32(buf + head_pos + 0x10); h->channels = get_u8(buf + head_pos + 0x1e); int loop_cues = get_u8(buf + head_pos + 0x1f); head_pos += 0x24; /* just in case to avoid bad reads outside buf */ if (h->channels > MAX_CHANNELS) return false; head_pos += h->channels * 0x08; h->loop_flag = (loop_cues != 0); if (h->loop_flag) { /* only two cue points */ if (loop_cues != 0 && loop_cues != 2) return false; h->loop_start = get_u32(buf + head_pos + 0x04); h->loop_end = get_u32(buf + head_pos + 0x0c); head_pos += 0x10; } coef_offset = head_pos; h->stream_offset = data_offset; h->stream_size = data_size; } /* coefs from decrypted buf (could read hist but it's 0 in DSPs) */ for (int ch = 0; ch < h->channels; ch++) { for (int i = 0; i < 16; i++) { h->coefs[ch][i] = get_u16(buf + coef_offset + 0x2e * ch + i * 2); } } /* fixes */ if (!is_new) { h->loop_end += 1; if (bank_flag) { h->num_samples /= 2 * 0x8; h->loop_start /= 2 * 0x8; h->loop_end /= 2 * 0x8; } h->num_samples *= 14; h->loop_start *= 14; h->loop_end *= 14; if (h->loop_end > h->num_samples) /* needed for some files */ h->loop_end = h->num_samples; } /* new + bank may need +1 */ return true; }