Merge pull request #402 from bnnm/sxd3-spw

sxd3 spw
This commit is contained in:
Christopher Snowhill 2019-04-20 23:37:16 -07:00 committed by GitHub
commit 9206295e09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 252 additions and 23 deletions

135
cli/txtp_segmenter.py Normal file
View File

@ -0,0 +1,135 @@
# !/usr/bin/python
import os
import sys
import glob
import argparse
import re
def parse():
description = (
"creates segmented .txtp from a list of files obtained using wildcards"
)
epilog = (
"examples:\n"
"%(prog)s bgm_*.ogg\n"
"- get all files that start with bgm_ and end with .ogg\n"
"%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n"
"- get all files that start with bgm_ and end with 2 chars plus any extension\n"
"%(prog)s files/bgm_*_all.ogg -s\n"
"- create single .txtp per every bgm_(something)_all.ogg inside files dir\n"
"%(prog)s **/*.ogg -l\n"
"- find all .ogg in all subdirs but list only"
"%(prog)s files/*.ogg -f .+(a|all)[.]ogg$\n"
"- find all .ogg in files except those that end with 'a.ogg' or 'all.ogg'\n"
)
parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("files", help="files to match")
parser.add_argument("-n","--name", help="generated txtp name (adapts 'files' by default)")
parser.add_argument("-f","--filter", help="filter matched files with regex")
parser.add_argument("-s","--single", help="generate single files per list match", action='store_true')
parser.add_argument("-l","--list", help="list only results and don't write", action='store_true')
parser.add_argument("-cls","--command-loop-start", help="sets loop start segment")
parser.add_argument("-cle","--command-loop-end", help="sets loop end segment")
parser.add_argument("-cv","--command-volume", help="sets volume")
parser.add_argument("-c","--command", help="sets any command (free text)")
return parser.parse_args()
def is_file_ok(args, glob_file):
if not os.path.isfile(glob_file):
return False
if glob_file.endswith(".py"):
return False
if args.filter:
filename_test = os.path.basename(glob_file)
p = re.compile(args.filter)
if (p.match(filename_test) != None):
return False
return True
def get_txtp_name(args, segment):
txtp_name = ''
if args.name:
txtp_name = args.name
elif args.single:
txtp_name = os.path.splitext(os.path.basename(segment))[0]
else:
txtp_name = os.path.splitext(os.path.basename(args.files))[0]
txtp_name = txtp_name.replace('*', '')
txtp_name = txtp_name.replace('?', '')
if txtp_name.endswith('_'):
txtp_name = txtp_name[:-1]
if txtp_name == '':
txtp_name = 'bgm'
if not txtp_name.endswith(".txtp"):
txtp_name += ".txtp"
return txtp_name
def main():
args = parse()
# get target files
glob_files = glob.glob(args.files)
# process matches and add to output list
files = []
segments = []
for glob_file in glob_files:
if not is_file_ok(args, glob_file):
continue
if args.single:
name = get_txtp_name(args, glob_file)
segments = [glob_file]
files.append( (name,segments) )
else:
segments.append(glob_file)
if not args.single:
name = get_txtp_name(args, '')
files.append( (name,segments) )
if not files or not segments:
print("no files found")
exit()
# list info
for name, segments in files:
print("file: " + name)
for segment in segments:
print(" " + segment)
if args.list:
exit()
# write resulting files
for name, segments in files:
with open(name,"w+") as ftxtp:
for segment in segments:
ftxtp.write(segment + "\n")
if args.command_loop_start:
ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n")
if args.command_loop_end:
ftxtp.write("loop_end_segment = " + args.command_loop_end + "\n")
if args.command_volume:
ftxtp.write("commands = #@volume " + args.command_volume + "\n")
if args.command:
ftxtp.write(args.command + "\n")
if __name__ == "__main__":
main()

View File

@ -428,6 +428,7 @@ static const char* extension_list[] = {
"sx",
"sxd",
"sxd2",
"sxd3",
"tec",
"tgq",

View File

@ -114,6 +114,7 @@ fail:
/* SPW (SEWave) - from PlayOnline viewer for Final Fantasy XI (PC) */
VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
uint32_t codec, file_size, block_size, sample_rate, block_align;
int32_t loop_start;
off_t start_offset;
@ -139,8 +140,8 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
/*0x2c: unk (0x00?) */
/*0x2d: unk (0x00/01?) */
channel_count = read_8bit(0x2a,streamFile);
block_align = read_8bit(0x2b,streamFile);
/*0x2c: unk (0x01 when PCM, 0x10 when VAG?) */
/*0x2b: unk (0x01 when PCM, 0x10 when VAG?) */
block_align = read_8bit(0x2c,streamFile);
if (file_size != get_streamfile_size(streamFile))
goto fail;
@ -181,6 +182,38 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
break;
#ifdef VGM_USE_FFMPEG
case 3: { /* ATRAC3 (encrypted) */
uint8_t buf[0x100];
int bytes, joint_stereo, skip_samples;
size_t data_size = file_size - start_offset;
vgmstream->num_samples = block_size; /* atrac3_bytes_to_samples gives the same value */
if (loop_flag) {
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = vgmstream->num_samples;
}
block_align = 0xC0 * vgmstream->channels; /* 0x00 in header */
joint_stereo = 0;
skip_samples = 0;
bytes = ffmpeg_make_riff_atrac3(buf, 0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_align, joint_stereo, skip_samples);
if (bytes <= 0) goto fail;
temp_streamFile = setup_bgw_atrac3_streamfile(streamFile, start_offset,data_size, 0xC0,channel_count);
if (!temp_streamFile) goto fail;
vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0,data_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
close_streamfile(temp_streamFile);
break;
}
#endif
default:
goto fail;
}
@ -193,6 +226,7 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
return vgmstream;
fail:
close_streamfile(temp_streamFile);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -5,32 +5,62 @@
/* SXD - Sony/SCE's SNDX lib format (cousin of SGXD) [Gravity Rush, Freedom Wars, Soul Sacrifice PSV] */
VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
STREAMFILE * streamHeader = NULL, *streamData = NULL;
STREAMFILE *streamHeader = NULL, *streamExternal = NULL, *streamData = NULL, *streamHead = NULL, *streamBody = NULL;
off_t start_offset, chunk_offset, first_offset = 0x60, name_offset = 0;
size_t chunk_size, stream_size = 0;
int is_dual = 0, is_external = 0;
int is_dual, is_external;
int loop_flag, channels, codec, flags;
int sample_rate, num_samples, loop_start_sample, loop_end_sample;
uint32_t at9_config_data = 0;
int total_subsongs, target_subsong = streamFile->stream_index;
/* check extension, case insensitive */
/* .sxd: header+data (SXDF), .sxd1: header (SXDF) + .sxd2 = data (SXDS) */
if (!check_extensions(streamFile,"sxd,sxd2")) goto fail;
is_dual = !check_extensions(streamFile,"sxd");
/* sxd1+sxd2: use sxd1 as header; otherwise use the current file as header */
if (is_dual) {
if (read_32bitBE(0x00,streamFile) != 0x53584453) /* "SXDS" */
/* checks */
/* .sxd: header+data (SXDF)
* .sxd1: header (SXDF) + .sxd2 = data (SXDS)
* .sxd3: sxd1 + sxd2 pasted together (found in some PS4 games, ex. Fate Extella)*/
if (!check_extensions(streamFile,"sxd,sxd2,sxd3"))
goto fail;
streamHeader = open_streamfile_by_ext(streamFile, "sxd1");
if (!streamHeader) goto fail;
} else {
streamHeader = streamFile;
/* setup head/body variations */
if (check_extensions(streamFile,"sxd2")) {
/* sxd1+sxd2: open sxd1 as header */
streamHead = open_streamfile_by_ext(streamFile, "sxd1");
if (!streamHead) goto fail;
streamHeader = streamHead;
streamExternal = streamFile;
is_dual = 1;
}
if (read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */
else if (check_extensions(streamFile,"sxd3")) {
/* sxd3: make subfiles for head and body to simplify parsing */
off_t sxd1_offset = 0x00;
size_t sxd1_size = read_32bitLE(0x08, streamFile);
off_t sxd2_offset = sxd1_size;
size_t sxd2_size = get_streamfile_size(streamFile) - sxd1_size;
streamHead = setup_subfile_streamfile(streamFile, sxd1_offset, sxd1_size, "sxd1");
if (!streamHead) goto fail;
streamBody = setup_subfile_streamfile(streamFile, sxd2_offset, sxd2_size, "sxd2");
if (!streamBody) goto fail;
streamHeader = streamHead;
streamExternal = streamBody;
is_dual = 1;
}
else {
/* sxd: use the current file as header */
streamHeader = streamFile;
streamExternal = NULL;
is_dual = 0;
}
if (streamHeader && read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */
goto fail;
if (streamExternal && read_32bitBE(0x00,streamExternal) != 0x53584453) /* "SXDS" */
goto fail;
@ -132,8 +162,9 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
goto fail;
}
/* even dual files may have some non-external streams */
if (is_external) {
streamData = streamFile;
streamData = streamExternal;
} else {
streamData = streamHeader;
}
@ -196,11 +227,13 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
if (!vgmstream_open_stream(vgmstream,streamData,start_offset))
goto fail;
if (is_dual) close_streamfile(streamHeader);
if (streamHead) close_streamfile(streamHead);
if (streamBody) close_streamfile(streamBody);
return vgmstream;
fail:
if (is_dual) close_streamfile(streamHeader);
if (streamHead) close_streamfile(streamHead);
if (streamBody) close_streamfile(streamBody);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -1325,7 +1325,7 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile);
if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) {
VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset);
VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset);
if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */
goto fail;
}
@ -1451,6 +1451,14 @@ static int parse_stream_codec(ubi_sb_header * sb) {
sb->stream_type = 0; /* probably ignored for non-stream types */
}
/* Batman: Rise of Sin Tzu (Xbox)
* (similar but also for some streams, maybe uses a 'hardware flag' possibly at 0x20?) */
if ((sb->version == 0x000A0003 && sb->platform == UBI_XBOX) &&
(!sb->is_external || strncmp(sb->resource_name, "STREAMHW.", 9) == 0)) {
sb->stream_type = 0;
}
/* guess codec */
switch (sb->stream_type) {
case 0x00: /* platform default (rarely external) */
@ -2068,7 +2076,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34;
if (sb->version <= 0x00000200) {
if (sb->version == 0x00000000 || sb->version == 0x00000200) {
sb->cfg.audio_stream_size = 0x0c;
sb->cfg.audio_stream_offset = 0x10;
//sb->cfg.audio_extra_offset = 0x10;
@ -2166,7 +2174,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
}
/* Batman: Vengeance (2001)(PC)-map */
if (sb->version == 0x00000003 && sb->platform == UBI_PC) {
/* Batman: Vengeance (2001)(Xbox)-map */
if ((sb->version == 0x00000003 && sb->platform == UBI_PC) ||
(sb->version == 0x00000003 && sb->platform == UBI_XBOX)) {
config_sb_entry(sb, 0x40, 0x68);
config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */
@ -2385,6 +2395,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
return 1;
}
/* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */
if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) {
config_sb_entry(sb, 0x64, 0x80);
config_sb_audio_fs(sb, 0x24, 0x28, 0x2c);
config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); /* stream_type may contain garbage */
sb->cfg.audio_has_internal_names = 1;
config_sb_sequence(sb, 0x28, 0x14);
config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30);
config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10);
//todo some sequences mix 1ch and 2ch (voices?)
return 1;
}
/* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */
if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) ||
(sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) {