From 00278d8e728693887ac77499ea46201915349fa3 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:01:50 +0100 Subject: [PATCH 1/7] Fix some .sfx MUSX sample rate [Ice Ace 3 (PC), G-Force (PS3)] --- src/meta/musx.c | 181 ++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 91 deletions(-) diff --git a/src/meta/musx.c b/src/meta/musx.c index 09ccde69..05b62d8c 100644 --- a/src/meta/musx.c +++ b/src/meta/musx.c @@ -25,6 +25,7 @@ typedef struct { int channels; int sample_rate; int loop_flag; + uint32_t flags; int32_t loop_start; int32_t loop_end; int32_t num_samples; @@ -173,7 +174,6 @@ fail: 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; @@ -210,7 +210,85 @@ static int parse_musx_stream(STREAMFILE* sf, musx_header* musx) { } + /* parse loops and other info */ + if (musx->tables_offset && musx->loops_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) */ + int cues2_count = read_u32(musx->loops_offset+0x04, sf); + off_t cues2_offset = musx->loops_offset + read_u32(musx->loops_offset+0x0c, sf); + for (int 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? (always 1) + * 0x04: flags (&1=loops, &2=alt?) + * 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->flags = read_u32le(musx->loops_offset+0x04, sf); + 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; + } + } + } + + /* defaults */ + int default_channels, default_sample_rate; switch(musx->platform) { case 0x5053325F: /* "PS2_" */ @@ -254,27 +332,27 @@ static int parse_musx_stream(STREAMFILE* sf, musx_header* musx) { break; case 0x5749495F: /* "WII_" */ - default_channels = 2; - default_sample_rate = 32000; - 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 0x5053335F: /* "PS3_" */ case 0x50435F5F: /* "PC__" */ default_channels = 2; default_sample_rate = 44100; musx->codec = DAT; + + // some v10 versions use 44100 and others 32000, the latter seem to have loop info table (even without loops) and a flag + // - 44100: Robots (PC)-v10 (no loop table), Pirates of the Caribbean: At World's End (PC)-v10 (no loop table), Beijing 2008 (loop table) + // - 32000: G-Force (PS3)-v10, Ice Age 3 (PC)-v10 (loop table with flag 2) + // The flag also exists in files with similar loop tables in the DAT* chunk + if (musx->version == 10 && musx->flags && musx->flags & 0x02) { + default_sample_rate = 32000; + } + //TO-DO: some files use 22050 but don't seem to set any flag [Beijing 2008 (PS3)] + break; case 0x50433032: /* "PC02" */ @@ -293,85 +371,6 @@ static int parse_musx_stream(STREAMFILE* sf, musx_header* musx) { 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; From 308c9c3c3def7b266bac480d95edb2cd6506a888 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:03:27 +0100 Subject: [PATCH 2/7] Fix some .sch [Conflict: Desert Storm (PC)] --- src/meta/psf.c | 69 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/meta/psf.c b/src/meta/psf.c index 9333ee74..6885445b 100644 --- a/src/meta/psf.c +++ b/src/meta/psf.c @@ -10,20 +10,19 @@ VGMSTREAM* init_vgmstream_psf_single(STREAMFILE* sf) { off_t start_offset; int loop_flag, channel_count, sample_rate, rate_value, interleave; uint32_t psf_config; - uint8_t flags; size_t data_size; coding_t codec; /* checks */ if ((read_u32be(0x00,sf) & 0xFFFFFF00) != get_id32be("PSF\0")) - goto fail; + return NULL; /* .psf: actual extension * .swd: bigfile extension */ if (!check_extensions(sf, "psf,swd")) - goto fail; + return NULL; - flags = read_8bit(0x03,sf); + uint8_t flags = read_8bit(0x03,sf); switch(flags) { case 0xC0: /* [The Great Escape (PS2), Conflict: Desert Storm (PS2)] */ case 0x40: /* [The Great Escape (PS2)] */ @@ -495,6 +494,8 @@ fail: typedef enum { UNKNOWN, IMUS, PFST, PFSM } sch_type; #define SCH_STREAM "Stream.swd" +#define SCH_STREAM_PS2 "STREAM.SWD" + /* SCH - Pivotal games multi-audio container [The Great Escape, Conflict series] */ @@ -504,7 +505,6 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { STREAMFILE* temp_sf = NULL; off_t skip = 0, chunk_offset, target_offset = 0, header_offset, subfile_offset = 0; size_t file_size, chunk_padding, target_size = 0, subfile_size = 0; - int big_endian; int total_subsongs = 0, target_subsong = sf->stream_index; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; sch_type target_type = UNKNOWN; @@ -516,23 +516,23 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { skip = 0x0E; if (!is_id32be(skip + 0x00,sf, "SCH\0") && !is_id32le(skip + 0x00,sf, "SCH\0")) /* (BE consoles) */ - goto fail; + return NULL; if (!check_extensions(sf, "sch")) - goto fail; + return NULL; /* chunked format (id+size, GC pads to 0x20 and uses BE/inverted ids): * no other info so total subsongs would be count of usable chunks * (offsets are probably in level .dat files) */ - big_endian = (is_id32le(skip + 0x00,sf, "SCH\0")); + int big_endian = (is_id32le(skip + 0x00,sf, "SCH\0")); if (big_endian) { read_32bit = read_32bitBE; chunk_padding = 0x18; } else { read_32bit = read_32bitLE; - chunk_padding = 0; + chunk_padding = 0x00; } file_size = get_streamfile_size(sf); @@ -598,9 +598,9 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { switch(target_type) { case IMUS: { /* external segmented track */ - STREAMFILE *psf_sf; + STREAMFILE* psf_sf = NULL; uint8_t name_size; - char name[255]; + char name[256]; /* 0x00: config/size? * 0x04: name size @@ -611,32 +611,33 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { */ name_size = read_u8(header_offset + 0x04, sf); - read_string(name,name_size, header_offset + 0x08, sf); + read_string(name, name_size, header_offset + 0x08, sf); - /* later games have name but actually use bigfile [Conflict: Global Storm (Xbox)] */ + /* later Xbox games have name but actually use bigfile [Conflict: Global Storm (Xbox)] */ if (read_u8(header_offset + 0x07, sf) == 0xCC) { external_sf = open_streamfile_by_filename(sf, SCH_STREAM); - if (!external_sf) { - vgm_logi("SCH: external file '%s' not found (put together)\n", SCH_STREAM); - goto fail; + if (external_sf) { + subfile_offset = read_32bit(header_offset + 0x08 + name_size, sf); + subfile_size = get_streamfile_size(external_sf) - subfile_offset; /* not ok but meh */ + + temp_sf = setup_subfile_streamfile(external_sf, subfile_offset,subfile_size, "psf"); + if (!temp_sf) goto fail; + + psf_sf = temp_sf; } - - subfile_offset = read_32bit(header_offset + 0x08 + name_size, sf); - subfile_size = get_streamfile_size(external_sf) - subfile_offset; /* not ok but meh */ - - temp_sf = setup_subfile_streamfile(external_sf, subfile_offset,subfile_size, "psf"); - if (!temp_sf) goto fail; - - psf_sf = temp_sf; } - else { - external_sf = open_streamfile_by_filename(sf, name); - if (!external_sf) { - vgm_logi("SCH: external file '%s' not found (put together)\n", name); - goto fail; - } - psf_sf = external_sf; + /* PC games still use name + 0xCC at header, no diffs vs Xbox? [Conflict: Global Storm (PC)] */ + if (!psf_sf) { + external_sf = open_streamfile_by_filename(sf, name); + if (external_sf) { + psf_sf = external_sf; + } + } + + if (!psf_sf) { + vgm_logi("SCH: external file '%s' or '%s' not found (put together)\n", SCH_STREAM, name); + goto fail; } vgmstream = init_vgmstream_psf_segmented(psf_sf); @@ -652,7 +653,7 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { case PFST: { /* external track */ STREAMFILE *psf_sf; uint8_t name_size; - char name[255]; + char name[256]; if (chunk_padding == 0 && target_size > 0x08 + 0x0c) { /* TGE PC/Xbox version */ /* 0x00: -1/0 @@ -697,7 +698,7 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { } } else if (chunk_padding) { - strcpy(name, "STREAM.SWD"); /* fixed */ + strcpy(name, SCH_STREAM_PS2); /* fixed */ /* 0x00: -1 * 0x04: config/size? @@ -715,7 +716,7 @@ VGMSTREAM* init_vgmstream_sch(STREAMFILE* sf) { psf_sf = temp_sf; } else { /* others */ - strcpy(name, "STREAM.SWD"); /* fixed */ + strcpy(name, SCH_STREAM_PS2); /* fixed */ /* 0x00: -1 * 0x04: config/size? From a2f75bb43f0734fae9571370c31a19961fcfffac Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:03:41 +0100 Subject: [PATCH 3/7] Add ADX key --- src/meta/adx_keys.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/meta/adx_keys.h b/src/meta/adx_keys.h index de4e992e..637d1133 100644 --- a/src/meta/adx_keys.h +++ b/src/meta/adx_keys.h @@ -279,6 +279,9 @@ static const adxkey_info adxkey9_list[] = { /* ARGONAVIS -Kimi ga Mita Stage e- (Android) */ {0x0000,0x0000,0x0000, NULL,301179795002661}, // 000111EBE2B1D525 (+ AWB subkeys) + // Bungo Stray Dogs: Mayoi Inu Kaikitan (iOS/Android) + {0x0000,0x0000,0x0000, NULL,1655728931134731873}, // 16FA54B0C09F7661 + }; static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); From f229833ec0770b8a097d491729a92ffee3b8d17c Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:04:02 +0100 Subject: [PATCH 4/7] Add extra .wave test codecs --- src/meta/wave.c | 88 +++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/src/meta/wave.c b/src/meta/wave.c index 921c8a71..c22d2efa 100644 --- a/src/meta/wave.c +++ b/src/meta/wave.c @@ -8,7 +8,6 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) { uint32_t start_offset, extradata_offset, interleave; int channels, loop_flag, sample_rate, codec, version; int32_t num_samples, loop_start, loop_end; - int big_endian; read_u32_t read_u32; read_s32_t read_s32; read_f32_t read_f32; @@ -25,7 +24,7 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) { if (!check_extensions(sf, "wave")) return NULL; - big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE || is_id32be(0x00,sf, "WWAV"); + bool big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE || is_id32be(0x00,sf, "WWAV"); if (big_endian) { read_u32 = read_u32be; read_s32 = read_s32be; @@ -46,7 +45,7 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) { loop_end = read_s32(0x18, sf); codec = read_u8(0x1c, sf); - channels = read_u8(0x1d, sf); + channels = read_u8(0x1d, sf); // DS can only do mono if (read_u8(0x1e, sf) != 0x00) goto fail; /* unknown */ if (read_u8(0x1f, sf) != 0x00) goto fail; /* unknown */ @@ -60,14 +59,19 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) { if(!loop_flag && loop_start == 0 && loop_end == num_samples /* full loop */ && (channels > 1 || (channels == 1 && start_offset <= 0x40)) - && num_samples > 30*sample_rate) { /* in seconds */ + && num_samples > 30 * sample_rate) { /* in seconds */ loop_flag = 1; } - /* normalize codec: WWAV uses codec 0x00 for DSP */ + /* normalize codec (files generated by DsBuildWave/3dsBuildWave) */ if (codec == 0x00 && version == 0x00050000 && start_offset > 0x40) { + /* WWAV uses codec 0x00 for DSP (only one?) */ codec = 0x02; } + else if (codec == 0x02 && start_offset <= 0x40) { + /* DS games use IMA, no apparent flag (could also test ID) */ + codec = 0x03; + } /* build the VGMSTREAM */ @@ -81,39 +85,51 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) { vgmstream->meta_type = meta_WAVE; - /* not sure if there are other codecs but anyway (based on wave-segmented) */ + /* some codecs aren't used by known games but can be created by DsBuildWave/3dsBuildWave */ switch(codec) { - case 0x02: - /* DS games use IMA, no apparent flag (could also test ID) */ - if (start_offset <= 0x40) { - vgmstream->coding_type = coding_IMA_int; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - - /* extradata: - * 0x00: base hist? (only seen 0) - * 0x02: base step? (only seen 0) - * 0x04: loop hist? - * 0x06: loop step? - */ - } - else { - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - - /* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2 + ?, per channel */ - int head_spacing = 0x2c; - int hist_spacing = 0x22; - if (version == 0x00050000) { /* has an extra empty 16b after coefs */ - head_spacing = 0x2e; - hist_spacing = 0x24; - } - - dsp_read_coefs(vgmstream, sf, extradata_offset + 0x00, head_spacing, big_endian); - dsp_read_hist(vgmstream, sf, extradata_offset + hist_spacing, head_spacing, big_endian); - } + case 0x00: // PCM8 (not seen) + vgmstream->coding_type = coding_PCM8; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; break; + + case 0x01: // PCM16 (not seen) + vgmstream->coding_type = coding_PCM16BE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + break; + + case 0x02: { // DSP (3DS only, common) + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + /* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2 + ?, per channel */ + int head_spacing = 0x2c; + int hist_spacing = 0x22; + if (version == 0x00050000) { /* has an extra empty 16b after coefs */ + head_spacing = 0x2e; + hist_spacing = 0x24; + } + + dsp_read_coefs(vgmstream, sf, extradata_offset + 0x00, head_spacing, big_endian); + dsp_read_hist(vgmstream, sf, extradata_offset + hist_spacing, head_spacing, big_endian); + break; + } + + case 0x03: //IMA (DS uses codec 02 for IMA, common; 3DS: uses 03 but not seen) + vgmstream->coding_type = coding_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + /* extradata: + * 0x00: base hist? (only seen 0) + * 0x02: base step? (only seen 0) + * 0x04: loop hist? + * 0x06: loop step? + */ + break; + default: goto fail; } From 3f08ef331923dcbe5c0112e03acdc94a56d23623 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:04:18 +0100 Subject: [PATCH 5/7] txtp: cleanup --- src/meta/txtp_parser.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/meta/txtp_parser.c b/src/meta/txtp_parser.c index cfe92553..0f6ff132 100644 --- a/src/meta/txtp_parser.c +++ b/src/meta/txtp_parser.c @@ -854,7 +854,7 @@ static int add_entry(txtp_header_t* txtp, char* filename, int is_default) { txtp_entry_t entry = {0}; - ;VGM_LOG("TXTP: input filename=%s\n", filename); + //;VGM_LOG("TXTP: input filename=%s\n", filename); /* parse filename: file.ext#(commands) */ { @@ -896,11 +896,11 @@ static int add_entry(txtp_header_t* txtp, char* filename, int is_default) { params = NULL; } - ;VGM_LOG("TXTP: params=%s\n", params); + //;VGM_LOG("TXTP: params=%s\n", params); parse_params(&entry, params); } - ;VGM_LOG("TXTP: output filename=%s\n", filename); + //;VGM_LOG("TXTP: output filename=%s\n", filename); clean_filename(filename); //;VGM_LOG("TXTP: clean filename='%s'\n", filename); From e458090dcc0fbd480803b71c8943b027b261af6e Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:04:31 +0100 Subject: [PATCH 6/7] txtp dumper: tweaks --- cli/tools/txtp_dumper.py | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/cli/tools/txtp_dumper.py b/cli/tools/txtp_dumper.py index adba008d..17f7c308 100644 --- a/cli/tools/txtp_dumper.py +++ b/cli/tools/txtp_dumper.py @@ -83,23 +83,37 @@ class App(object): if line.startswith('#'): continue - if not line.endswith('.txtp'): - if self.args.force: - line += '.txtp' - else: - continue - - line.replace('\\', '/') - + line = line.replace('\\n', '\n') #may conflict with paths but... + line = line.replace('\\', '/') subdir = self.args.subdir if ':' in line: - index = line.find(':') #internal txtp : txtp name + # 'commands : name.txtp' format + index = line.find(':') text = line[0:index].strip() name = line[index+1:].strip() + # detect and allow 'name.txtp : commands' too + if text.endswith('.txtp') and not name.endswith('.txtp'): + temp = text + text = name + name = temp + + if not name.endswith('.txtp'): + if self.args.force: + name += '.txtp' + else: + continue + elif self.args.maxitxtp or subdir: + if not line.endswith('.txtp'): + if self.args.force: + line += '.txtp' + else: + continue + + index = line.find('.') #first extension if line[index:].startswith('.txtp'): #??? @@ -114,6 +128,12 @@ class App(object): subdir = subdir + '/' text = subdir + text else: + if not line.endswith('.txtp'): + if self.args.force: + line += '.txtp' + else: + continue + # should be a mini-txtp, but if name isn't "file.ext.txtp" and just "file.txtp", # probably means proper txtp exists and should't be created (when generating from !tags.m3u) name = line @@ -128,6 +148,9 @@ class App(object): outpath = os.path.join(path, name) + dir_path = os.path.dirname(outpath) + os.makedirs(dir_path, exist_ok=True) + with open(outpath, 'w') as fo: if text: fo.write(text) From 40599957d5d46cadaefb40b421be98f27e868859 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 8 Jan 2025 18:04:37 +0100 Subject: [PATCH 7/7] doc --- doc/USAGE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/USAGE.md b/doc/USAGE.md index 7e3b5231..2e6b7a40 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -973,3 +973,23 @@ with `.txtp` as well. This may even happen with formats that do have loops in other games (for example relatively common with `.fsb` and mobile games, that may define loops in a .json file). + + +## Modding game audio and encoding wav files to video game formats +vgmstream cannot *encode* (convert *from* `.wav` *to* a game format), it only *decodes* +(plays game audio). It also can't repack/mod game files (like `.wem`) into other game +formats (like `.bnk`). + +One may think it's easy to do, since vgmstream reads game audio might as well write audio +too, but *encoding* and *decoding* are very different. + +To *decode* vgmstream just reads a few existing values from the file's *header*, +to setup and play the file's *body* data, decompressing the game's audio codec. + +To *encode* the program would need to make the *header* from scratch (having to include +lots of values the game needs but aren't needed for vgmstream to play audio), and take +PCM audio (.wav) and compress it (*very* different than decompressing) to make a *body*. + +In other words you need a dedicated tool that can *encode* to your particular format. +Since *encoding* is lot harder than *decoding* it's not very common to find public tools, +and may need to program one yourself.