diff --git a/doc/FORMATS.md b/doc/FORMATS.md index 438327d0..0ce1f25c 100644 --- a/doc/FORMATS.md +++ b/doc/FORMATS.md @@ -156,6 +156,7 @@ This list is not complete and many other files are supported. - .ast (GC AFC ADPCM, 16 bit PCM) - .aud (IMA ADPCM, WS DPCM) - .aus (PSX ADPCM, Xbox IMA ADPCM) + - .awd/.hwd/.lwd (PSX ADPCM, XBOX IMA ADPCM, GC DSP ADPCM, 16 bit PCM) - .brstm (GC DSP ADPCM, 8/16 bit PCM) - .emff (PSX APDCM, GC DSP ADPCM) - .fsb/wii (PSX ADPCM, GC DSP ADPCM, Xbox IMA ADPCM, MPEG audio, FSB Vorbis, MS XMA) diff --git a/src/formats.c b/src/formats.c index a51c3d85..2ed76113 100644 --- a/src/formats.c +++ b/src/formats.c @@ -89,6 +89,7 @@ static const char* extension_list[] = { "awa", //txth/reserved [Missing Parts Side A (PS2)] "awb", "awc", + "awd", "b1s", "baf", @@ -217,6 +218,7 @@ static const char* extension_list[] = { "hab", "hca", "hdr", + "hdt", "hgc1", "his", "hps", @@ -230,6 +232,8 @@ static const char* extension_list[] = { "hxg", "hxx", "hwas", + "hwb", + "hwd", "iab", "iadp", @@ -289,6 +293,7 @@ static const char* extension_list[] = { "lasf", //fake extension for .asf (various) "lbin", //fake extension for .bin (various) "ldat", //fake extension for .dat + "ldt", "leg", "lep", "lflac", //fake extension for .flac, FFmpeg/not parsed @@ -316,6 +321,7 @@ static const char* extension_list[] = { "lsf", "lstm", //fake extension for .stm "lwav", //fake extension for .wav + "lwd", "lwma", //fake extension for .wma, FFmpeg/not parsed "mab", @@ -1420,6 +1426,7 @@ static const meta_info meta_info_list[] = { {meta_VAB, "Sony VAB header"}, {meta_BIGRP, "Inti Creates .BIGRP header"}, {meta_DIC1, "Codemasters DIC1 header"}, + {meta_AWD, "RenderWare Audio Wave Dictionary 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 a4f990cc..d08efabe 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -363,6 +363,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index f80325d4..01de1365 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -910,6 +910,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -2069,4 +2072,4 @@ util\Source Files - \ No newline at end of file + diff --git a/src/meta/awd.c b/src/meta/awd.c new file mode 100644 index 00000000..88aaf2a7 --- /dev/null +++ b/src/meta/awd.c @@ -0,0 +1,154 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../util/endianness.h" + + +/* AWD - Audio Wave Dictionary (RenderWare) */ +VGMSTREAM* init_vgmstream_awd(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + char header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE]; + int bit_depth = 0, channels = 0, sample_rate = 0, stream_codec = -1, total_subsongs = 0, target_subsong = sf->stream_index; + int interleave, loop_flag; + off_t data_offset, header_name_offset, misc_data_offset, linked_list_offset, wavedict_offset; + off_t entry_info_offset, entry_name_offset, entry_uuid_offset, next_entry_offset, prev_entry_offset, stream_offset; + read_u32_t read_u32; + size_t data_size, header_size, misc_data_size, stream_size = 0; + + /* checks */ + if ((read_u32le(0x00, sf) != 0x809) && (read_u32be(0x00, sf) != 0x809)) + goto fail; + + /* .awd: standard (Black, Burnout series, Call of Duty: Finest Hour) + * .hwd/lwd: high/low vehicle engine sounds (Burnout series) + * (Burnout 3: Takedown, Burnout Revenge, Burnout Dominator) */ + if (!check_extensions(sf, "awd,hwd,lwd")) + goto fail; + + read_u32 = guess_endian32(0x00, sf) ? read_u32be : read_u32le; + + data_offset = read_u32(0x08, sf); + wavedict_offset = read_u32(0x0C, sf); + data_size = read_u32(0x14, sf); + /* Platform UUIDs in big endian + * {FD9D32D3-E179-426A-8424-14720AC7F648}: GameCube + * {ACC9EAAA-38FC-1749-AE81-64EADBC79353}: PlayStation 2 + * {042D3A45-5FE4-C84B-81F0-DF758B01F273}: Xbox */ + //platf_uuid_1 = read_u64be(0x18, sf); + //platf_uuid_2 = read_u64be(0x20, sf); + header_size = read_u32(0x28, sf); + + if (data_offset != header_size) + goto fail; + + header_name_offset = read_u32(wavedict_offset + 0x04, sf); + + if (header_name_offset) /* not used in Black */ + read_string(header_name, STREAM_NAME_SIZE, header_name_offset, sf); + + if (!target_subsong) + target_subsong = 1; + + /* Linked lists have no total subsong count; instead iterating + * through all of them until it returns to the 1st entry again */ + linked_list_offset = wavedict_offset + 0x0C; + + prev_entry_offset = read_u32(linked_list_offset + 0x00, sf); + next_entry_offset = read_u32(linked_list_offset + 0x04, sf); + + while (next_entry_offset != linked_list_offset) { + total_subsongs++; + + if (total_subsongs > 1024 || prev_entry_offset > header_size || next_entry_offset > header_size) + goto fail; + + entry_info_offset = read_u32(next_entry_offset + 0x08, sf); + + prev_entry_offset = read_u32(next_entry_offset + 0x00, sf); + next_entry_offset = read_u32(next_entry_offset + 0x04, sf); + + /* is at the correct target song index */ + if (total_subsongs == target_subsong) { + entry_uuid_offset = read_u32(entry_info_offset + 0x00, sf); /* only used in Burnout games */ + entry_name_offset = read_u32(entry_info_offset + 0x04, sf); + + sample_rate = read_u32(entry_info_offset + 0x10, sf); + stream_codec = read_u32(entry_info_offset + 0x14, sf); + stream_size = read_u32(entry_info_offset + 0x18, sf); + bit_depth = read_8bit(entry_info_offset + 0x1C, sf); + channels = read_8bit(entry_info_offset + 0x1D, sf); /* always 1, don't think stereo entries exist */ + if (channels != 1) + goto fail; + + /* stores a "00: GCN ADPCM Header" chunk, otherwise empty */ + misc_data_offset = read_u32(entry_info_offset + 0x20, sf); + misc_data_size = read_u32(entry_info_offset + 0x24, sf); + + /* entry_info_offset + 0x2C to +0x44 has the target format information, + * which in most cases would probably be identical to the input format + * variables (from sample_rate to misc_data_size) */ + + stream_offset = read_u32(entry_info_offset + 0x4C, sf) + data_offset; + + read_string(stream_name, STREAM_NAME_SIZE, entry_name_offset, sf); + } + } + + if (total_subsongs < 1 || target_subsong > total_subsongs) + goto fail; + + interleave = 0; + loop_flag = 0; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) + goto fail; + + vgmstream->meta_type = meta_AWD; + vgmstream->layout_type = layout_none; + vgmstream->sample_rate = sample_rate; + vgmstream->stream_size = stream_size; + vgmstream->num_streams = total_subsongs; + vgmstream->interleave_block_size = interleave; + + if (header_name_offset) + snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name); + else + snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", stream_name); + + switch (stream_codec) { + case 0x00: /* PS2 (Black, Burnout series, Call of Duty: Finest Hour) */ + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels); + vgmstream->coding_type = coding_PSX; + break; + + case 0x01: /* Xbox (Black, Burnout series) */ + vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels); + vgmstream->coding_type = coding_PCM16LE; + break; + + case 0x03: /* GCN (Call of Duty: Finest Hour) */ + vgmstream->num_samples = dsp_bytes_to_samples(stream_size, channels); + dsp_read_coefs_be(vgmstream, sf, misc_data_offset + 0x1C, 0); + vgmstream->coding_type = coding_NGC_DSP; + break; + + case 0x04: /* Xbox (Black, Call of Duty: Finest Hour) */ + vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, channels); + vgmstream->coding_type = coding_XBOX_IMA; + break; + + default: + VGM_LOG("AWD: unknown codec type %d\n", stream_codec); + goto fail; + } + + if (!vgmstream_open_stream(vgmstream, sf, stream_offset)) + goto fail; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index eddd9abb..a64f15fd 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -294,10 +294,11 @@ VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) { /* check extension */ /* .bnk: common - * .sdt: Harry Potter games + * .sdt: Harry Potter games, Burnout games (PSP) + * .hdt/ldt: Burnout games (PSP) * .abk: GoldenEye - Rogue Agent * .ast: FIFA 2004 (inside .big) */ - if (!check_extensions(sf,"bnk,sdt,abk,ast")) + if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast")) goto fail; if (target_stream == 0) target_stream = 1; diff --git a/src/meta/meta.h b/src/meta/meta.h index cf20adec..7bee9ea9 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -21,6 +21,8 @@ VGMSTREAM * init_vgmstream_agsc(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ast(STREAMFILE *streamFile); +VGMSTREAM* init_vgmstream_awd(STREAMFILE *sf); + VGMSTREAM * init_vgmstream_brstm(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_cstr(STREAMFILE *streamFile); diff --git a/src/meta/xwb.c b/src/meta/xwb.c index 307719da..9bd6da6c 100644 --- a/src/meta/xwb.c +++ b/src/meta/xwb.c @@ -95,9 +95,10 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) { /* .xwb: standard * .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC) - * (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) + * (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) + * .hwb: Burnout Revenge (X360) * .bd: Fatal Frame 2 (Xbox) */ - if (!check_extensions(sf,"xwb,xna,bd,")) + if (!check_extensions(sf,"xwb,xna,hwb,bd,")) goto fail; xwb.little_endian = is_id32be(0x00,sf, "WBND"); /* Xbox/PC */ diff --git a/src/vgmstream.c b/src/vgmstream.c index dbfbe0f0..495c4ba3 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -28,6 +28,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_nds_strm, init_vgmstream_afc, init_vgmstream_ast, + init_vgmstream_awd, init_vgmstream_halpst, init_vgmstream_rs03, init_vgmstream_ngc_dsp_std, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 84cb27e2..596daa1c 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -711,6 +711,7 @@ typedef enum { meta_VAB, meta_BIGRP, meta_DIC1, + meta_AWD, } meta_t;