Merge pull request #975 from bnnm/lpcm-txth-ea

- Fix some LOPU .lopus loops [Melty Blood Type Lumina (Switch)]
- Add LPCM .w extension, cleanup
- Fix NX Opus with odd rates [Lego Marvel (Sw)]
- Add XA in TXTH [Phantasy Star (SAT), Fantavision (PS2)]
- Remove headerless XA (use TXTH) and improve detection speed
- Fix some .cnk 1SNh [Triple Play 97, FIFA 97 (PS1)]
- Fix some .cnk SCHl [NBA Live 97 (PS1)]
- vgmstream123: make options closer to CLI's
- Add more TXTH chunk options
- txth: clean detection and remove rare edge case
- cleanup
This commit is contained in:
bnnm 2021-10-10 17:11:27 +02:00 committed by GitHub
commit 08dc607e2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1229 additions and 988 deletions

13
.gitignore vendored
View File

@ -5,6 +5,11 @@
*.o *.o
*.a *.a
# gcc (-MMD -save-temps)
*.d
*.i
*.s
# VS stuff # VS stuff
ipch ipch
.vs .vs
@ -43,14 +48,18 @@ Release
/xmplay/Debug /xmplay/Debug
/xmplay/Release /xmplay/Release
/xmplay/*.dll /xmplay/*.dll
/dependencies
/version_auto.h
/msvc-build.config.ps1 /msvc-build.config.ps1
/msvc-build.log /msvc-build.log
# for test batchs, note that already tracked files are never ignored # for test batchs, note that already tracked files are never ignored
/msvc-build-*.bat /msvc-build-*.bat
# vscode
.vscode
# build
/version_auto.h
/dependencies
/bin/**/* /bin/**/*
/tmp/**/* /tmp/**/*
/**/vgmstream-win.zip /**/vgmstream-win.zip

View File

@ -119,7 +119,7 @@ class App(object):
count += 1 count += 1
if not count: if not count:
print("%s: no .txtp found" % (filename, count)) print("%s: no .txtp found" % (filename))
else: else:
print("%s: total %i .txtp" % (filename, count)) print("%s: total %i .txtp" % (filename, count))

View File

@ -1,10 +1,6 @@
# !/usr/bin/python # !/usr/bin/python
import os import os, glob, argparse, re
import sys
import glob
import argparse
import re
def parse(): def parse():
@ -12,28 +8,34 @@ def parse():
"creates segmented .txtp from a list of files obtained using wildcards" "creates segmented .txtp from a list of files obtained using wildcards"
) )
epilog = ( epilog = (
"examples:\n" 'examples:\n'
"%(prog)s bgm_*.ogg\n" '%(prog)s bgm_*.ogg\n'
"- get all files that start with bgm_ and end with .ogg\n" '- get all files that start with bgm_ and end with .ogg\n'
"%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n" '%(prog)s bgm_??.* -n bgm_main.txtp -cla\n'
"- get all files that start with bgm_ and end with 2 chars plus any extension\n" '- get all files that start with bgm_ and end with 2 chars plus any extension + loop\n'
"%(prog)s files/bgm_*_all.ogg -s\n" '%(prog)s files/bgm_*_all.ogg -s\n'
"- create single .txtp per every bgm_(something)_all.ogg inside files dir\n" '- create single .txtp per every bgm_(something)_all.ogg inside files dir\n'
"%(prog)s **/*.ogg -l\n" '%(prog)s **/*.ogg -l\n'
"- find all .ogg in all subdirs but list only\n" '- list results only\n'
"%(prog)s files/*.ogg -f .+(a|all)[.]ogg$\n" '%(prog)s files/*.ogg -fi .+(00[01])[.]ogg$\n'
"- find all .ogg in files except those that end with 'a.ogg' or 'all.ogg'\n" '- find all .ogg in files including those that end with 0.ogg or 1.ogg\n'
"%(prog)s files/*.ogg -f .+(00[01])[.]ogg$\n" '%(prog)s files/*.ogg -fe .+(a|all)[.]ogg$\n'
"- find all .ogg in files that end with '0.ogg' or '1.ogg'\n" '- find all .ogg in files excluding those that end with a.ogg or all.ogg\n'
'%(prog)s files/*.* -fi "(.+)(_intro|_loop)([.].+)$" -n "\\1)_full" -cfa\n'
'- makes intro+loop .txtp named (first part without _intro/_loop)_full.txtp + loops\n'
'%(prog)s files/*.* -fe "(.+)(_intro|_loop)([.].+)$" -s\n'
'- makes single .txtp for files that don\'t have intro+loop pairs\n'
) )
parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("files", help="files to match") parser.add_argument("files", help="files to match")
parser.add_argument("-n","--name", help="generated txtp name (adapts 'files' by default)") parser.add_argument("-n","--name", help="generated txtp name (auto from 'files' by default)\nMay use regex groups like '\\1.ogg' when used with filter-include")
parser.add_argument("-f","--filter", help="filter matched files with regex and keep rest") parser.add_argument("-fi","--filter-include", help="include files matched with regex and ignore rest")
parser.add_argument("-i","--include", help="include matched files with regex and ignore rest") parser.add_argument("-fe","--filter-exclude", help="exclude files matched with regex and keep rest")
parser.add_argument("-s","--single", help="generate single files per list match", action='store_true') 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("-l","--list", help="list only results and don't write .txtp", action='store_true')
parser.add_argument("-cla","--command-loop-auto", help="sets auto-loop (last segment)", action='store_true')
parser.add_argument("-clf","--command-loop-force", help="sets auto-loop (last segment) even with 1 segment", action='store_true')
parser.add_argument("-cls","--command-loop-start", help="sets loop start segment") 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("-cle","--command-loop-end", help="sets loop end segment")
parser.add_argument("-cv","--command-volume", help="sets volume") parser.add_argument("-cv","--command-volume", help="sets volume")
@ -42,35 +44,36 @@ def parse():
return parser.parse_args() return parser.parse_args()
def is_file_ok(args, glob_file): def is_file_ok(args, file):
if not os.path.isfile(glob_file): if not os.path.isfile(file):
return False return False
if glob_file.endswith(".py"): if file.endswith(".py"):
return False return False
if args.filter: file_test = os.path.basename(file)
filename_test = os.path.basename(glob_file) if args.filter_exclude:
p = re.compile(args.filter) if args.p_exclude.match(file_test) != None:
if p.match(filename_test) != None:
return False return False
if args.include: if args.filter_include:
filename_test = os.path.basename(glob_file) if args.p_include.match(file_test) == None:
p = re.compile(args.include)
if p.match(filename_test) == None:
return False return False
return True return True
def get_txtp_name(args, segment): def get_txtp_name(args, file):
txtp_name = '' txtp_name = ''
if args.name: if args.filter_include and args.name and '\\' in args.name:
file_test = os.path.basename(file)
txtp_name = args.p_include.sub(args.name, file_test)
elif args.name:
txtp_name = args.name txtp_name = args.name
elif args.single: elif args.single:
txtp_name = os.path.splitext(os.path.basename(segment))[0] txtp_name = os.path.splitext(os.path.basename(file))[0]
else: else:
txtp_name = os.path.splitext(os.path.basename(args.files))[0] txtp_name = os.path.splitext(os.path.basename(args.files))[0]
@ -80,45 +83,45 @@ def get_txtp_name(args, segment):
if txtp_name.endswith('_'): if txtp_name.endswith('_'):
txtp_name = txtp_name[:-1] txtp_name = txtp_name[:-1]
if txtp_name == '':
txtp_name = 'bgm' if not txtp_name:
txtp_name = 'bgm'
if not txtp_name.endswith(".txtp"): if not txtp_name.endswith(".txtp"):
txtp_name += ".txtp" txtp_name += ".txtp"
return txtp_name return txtp_name
def main(): def main():
args = parse() args = parse()
if args.filter_include:
args.p_include = re.compile(args.filter_include)
if args.filter_exclude:
args.p_exclude = re.compile(args.filter_exclude)
# get target files # get target files
glob_files = glob.glob(args.files) files = glob.glob(args.files)
# process matches and add to output list # process matches and add to output list
files = [] txtps = {}
segments = [] for file in files:
for glob_file in glob_files: if not is_file_ok(args, file):
if not is_file_ok(args, glob_file):
continue continue
if args.single: name = get_txtp_name(args, file)
name = get_txtp_name(args, glob_file) if not name in txtps:
segments = [glob_file] txtps[name] = []
files.append( (name,segments) ) txtps[name].append(file)
else:
segments.append(glob_file)
if not args.single:
name = get_txtp_name(args, '')
files.append( (name,segments) )
if not files or not segments: if not txtps:
print("no files found") print("no files found")
exit() exit()
# list info # list info
for name, segments in files: for name, segments in txtps.items():
print("file: " + name) print("file: " + name)
for segment in segments: for segment in segments:
print(" " + segment) print(" " + segment)
@ -127,10 +130,17 @@ def main():
exit() exit()
# write resulting files # write resulting files
for name, segments in files: for name, segments in txtps.items():
len_segments = len(segments)
with open(name,"w+") as ftxtp: with open(name,"w+") as ftxtp:
for segment in segments: for i, segment in enumerate(segments):
ftxtp.write(segment + "\n") if args.command_loop_force and len_segments == 1:
ftxtp.write(segment + " #e\n")
else:
ftxtp.write(segment + "\n")
if args.command_loop_auto or args.command_loop_force and len_segments > 1:
ftxtp.write("loop_mode = auto\n")
if args.command_loop_start: if args.command_loop_start:
ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n") ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n")
if args.command_loop_end: if args.command_loop_end:

View File

@ -125,66 +125,6 @@ static int record_interrupt(void) {
return ret; return ret;
} }
static void usage(const char* progname) {
song_settings_t default_par = DEFAULT_PARAMS;
const char* default_driver = "???";
{
ao_info *info = ao_driver_info(driver_id);
if (info)
default_driver = info->short_name;
}
printf(APP_INFO "\n"
"Usage: %s [options] <infile> ...\n"
"\n"
"Options:\n"
" -d DRV Use output driver DRV [%s]; available drivers:\n"
" ",
progname,
default_driver);
{
ao_info **info_list;
int driver_count = 0;
int i;
info_list = ao_driver_info_list(&driver_count);
for (i = 0; i < driver_count; i++)
printf("%s ", info_list[i]->short_name);
}
printf("\n"
" -f OUTFILE Set output filename for a file driver specified with -d\n"
" -o KEY:VAL Pass option KEY with value VAL to the output driver\n"
" (see https://www.xiph.org/ao/doc/drivers.html)\n"
" -b N Use an audio buffer of N kilobytes [%d]\n"
" -@ LSTFILE Read playlist from LSTFILE\n"
" -h Print this help\n"
" -r Repeat playback again (with fade, use -p for infinite loops)\n"
" -v Display stream metadata and playback progress\n"
" -S N Play substream with index N\n"
"\n"
"Looping options:\n"
" -M MINTIME Loop for a playback time of at least MINTIME seconds\n"
" -L N Loop N times [%.1f]\n"
" -F FTIME End playback with a fade-out of FTIME seconds [%.1f]\n"
" -D FDELAY Delay fade-out for an additional FDELAY seconds [%.1f]\n"
" -i Ignore loop\n"
" -e Force loop (loop only if file doesn't have loop points)\n"
" -E Really force loop (repeat file)\n"
" -p Play forever (loops file until stopped)\n"
"\n"
"<infile> can be any stream file type supported by vgmstream, or an .m3u/.m3u8\n"
"playlist referring to same. This program supports the \"EXT-X-VGMSTREAM\" tag\n"
"in playlists, and files compressed with gzip/bzip2/xz.\n",
buffer_size_kb,
default_par.loop_count,
default_par.fade_time,
default_par.fade_delay
);
}
/* Opens the audio device with the appropriate parameters /* Opens the audio device with the appropriate parameters
*/ */
@ -657,6 +597,70 @@ static void add_driver_option(const char *key_value) {
ao_append_option(&device_options, buf, value); ao_append_option(&device_options, buf, value);
} }
static void usage(const char* progname, int is_help) {
song_settings_t default_par = DEFAULT_PARAMS;
const char* default_driver = "???";
{
ao_info* info = ao_driver_info(driver_id);
if (info)
default_driver = info->short_name;
}
fprintf(is_help ? stdout : stderr, APP_INFO "\n"
"Usage: %s [options] <infile> ...\n"
"Options:\n"
" -D DRV Use output driver DRV [%s]; available drivers:\n"
" ",
progname,
default_driver);
{
ao_info** info_list;
int driver_count = 0;
int i;
info_list = ao_driver_info_list(&driver_count);
for (i = 0; i < driver_count; i++) {
fprintf(is_help ? stdout : stderr, "%s ", info_list[i]->short_name);
}
}
fprintf(is_help ? stdout : stderr, "\n"
" -P KEY:VAL Pass parameter KEY with value VAL to the output driver\n"
" (see https://www.xiph.org/ao/doc/drivers.html)\n"
" -B N Use an audio buffer of N kilobytes [%d]\n"
" -@ LSTFILE Read playlist from LSTFILE\n"
"\n"
" -o OUTFILE Set output filename for a file driver specified with -D\n"
" -m Display stream metadata and playback progress\n"
" -s N Play subsong index N\n"
" -h Print this help\n"
"\n"
"Looping options:\n"
" -M MINTIME Loop for a playback time of at least MINTIME seconds\n"
" -l FLOOPS Loop N times [%.1f]\n"
" -f FTIME End playback with a fade-out of FTIME seconds [%.1f]\n"
" -d FDELAY Delay fade-out for an additional FDELAY seconds [%.1f]\n"
" -i Ignore loop\n"
" -e Force loop (loop only if file doesn't have loop points)\n"
" -E Really force loop (repeat file)\n"
" -c Play forever (continuously), looping file until stopped\n"
" -r Repeat playback again (with fade, use -c for infinite loops)\n"
"\n"
"<infile> can be any stream file type supported by vgmstream, or an .m3u/.m3u8\n"
"playlist referring to same. This program supports the \"EXT-X-VGMSTREAM\" tag\n"
"in playlists, and files compressed with gzip/bzip2/xz.\n",
buffer_size_kb,
default_par.loop_count,
default_par.fade_time,
default_par.fade_delay
);
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
int error = 0; int error = 0;
int opt; int opt;
@ -673,7 +677,7 @@ int main(int argc, char **argv) {
if (argc == 1) { if (argc == 1) {
/* We were invoked with no arguments */ /* We were invoked with no arguments */
usage(argv[0]); usage(argv[0], 0);
goto done; goto done;
} }
@ -683,7 +687,7 @@ again_opts:
cfg = default_par; cfg = default_par;
} }
while ((opt = getopt(argc, argv, "-D:F:L:M:S:b:d:f:o:@:hrvieEp")) != -1) { while ((opt = getopt(argc, argv, "-D:f:l:M:s:B:d:o:P:@:hrmieEc")) != -1) {
switch (opt) { switch (opt) {
case 1: case 1:
/* glibc getopt extension /* glibc getopt extension
@ -701,20 +705,20 @@ again_opts:
} }
break; break;
case 'D': case 'd':
cfg.fade_delay = atof(optarg); cfg.fade_delay = atof(optarg);
break; break;
case 'F': case 'f':
cfg.fade_time = atof(optarg); cfg.fade_time = atof(optarg);
break; break;
case 'L': case 'l':
cfg.loop_count = atof(optarg); cfg.loop_count = atof(optarg);
break; break;
case 'M': case 'M':
cfg.min_time = atof(optarg); cfg.min_time = atof(optarg);
cfg.loop_count = -1.0; cfg.loop_count = -1.0;
break; break;
case 'S': case 's':
cfg.stream_index = atoi(optarg); cfg.stream_index = atoi(optarg);
break; break;
case 'i': case 'i':
@ -726,15 +730,15 @@ again_opts:
case 'E': case 'E':
cfg.really_force_loop = 1; cfg.really_force_loop = 1;
break; break;
case 'p': case 'c':
cfg.play_forever = 1; cfg.play_forever = 1;
break; break;
case 'b': case 'B':
if (!buffer) if (!buffer)
buffer_size_kb = atoi(optarg); buffer_size_kb = atoi(optarg);
break; break;
case 'd': case 'D':
driver_id = ao_driver_id(optarg); driver_id = ao_driver_id(optarg);
if (driver_id < 0) { if (driver_id < 0) {
fprintf(stderr, "Invalid output driver \"%s\"\n", optarg); fprintf(stderr, "Invalid output driver \"%s\"\n", optarg);
@ -742,19 +746,19 @@ again_opts:
goto done; goto done;
} }
break; break;
case 'f': case 'o':
out_filename = optarg; out_filename = optarg;
break; break;
case 'h': case 'h':
usage(argv[0]); usage(argv[0], 1);
goto done; goto done;
case 'o': case 'P':
add_driver_option(optarg); add_driver_option(optarg);
break; break;
case 'r': case 'r':
repeat = 1; repeat = 1;
break; break;
case 'v': case 'm':
verbose = 1; verbose = 1;
break; break;
default: default:

View File

@ -49,9 +49,9 @@
static size_t make_wav_header(uint8_t* buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end); static size_t make_wav_header(uint8_t* buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end);
static void usage(const char* name, int is_full) { static void usage(const char* progname, int is_help) {
fprintf(is_full ? stdout : stderr, APP_INFO "\n" fprintf(is_help ? stdout : stderr, APP_INFO "\n"
"Usage: %s [-o <outfile.wav>] [options] <infile> ...\n" "Usage: %s [-o <outfile.wav>] [options] <infile> ...\n"
"Options:\n" "Options:\n"
" -o <outfile.wav>: name of output .wav file, default <infile>.wav\n" " -o <outfile.wav>: name of output .wav file, default <infile>.wav\n"
@ -75,10 +75,10 @@ static void usage(const char* name, int is_full) {
" -V: print version info and supported extensions as JSON\n" " -V: print version info and supported extensions as JSON\n"
" -I: print requested file info as JSON\n" " -I: print requested file info as JSON\n"
#endif #endif
, name); , progname);
if (!is_full) if (!is_help)
return; return;
fprintf(is_full ? stdout : stderr, fprintf(is_help ? stdout : stderr,
" -2 N: only output the Nth (first is 0) set of stereo channels\n" " -2 N: only output the Nth (first is 0) set of stereo channels\n"
" -x: decode and print adxencd command line to encode as ADX\n" " -x: decode and print adxencd command line to encode as ADX\n"
" -g: decode and print oggenc command line to encode as OGG\n" " -g: decode and print oggenc command line to encode as OGG\n"

View File

@ -10,9 +10,7 @@ REM # extensions found unless specified (except a few).
REM # REM #
REM # Options: see below. REM # Options: see below.
REM #------------------------------------------------------------------------- REM #-------------------------------------------------------------------------
REM #TODO: escape & ! % in file/folder names
setlocal enableDelayedExpansion
REM #------------------------------------------------------------------------- REM #-------------------------------------------------------------------------
REM #options REM #options
@ -35,7 +33,8 @@ REM # -Po: performance test old (same but also don't write file)
REM # -Pm: performance test new (only parse meta) REM # -Pm: performance test new (only parse meta)
REM # -Pmo: performance test old (only parse meta) REM # -Pmo: performance test old (only parse meta)
set OP_PERFORMANCE= set OP_PERFORMANCE=
REM # -fc <exe>: file comparer (Windows's FC is slow) REM # -fc <exe>: file comparer
REM # Windows's FC is slow, try -fc ffc.exe (https://github.com/bnnm/vgm-tools/tree/master/misc/ffc)
set OP_CMD_FC=fc /a /b set OP_CMD_FC=fc /a /b
@ -60,9 +59,9 @@ goto set_options
:end_options :end_options
REM # output color defs REM # output color defs
set C_W=0e set COLOR_WN=0e
set C_E=0c set COLOR_ER=0c
set C_O=0f set COLOR_OK=0f
REM # remove command options and possibly " REM # remove command options and possibly "
for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_OLD%") do set CHECK_OLD=%%A for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_OLD%") do set CHECK_OLD=%%A
@ -97,11 +96,17 @@ REM # process files
for /f "delims=" %%x in ('%CMD_DIR% ^| %CMD_FIND%') do ( for /f "delims=" %%x in ('%CMD_DIR% ^| %CMD_FIND%') do (
set CMD_FILE=%%x set CMD_FILE=%%x
REM # must enable/disable after setting CMD as ! & in dir names would become expanded/removed otherwise
setlocal EnableDelayedExpansion
if "%OP_PERFORMANCE%" == "" ( if "%OP_PERFORMANCE%" == "" (
call :process_file "!CMD_FILE!" call :process_file "!CMD_FILE!"
) else ( ) else (
call :performance_file "!CMD_FILE!" call :performance_file "!CMD_FILE!"
) )
endlocal
) )
REM # find time elapsed REM # find time elapsed
@ -131,58 +136,59 @@ REM # ########################################################################
set CMD_SHORTNAME=%~n1 set CMD_SHORTNAME=%~n1
if "%CMD_SHORTNAME%" == "" goto process_file_continue if "%CMD_SHORTNAME%" == "" goto process_file_continue
REM # get file
set CMD_FILE=%1 REM # get file (not from arg %1)
set CMD_FILE=%CMD_FILE:"=% set CMD_FILE=!CMD_FILE!
REM echo VTRS: file %CMD_FILE% set CMD_FILE=!CMD_FILE:"=!
REM echo VTRS: file !CMD_FILE!
REM # old/new temp output REM # old/new temp output
set WAV_OLD=%CMD_FILE%.old.wav set WAV_OLD=!CMD_FILE!.old.wav
set TXT_OLD=%CMD_FILE%.old.txt set TXT_OLD=!CMD_FILE!.old.txt
set CMD_VGM_OLD="%OP_CMD_OLD%" -o "%WAV_OLD%" "%CMD_FILE%" set CMD_VGM_OLD="%OP_CMD_OLD%" -o "!WAV_OLD!" "!CMD_FILE!"
%CMD_VGM_OLD% 1> "%TXT_OLD%" 2>&1 & REM || goto error !CMD_VGM_OLD! 1> "!TXT_OLD!" 2>&1 & REM || goto error
set WAV_NEW=%CMD_FILE%.new.wav set WAV_NEW=!CMD_FILE!.new.wav
set TXT_NEW=%CMD_FILE%.new.txt set TXT_NEW=!CMD_FILE!.new.txt
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%" set CMD_VGM_NEW="%OP_CMD_NEW%" -o "!WAV_NEW!" "!CMD_FILE!"
%CMD_VGM_NEW% 1> "%TXT_NEW%" 2>&1 & REM || goto error !CMD_VGM_NEW! 1> "!TXT_NEW!" 2>&1 & REM || goto error
REM # ignore if no files are created (unsupported formats) REM # ignore if no files are created (unsupported formats)
if not exist "%WAV_NEW%" ( if not exist "!WAV_NEW!" (
if not exist "%WAV_OLD%" ( if not exist "!WAV_OLD!" (
REM echo VRTS: nothing created for file %CMD_FILE% REM echo VRTS: nothing created for file !CMD_FILE!
if exist "%TXT_NEW%" del /a:a "%TXT_NEW%" if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
if exist "%TXT_OLD%" del /a:a "%TXT_OLD%" if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
goto process_file_continue goto process_file_continue
) )
) )
REM # compare files (without /b may to be faster for small files?) REM # compare files (without /b may to be faster for small files?)
set CMP_WAV=%OP_CMD_FC% "%WAV_OLD%" "%WAV_NEW%" set CMP_WAV=%OP_CMD_FC% "!WAV_OLD!" "!WAV_NEW!"
set CMP_TXT=%OP_CMD_FC% "%TXT_OLD%" "%TXT_NEW%" set CMP_TXT=%OP_CMD_FC% "!TXT_OLD!" "!TXT_NEW!"
%CMP_WAV% 1> nul 2>&1 !CMP_WAV! 1> nul 2>&1
set CMP_WAV_ERROR=0 set CMP_WAV_ERROR=0
if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1 if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1
%CMP_TXT% 1> nul 2>&1 !CMP_TXT! 1> nul 2>&1
set CMP_TXT_ERROR=0 set CMP_TXT_ERROR=0
if %ERRORLEVEL% NEQ 0 set CMP_TXT_ERROR=1 if %ERRORLEVEL% NEQ 0 set CMP_TXT_ERROR=1
REM # print output REM # print output
if %CMP_WAV_ERROR% EQU 1 ( if %CMP_WAV_ERROR% EQU 1 (
if %CMP_TXT_ERROR% EQU 1 ( if %CMP_TXT_ERROR% EQU 1 (
call :echo_color %C_E% "%CMD_FILE%" "wav and txt diffs" call :echo_color %COLOR_ER% "!CMD_FILE!" "wav and txt diffs"
) else ( ) else (
call :echo_color %C_E% "%CMD_FILE%" "wav diffs" call :echo_color %COLOR_ER% "!CMD_FILE!" "wav diffs"
) )
set /a "FILES_KO+=1" set /a "FILES_KO+=1"
) else ( ) else (
if %CMP_TXT_ERROR% EQU 1 ( if %CMP_TXT_ERROR% EQU 1 (
call :echo_color %C_W% "%CMD_FILE%" "txt diffs" call :echo_color %COLOR_WN% "!CMD_FILE!" "txt diffs"
) else ( ) else (
if "%OP_NOCORRECT%" == "" ( if "%OP_NOCORRECT%" == "" (
call :echo_color %C_O% "%CMD_FILE%" "no diffs" call :echo_color %COLOR_OK% "!CMD_FILE!" "no diffs"
) )
) )
set /a "FILES_OK+=1" set /a "FILES_OK+=1"
@ -190,10 +196,10 @@ REM # ########################################################################
REM # delete temp files REM # delete temp files
if "%OP_NODELETE%" == "" ( if "%OP_NODELETE%" == "" (
if exist "%WAV_OLD%" del /a:a "%WAV_OLD%" if exist "!WAV_OLD!" del /a:a "!WAV_OLD!"
if exist "%TXT_OLD%" del /a:a "%TXT_OLD%" if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
if exist "%WAV_NEW%" del /a:a "%WAV_NEW%" if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
if exist "%TXT_NEW%" del /a:a "%TXT_NEW%" if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
) )
:process_file_continue :process_file_continue
@ -209,39 +215,39 @@ REM # ########################################################################
set CMD_SHORTNAME=%~n1 set CMD_SHORTNAME=%~n1
if "%CMD_SHORTNAME%" == "" goto performance_file_continue if "%CMD_SHORTNAME%" == "" goto performance_file_continue
REM # get file REM # get file (not from arg %1)
set CMD_FILE=%1 set CMD_FILE=!CMD_FILE!
set CMD_FILE=%CMD_FILE:"=% set CMD_FILE=!CMD_FILE:"=!
REM echo VTRS: file %CMD_FILE% REM echo VTRS: file !CMD_FILE!
set WAV_NEW=%CMD_FILE%.test.wav set WAV_NEW=!CMD_FILE!.test.wav
if "%OP_PERFORMANCE%" == "1" ( if "%OP_PERFORMANCE%" == "1" (
set CMD_VGM="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%" set CMD_VGM="%OP_CMD_NEW%" -o "!WAV_NEW!" "!CMD_FILE!"
) )
if "%OP_PERFORMANCE%" == "2" ( if "%OP_PERFORMANCE%" == "2" (
set CMD_VGM="%OP_CMD_NEW%" -O "%CMD_FILE%" set CMD_VGM="%OP_CMD_NEW%" -O "!CMD_FILE!"
) )
if "%OP_PERFORMANCE%" == "3" ( if "%OP_PERFORMANCE%" == "3" (
set CMD_VGM="%OP_CMD_OLD%" -o "%WAV_OLD%" "%CMD_FILE%" set CMD_VGM="%OP_CMD_OLD%" -o "!WAV_OLD!" "!CMD_FILE!"
) )
if "%OP_PERFORMANCE%" == "4" ( if "%OP_PERFORMANCE%" == "4" (
set CMD_VGM="%OP_CMD_OLD%" -O "%CMD_FILE%" set CMD_VGM="%OP_CMD_OLD%" -O "!CMD_FILE!"
) )
if "%OP_PERFORMANCE%" == "5" ( if "%OP_PERFORMANCE%" == "5" (
set CMD_VGM="%OP_CMD_NEW%" -m "%CMD_FILE%" set CMD_VGM="%OP_CMD_NEW%" -m "!CMD_FILE!"
) )
if "%OP_PERFORMANCE%" == "6" ( if "%OP_PERFORMANCE%" == "6" (
set CMD_VGM="%OP_CMD_OLD%" -m "%CMD_FILE%" set CMD_VGM="%OP_CMD_OLD%" -m "!CMD_FILE!"
) )
%CMD_VGM% 1> nul 2>&1 & REM || goto error !CMD_VGM! 1> nul 2>&1 & REM || goto error
set CMP_WAV_ERROR=0 set CMP_WAV_ERROR=0
if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1 if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1
call :echo_color %C_O% "%CMD_FILE%" "done" call :echo_color %COLOR_OK% "!CMD_FILE!" "done"
REM # ignore output REM # ignore output
if exist "%WAV_NEW%" del /a:a "%WAV_NEW%" if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
:performance_file_continue :performance_file_continue
exit /B exit /B
@ -252,14 +258,17 @@ REM # ########################################################################
REM # hack to get colored output in Windows CMD using findstr + temp file REM # hack to get colored output in Windows CMD using findstr + temp file
REM # ######################################################################## REM # ########################################################################
:echo_color :echo_color
set TEMP_FILE=%2-result REM # don't use %2 but !CMD_FILE!
set TEMP_FILE=%TEMP_FILE:"=% set TEMP_FILE=!CMD_FILE!-result
set TEMP_FILE=!TEMP_FILE:"=!
set TEMP_TEXT=%3 set TEMP_TEXT=%3
set TEMP_TEXT=%TEMP_TEXT:"=% set TEMP_TEXT=!TEMP_TEXT:"=!
echo %TEMP_TEXT% > "%TEMP_FILE%" echo !TEMP_TEXT! > "!TEMP_FILE!"
REM # show colored filename + any text in temp file REM # show colored filename + any text in temp file
findstr /v /a:%1 /r "^$" "%TEMP_FILE%" nul findstr /a:%1 /r ".*" "!TEMP_FILE!" nul
del "%TEMP_FILE%" del "!TEMP_FILE!"
exit /B exit /B
REM :echo_color end, continue from last call REM :echo_color end, continue from last call

View File

@ -3,32 +3,33 @@ This document explains how to build each of vgmstream's components and libraries
## Compilation requirements ## Compilation requirements
vgmstream can be compiled using one of many build scripts that are available in this repository. Components are detailed below, but if you are new to development you probably want one of these: vgmstream can be compiled using one of several build scripts that are available in this repository. Components are detailed below, but if you are new to development you probably want one of these:
- **Windows**: [simple scripts](#simple-scripts-builds) + [Visual Studio](#microsofts-visual-c-msvc--visual-studio--msbuild-compiler) - **Windows**: [simple scripts](#simple-scripts-builds) + [Visual Studio](#microsofts-visual-c-msvc--visual-studio--msbuild-compiler)
- **Linux**: [Cmake](#cmake-builds) + [GCC](#gcc--make-compiler) - **Linux**: [CMake](#cmake-builds) + [GCC](#gcc--make-compiler)
- **macOS**: [Cmake](#cmake-builds) + [Clang](#clang-compiler) - **macOS**: [CMake](#cmake-builds) + [Clang](#clang-compiler)
- **Web**: [Cmake](#cmake-builds) + [Emscripten](#emscripten-compiler) - **Web**: [CMake](#cmake-builds) + [Emscripten](#emscripten-compiler)
Because each module has different quirks one can't use a single tool for everything. You should be able to build most using a standard *compiler* (GCC/MSVC/Clang) using common *build systems* (scripts/CMake/autotools) in any typical *OS* (Windows/Linux/macOS). Because each module has different quirks one can't use a single tool for everything. You should be able to build most using a standard *compiler* (GCC/MSVC/Clang) using common *build systems* (scripts/CMake/autotools) in any typical *OS* (Windows/Linux/macOS).
On **Windows**, 64-bit support may work but has been minimally tested, since the main use of vgmstream is plugins for 32-bit players. Windows libraries for extra codecs are included for 32-bit only at the moment, and there may be bugs in some codecs and formats. 64-bit support should work but hasn't been throughly tested (may have subtle decoding bugs in some codecs), since most used components are plugins for 32-bit players. Windows libraries for extra codecs are included for 32-bit only at the moment.
Though it's rather flexible (like using Windows with GCC and autotools), some combos may be a bit more complex to get working depending on your system and other factors. Though it's rather flexible (like using Windows with GCC and autotools), some combos may be a bit more complex to get working depending on your system and other factors.
### CMake (builds) ## Quick guide
A tool used to generate common build files (for *make*, *VS/MSBuild*, etc), that in turn can be used to compile vgmstream's modules instead of using the existing scripts and files. Needs v3.6 or later:
- https://cmake.org/download/
On **Linux**, the CMake script can automatically download and build the source code for dependencies that it requires. It is also capable of creating a statically linked binary for distribution purposes. The build steps with CMake are as follows: ### Linux
- Use these commands to install dependencies and compile with CMake + make
```sh ```sh
sudo apt-get update sudo apt-get update
sudo apt-get install -y gcc g++ make build-essential git # base deps
sudo apt-get install -y gcc g++ make cmake build-essential git
# optional: for extra formats (can be ommited to build with static libs)
sudo apt-get install -y libmpg123-dev libvorbis-dev libspeex-dev sudo apt-get install -y libmpg123-dev libvorbis-dev libspeex-dev
sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
sudo apt-get install -y libao-dev audacious-dev
sudo apt-get install -y yasm libopus-dev sudo apt-get install -y yasm libopus-dev
sudo apt-get install -y cmake # optional: for vgmstream 123 and audacious
sudo apt-get install -y libao-dev audacious-dev
git clone https://github.com/vgmstream/vgmstream git clone https://github.com/vgmstream/vgmstream
cd vgmstream cd vgmstream
@ -38,13 +39,108 @@ cd build
cmake .. cmake ..
make make
``` ```
- Output files are located in `./build` and subdirs
- May use `./make-build-cmake.sh` instead (same thing)
- autotools and simple makefiles also work (less libs)
### macOS
- *Should* work with CMake + make, like the above example
- Replace `apt-get intall` with manual installation of those deps
- Or with *Homebrew*: `brew install cmake mpg123 libvorbis ffmpeg libao`
- May try https://formulae.brew.sh/formula/vgmstream instead (not part of this project)
### Windows
- Install Visual Studio: https://www.visualstudio.com/downloads/ (for C/C++, with "MFC support" and "ATL support)
- Make a file called `msvc-build.config.ps1` in vgmstream's root, with your installed toolset and SDK:
```ps1
# - toolsets: "" (default), "v140" (VS 2015), "v141" (VS 2017), "v141_xp" (XP support), "v142" (VS 2019), etc
# - sdks: "" (default), "7.0" (Win7 SDK), "8.1" (Win8 SDK), "10.0" (Win10 SDK), etc
$toolset = "142"
$sdk = "10.0"
```
- Execute file `msvc-build-package.bat` to compile
- Output files are located in `./bin`
## Full guide
This guide is mainly geared towards beginner devs, introducing concepts in steps. Many things may be obvious to experienced devs, so feel free to skim or skip sections.
### GCC / Make (compiler)
Common C compiler, most development is done with this.
On **Windows** you need one of these somewhere in PATH:
- MinGW-w64 (32bit version): https://sourceforge.net/projects/mingw-w64/
- Use this for easier standalone executables
- [Latest online MinGW installer](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download) with any config should work (for example: gcc-8.1.0, i686, win32, sjlj).
- Or download and unzip the [portable MinGW package](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/8.1.0/threads-win32/sjlj/i686-8.1.0-release-win32-sjlj-rt_v6-rev0.7z/download)
- MSYS2 with the MinGW-w64_shell (32bit) package: https://msys2.github.io/
- Resulting binaries may depend on `msys*.dll`.
On **Linux** it should be included by default in the distribution, or can be easily installed using the distro's package manager (for example `sudo apt-get install gcc g++ make`).
On **macOS** may be installed with a package manager like *Homebrew*, but using *Clang* is probably easier.
Any versions that are not too ancient should work, since vgmstream uses standard C. GCC usually comes with *Make*, a program that can be used to build vgmstream.
### Microsoft's Visual C++ (MSVC) / Visual Studio / MSBuild (compiler)
Alt C compiler (**Windows** only), auto-generated builds for Windows use this. Bundled in:
- Visual Studio (2015/2017/2019/latest): https://www.visualstudio.com/downloads/
Visual Studio Community (free) should work, but you may need to register after a trial period. Even after trial you can still use *MSBuild*, command-line tool that actually does all the building, calling the *MSVC* compiler (Visual Studio itself is just an IDE for development and not actually needed).
Instead of the full (usually huge) Visual Studio, you can also get "Build Tools for Visual Studio", variation that only installs *MSBuild* and necessary files without the IDE. Usually found in the above link, under "Tools for Visual Studio" (or google as MS's links tend to move around).
When installing check the "Desktop development with C++" group, and optionally select "MFC support" and "ATL support" sub-options to build foobar2000 plugin (you can modify that or re-install IDE later, by running installed "Visual Studio Installer"). You could include MSVC v141 (2017) compatibility too just in case, since it's mainly tested with that.
Older versions of MSVC (2010 and earlier) have limited C support and may not work with latest commits, while reportedly beta/new versions aren't always very stable. Also, only projects (`.vcxproj`) for VS2015+ are included (CMake may be able to generate older `.vcproj` if you really need them). Some very odd issues affecting MSVC only have been found and fixed before. Keep in mind all of this if you run into problems.
### Clang (compiler)
Alt C compiler, reportedly works fine on **macOS** and may used as a replacement of GCC without issues.
- https://releases.llvm.org/download.html
Should be usable on **Linux** and possibly **Windows** with CMake. For default Makefiles may need to set compiler vars appropriately (`CC=clang`, `AR=llvm-ar` and so on).
### Emscripten (compiler)
C compiler that generates *WebAssembly* (custom Javascript), to build vgmstream's components with in-browser support.
First, follow the *Emscripten* installation instructions:
- https://emscripten.org/docs/getting_started/downloads.html
- https://emscripten.org/docs/compiling/Building-Projects.html#building-projects
Though basically:
```sh
git clone https://github.com/emscripten-core/emsdk
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
```
Then you should be able to build it on **Linux** (**Windows** should be possible too, but has some issues at the moment), for example with CMake:
```sh
git clone https://github.com/vgmstream/vgmstream
cd vgmstream
mkdir -p embuild
cd embuild
emcmake cmake ..
make
```
You can compile faster using `make -j 5` instead of the last `make` command (replace `5` with the number of cores your CPU has plus one), but please note that, with multiple jobs, in case any issues occur the output will become useless. You can compile faster using `make -j 5` instead of the last `make` command (replace `5` with the number of cores your CPU has plus one), but please note that, with multiple jobs, in case any issues occur the output will become useless.
The output files are `build/cli/vgmstream-cli` (CLI decoder), `build/cli/vgmstream123` (CLI player), and `build/audacious/vgmstream.so` (Audacious plugin). The output files `vgmstream-cli.wasm` and `vgmstream-cli.js` will be located in the `embuild/cli` directory.
For more information and options see the full guide in the [CMAKE.md](CMAKE.md) file. Or with the base makefiles (the output may need to be renamed to .js):
```sh
git clone https://github.com/vgmstream/vgmstream
cd vgmstream
make vgmstream-cli CC=emcc AR=emar strip=echo
```
Note that doing in-source builds of CMake (`cmake .`) is not recommended, as that may clobber default build files. Load `vgmstream-cli.js` in a web page, you will be able to call the `callMain()` function from the browser developer console. Parameters to vgmstream can be passed in an array: `callMain(["-i", "input_file.pcm"])`. Files can be accessed through Emscripten [File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html) (`FS`).
For a fully-featured player see:
- https://github.com/KatieFrogs/vgmstream-web
### Simple scripts (builds) ### Simple scripts (builds)
Default build scripts are included in the source that can compile vgmstream, though limited in some ways. Default build scripts are included in the source that can compile vgmstream, though limited in some ways.
@ -53,7 +149,7 @@ Default build scripts are included in the source that can compile vgmstream, tho
First, you may need to either open the `.sln` and change project compiler (*PlatformToolset*) and SDK (*WindowsTargetPlatformVersion*) to your installed version, or edit `msvc-build.ps1` and set the variables near *CONFIG*. To avoid modifying files, you can also create a file named `msvc-build.config.ps1` with: First, you may need to either open the `.sln` and change project compiler (*PlatformToolset*) and SDK (*WindowsTargetPlatformVersion*) to your installed version, or edit `msvc-build.ps1` and set the variables near *CONFIG*. To avoid modifying files, you can also create a file named `msvc-build.config.ps1` with:
```ps1 ```ps1
# - toolsets: "" (p), "v140" (MSVC 2015), "v141" (MSVC 2017), "v141_xp" (XP support), "v142" (MSVC 2019), etc # - toolsets: "" (default), "v140" (VS 2015), "v141" (VS 2017), "v141_xp" (XP support), "v142" (VS 2019), etc
# - sdks: "" (default), "7.0" (Win7 SDK), "8.1" (Win8 SDK), "10.0" (Win10 SDK), etc # - sdks: "" (default), "7.0" (Win7 SDK), "8.1" (Win8 SDK), "10.0" (Win10 SDK), etc
$toolset = "142" $toolset = "142"
$sdk = "10.0" $sdk = "10.0"
@ -81,6 +177,22 @@ chmod +x version-get.sh version-make.sh make-build.sh
./make-build.sh ./make-build.sh
``` ```
### CMake (builds)
A tool used to generate common build files (for *make*, *VS/MSBuild*, etc), that in turn can be used to compile vgmstream's modules instead of using the existing scripts and files. Needs v3.6 or later:
- https://cmake.org/download/
On **Windows** you can use *cmake-gui*, that should be mostly self-explanatory. You need to set the *source dir*, *build dir*, *config options*, then hit *Configure* to set save options and build type (for example *Visual Studio* project files), then *Generate* to actually create files. If you want to change options, hit *Configure* and *Generate* again.
On **Linux**, the CMake script can automatically download and build the source code for dependencies that it requires. It is also capable of creating a statically linked binary for distribution purposes. See `./make-build-cmake.sh` (basically install desired deps then `mkdir -p build && cd build`, `cmake ..`, `make`).
You can compile faster using `make -j 5` instead of the last `make` command (replace `5` with the number of cores your CPU has plus one), but please note that, with multiple jobs, in case any issues occur the output will become useless.
The output files are `build/cli/vgmstream-cli` (CLI decoder), `build/cli/vgmstream123` (CLI player), and `build/audacious/vgmstream.so` (Audacious plugin).
For more information and options see the full guide in the [CMAKE.md](CMAKE.md) file.
Note that doing in-source builds of CMake (`cmake .` in vgmstream's root dir) is not recommended, as that may clobber default build files (try `cmake -S . -B build` or building some `./build` subfolder).
### autotools (builds) ### autotools (builds)
Autogenerated *make* scripts, used by some modules (mainly Audacious for **Linux**, and external libs). Autogenerated *make* scripts, used by some modules (mainly Audacious for **Linux**, and external libs).
@ -121,93 +233,16 @@ Also for older libs, call `sh.exe ./configure` with either `--build=mingw32`, `
mingw32-make.exe -f Makefile.autotools LDFLAGS="-no-undefined -static-libgcc" MAKE=mingw32-make.exe mingw32-make.exe -f Makefile.autotools LDFLAGS="-no-undefined -static-libgcc" MAKE=mingw32-make.exe
``` ```
### GCC / Make (compiler)
Common C compiler, most development is done with this.
On **Windows** you need one of these somewhere in PATH:
- MinGW-w64 (32bit version): https://sourceforge.net/projects/mingw-w64/
- Use this for easier standalone executables
- [Latest online MinGW installer](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download) with any config should work (for example: gcc-8.1.0, i686, win32, sjlj).
- Or download and unzip the [portable MinGW package](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/8.1.0/threads-win32/sjlj/i686-8.1.0-release-win32-sjlj-rt_v6-rev0.7z/download)
- MSYS2 with the MinGW-w64_shell (32bit) package: https://msys2.github.io/
- Resulting binaries may depend on `msys*.dll`.
On **Linux** it should be included by default in the distribution, or can be easily installed using the distro's package manager (for example `sudo apt-get install gcc g++ make`).
On **macOS** may be installed with a package manager like *Homebrew*, but using *Clang* is probably easier.
Any versions that are not too ancient should work, since vgmstream uses standard C. GCC usually comes with *Make*, a program that can be used to build vgmstream.
### Microsoft's Visual C++ (MSVC) / Visual Studio / MSBuild (compiler)
Alt C compiler (**Windows** only), auto-generated builds for Windows use this. Bundled in:
- Visual Studio (2015/2017/2019/latest): https://www.visualstudio.com/downloads/
Visual Studio Community (free) should work, but you may need to register after a trial period. Even after trial you can still use *MSBuild*, command-line tool that actually does all the building, calling the *MSVC* compiler (Visual Studio itself is just an IDE for development and not actually needed).
Instead of the full (usually huge) Visual Studio, you can also get "Build Tools for Visual Studio", variation that only installs *MSBuild* and necessary files without the IDE. Usually found in the above link, under "Tools for Visual Studio" (or google as MS's links tend to move around).
When installing check the "Desktop development with C++" group, and optionally select "MFC support" and "ATL support" sub-options to build foobar2000 plugin (you can modify that or re-install IDE later, by running installed "Visual Studio Installer"). You could include MSVC v141 (2017) compatibility too just in case, since it's mainly tested with that.
Older versions of MSVC (2010 and earlier) have limited C support and may not work with latest commits, while reportedly beta/new versions aren't always very stable. Also, only projects (`.vcxproj`) for VS2015+ are included (CMake may be able to generate older `.vcproj` if you really need them). Some very odd issues affecting MSVC only have been found and fixed before. Keep in mind all of this if you run into problems.
### Clang (compiler)
Alt C compiler, reportedly works fine on **macOS** and may used as a replacement of GCC without issues.
- https://releases.llvm.org/download.html
Should be usable on **Linux** and possibly **Windows** with CMake. For default Makefiles may need to set compiler vars appropriately (`CC=clang`, `AR=llvm-ar` and so on).
### Emscripten (compiler)
It's possible to build vgmstream components with Emscripten for in-browser support.
Follow Emscripten installation instructions:
- https://emscripten.org/docs/getting_started/downloads.html
- https://emscripten.org/docs/compiling/Building-Projects.html#building-projects
Though basically:
```sh
git clone https://github.com/emscripten-core/emsdk
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
```
Then you should be able to build it on **Linux** (**Windows** would be possible too, but it has some issues at the moment), for example with CMake:
```sh
git clone https://github.com/vgmstream/vgmstream
cd vgmstream
mkdir -p embuild
cd embuild
emcmake cmake ..
make
```
You can compile faster using `make -j 5` instead of the last `make` command (replace `5` with the number of cores your CPU has plus one), but please note that, with multiple jobs, in case any issues occur the output will become useless.
The output files `vgmstream-cli.wasm` and `vgmstream-cli.js` will be located in the `embuild/cli` directory.
Or with the base makefiles (the output may need to be renamed to .js):
```sh
git clone https://github.com/vgmstream/vgmstream
cd vgmstream
make vgmstream-cli CC=emcc AR=emar strip=echo
```
Load `vgmstream-cli.js` in a web page, you will be able to call the `callMain()` function from the browser developer console. Parameters to vgmstream can be passed in an array: `callMain(["-i", "input_file.pcm"])`. Files can be accessed through Emscripten [File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html) (`FS`).
### Git (extras) ### Git (extras)
Code version control for development. Optional, used to auto-generate version numbers: Code version control for development. Optional, used to auto-generate version numbers:
- https://git-scm.com/download - https://git-scm.com/download
Remember Git can only be used if you clone the vgmstream repo (not with `.zip` sources). Remember Git can only be used if you clone the vgmstream repo (not with source downloaded in `.zip`).
On **Windows**, Git also comes with typical Linux utils (in the usr\bin dir), that can help when compiling some extra components. On **Windows**, Git also comes with typical Linux utils (in the usr\bin dir), that can help when compiling some extra components.
### Extra libs (extras) ### Extra libs (extras)
Optional codec. See *External libraries* for full info. Optional codecs. See *External libraries* for full info.
On **Windows** most libs are pre-compiled and included to simplify building (since they can be quite involved to compile). On **Windows** most libs are pre-compiled and included to simplify building (since they can be quite involved to compile).
@ -226,10 +261,10 @@ Simplest way is using the *./Makefile* in the root folder, see inside for option
Also, on **Linux** you can't build *in_vgmstream* and *xmp-vgmstream* (given they are Windows DLLs...). Makefiles have been used in the past to cross-compile from Linux with MingW headers though, but can't generate native Win code at the moment (should be fixable with some effort). Also, on **Linux** you can't build *in_vgmstream* and *xmp-vgmstream* (given they are Windows DLLs...). Makefiles have been used in the past to cross-compile from Linux with MingW headers though, but can't generate native Win code at the moment (should be fixable with some effort).
*Autotools* should build and install it as `vgmstream-cli`, this is explained in detail in the Audacious section. It enables (some) extra codecs. Some Linux distributions like **Arch Linux** include pre-patched vgmstream with most libraries, you may want that instead: *Autotools* should build and install it as `vgmstream-cli`, this is explained in detail in the Audacious section. It enables (some) extra codecs. Some Linux distributions like **Arch Linux** include pre-patched vgmstream with most libraries, you may want that instead (not part of this project):
- https://aur.archlinux.org/packages/vgmstream-git/ - https://aur.archlinux.org/packages/vgmstream-git/
If you use **macOS or Linux**, there is a *Homebrew* script that may automate the process (uses CMake): If you use **macOS or Linux**, there is a *Homebrew* script that may automate the process (uses CMake, also not part of this project):
- https://formulae.brew.sh/formula/vgmstream - https://formulae.brew.sh/formula/vgmstream
You may try CMake instead as it may be simpler and handle libs better. See the build steps in the [Cmake section](#cmake-builds). Some older distros may not work though (CMake version needs to recognize FILTER command). You may also need to install resulting artifacts manually. Check the *CMAKE.md* doc for some extra info too. You may try CMake instead as it may be simpler and handle libs better. See the build steps in the [Cmake section](#cmake-builds). Some older distros may not work though (CMake version needs to recognize FILTER command). You may also need to install resulting artifacts manually. Check the *CMAKE.md* doc for some extra info too.

View File

@ -1,4 +1,4 @@
# CMake Build Instructions # CMake build help
## Build requirements ## Build requirements

View File

@ -1,4 +1,4 @@
# TXTH FORMAT # TXTH format
TXTH is a simple text file with text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio. TXTH is a simple text file with text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio.
@ -28,7 +28,7 @@ Also check the [examples](#examples) section for some quick recipes, of varying
## Issues ## Issues
The `.txth` may be rejected if incorrect commands are found. Errors are shown in the console log (see *USAGE* guide), better try starting with a simple case (see examples) then add more complex commands until it fully works. The `.txth` may be rejected if incorrect commands are found. Errors are shown in the console log (see *USAGE* guide), better try starting with a simple case from examples then add more complex commands until it fully works.
Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from an accepted list in `formats.c`), or one could rename to any supported extension (like `.vgmstream`), or leave the file extensionless. Before renaming consider reporting the unknown extension so it can be added to the list (so similar games benefit, as long as the extension is a good fit). Some plugins allow playing unknown extensions too. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from an accepted list in `formats.c`), or one could rename to any supported extension (like `.vgmstream`), or leave the file extensionless. Before renaming consider reporting the unknown extension so it can be added to the list (so similar games benefit, as long as the extension is a good fit). Some plugins allow playing unknown extensions too.
@ -74,7 +74,7 @@ as explained below, but often will use default values. Accepted codec strings:
# * For many XBOX games, and some PC games # * For many XBOX games, and some PC games
# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo) # * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo)
# - DSP|NGC_DSP Nintendo GameCube ADPCM # - DSP|NGC_DSP Nintendo GameCube ADPCM
# * For many GC/Wii/3DS games # * For many GC/Wii/3DS/Switch games
# * Interleave is multiple of 0x08 (default), often +0x1000 # * Interleave is multiple of 0x08 (default), often +0x1000
# * Must set decoding coefficients (coef_offset/spacing/etc) # * Must set decoding coefficients (coef_offset/spacing/etc)
# * Should set ADPCM state (hist_offset/spacing/etc) # * Should set ADPCM state (hist_offset/spacing/etc)
@ -84,7 +84,7 @@ as explained below, but often will use default values. Accepted codec strings:
# * For many games (usually on PC) # * For many games (usually on PC)
# * Interleave is multiple of 0x2 (default) # * Interleave is multiple of 0x2 (default)
# - PCM16BE PCM 16-bit big endian # - PCM16BE PCM 16-bit big endian
# * Variation for certain consoles (GC/Wii/PS3/X360/etc) # * Variation for certain consoles (GC/Wii/PS3/X360)
# - PCM8 PCM 8-bit signed # - PCM8 PCM 8-bit signed
# * For some games (usually on PC) # * For some games (usually on PC)
# * Interleave is multiple of 0x1 (default) # * Interleave is multiple of 0x1 (default)
@ -116,14 +116,14 @@ as explained below, but often will use default values. Accepted codec strings:
# - ATRAC3 Sony ATRAC3 # - ATRAC3 Sony ATRAC3
# * For some PS2 and PS3 games # * For some PS2 and PS3 games
# * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required] # * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required]
# * Should set skip_samples (more than 1024+69 but varies) # * Should set skip_samples (around 1024+69 but varies)
# - ATRAC3PLUS Sony ATRAC3plus # - ATRAC3PLUS Sony ATRAC3plus
# * For many PSP games and rare PS3 games # * For many PSP games and rare PS3 games
# * Interleave (frame size) can be: [required] # * Interleave (frame size) can be: [required]
# Mono: 0x0118|0178|0230|02E8 # Mono: 0x0118|0178|0230|02E8
# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800 # Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800
# 6/8 channels: multiple of one of the above # 6/8 channels: multiple of one of the above
# * Should set skip_samples (more than 2048+184 but varies) # * Should set skip_samples (around 2048+184 but varies)
# - XMA1 Microsoft XMA1 # - XMA1 Microsoft XMA1
# * For early X360 games # * For early X360 games
# - XMA2 Microsoft XMA2 # - XMA2 Microsoft XMA2
@ -143,18 +143,20 @@ as explained below, but often will use default values. Accepted codec strings:
# - PCM4_U PCM 4-bit unsigned # - PCM4_U PCM 4-bit unsigned
# * Variation with modified encoding # * Variation with modified encoding
# - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit) # - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit)
# * For rare PS2 games (Sweet Legacy, Hooligan) # * For rare PS2 games [Sweet Legacy (PS2), Hooligan (PS2)]
# - OKI4S OKI ADPCM with 16-bit output and adjusted tables # - OKI4S OKI ADPCM with 16-bit output and adjusted tables
# * For later Konami rhythm games # * For later Konami rhythm games
# - AAC Advanced Audio Coding (raw without .mp4) # - AAC Advanced Audio Coding (raw outside .mp4)
# * For some 3DS games and many iOS games # * For some 3DS games and many iOS games
# * Should set skip_samples (typically 1024 but varies, 2112 is also common) # * Should set skip_samples (typically 1024 but varies, 2112 is also common)
# - TGC Tiger Game.com 4-bit ADPCM # - TGC Tiger Game.com 4-bit ADPCM
# * For Tiger Game.com # * For Tiger Game.com games
# - ASF Argonaut ASF ADPCM # - ASF Argonaut ASF ADPCM
# * For rare Argonaut games [Croc (SAT)] # * For rare Argonaut games [Croc (SAT)]
# - EAXA Electronic Arts EA-XA ADPCM # - EAXA Electronic Arts EA-XA ADPCM
# * For rare EA games [Harry Potter and the Chamber of Secrets (PC)] # * For rare EA games [Harry Potter and the Chamber of Secrets (PC)]
# - XA CD-XA ADPCM (ISO 2048 mode1/data streams without subchannels)
# * For rare Saturn and PS2 games [Phantasy Star Collection (SAT), Fantavision (PS2), EA SAT videos]
codec = (codec string) codec = (codec string)
``` ```
@ -408,28 +410,42 @@ subfile_extension = (string)
``` ```
#### CHUNK DEINTERLEAVING #### CHUNK DEINTERLEAVING
Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. These settings allow vgmstream to play one of the chunks while ignoring the rest (read 0x10000 data, skip 0x10000*2). Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. Or maybe 0x100 of useless header + 0x10000 of valid data. Chunk settings allow vgmstream to play valid chunks while ignoring the rest (read 0x10000 data, skip rest).
File is first "dechunked" then played with using other settings (`start_offset` would point within the internal dechunked" file). It can be used to remove garbage data that affects decoding, too.
File is first "dechunked" before being played, so other settings work over this final file (`start_offset` would be a point within the internal dechunked" file). Use combinations of chunk settings to make vgmstream "see" only actual codec data.
You need to set: Main settings:
- `chunk_count`: total number of interleaved chunks (ex. 3=3 interleaved songs) - `chunk_count`: total number of interleaved chunks (ex. 3=3 interleaved songs)
- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...)
* If you set `subsong_count` and `chunk_count` first, `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc)
- `chunk_start`: absolute offset where chunks start (normally 0x00) - `chunk_start`: absolute offset where chunks start (normally 0x00)
- `chunk_size`: amount of data in a single chunk (ex. 0x10000) - `chunk_size`: amount of data in a single chunk (ex. 0x10000)
For fine-tuning you can optionally set (before `chunk_size`, for reasons):
- `chunk_header_size`: header to skip before chunk data (part of chunk_size)
- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding)
So, if you set size to 0x1000, header_size 0x100, data_size is implicitly 0xF00, or if size is 0x1000 and data_size 0x800 last 0x200 is ignored padding. Use combinations of the above to make vgmstream "see" only actual codec data. Optional settings (set before main):
- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...)
- If you set `subsong_count` and `chunk_count` first, `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc)
- `chunk_header_size`: header to skip before chunk data (part of chunk_size)
- If size is 0x1000 and header_size 0x100, data_size is implicitly set to 0xF00
- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding)
- If size is 0x1000 and data_size 0x800 last 0x200 is ignored padding.
Dynamic settings (set before main, requires `chunk_header_size`):
- `chunk_value`: ignores chunks that don't match this value at chunk offset 0x00 (32-bit, in `chunk_endianness`)
- `chunk_size_offset`: reads chunk size at this offset, in header (32-bit in `chunk_endianness`).
- `chunk_endianness`: sets endianness of the above values
For technical reasons, "dechunking" activates when setting all main settings, so set optional config first. Note that config is static (not per-chunk), so `chunk_size = @0x10` is read from the beginning of the file once, not every time a new chunk is found.
``` ```
chunk_count = (value) chunk_count = (value)
chunk_number = (value)
chunk_start = (value) chunk_start = (value)
chunk_size = (value)
chunk_number = (value)
chunk_header_size = (value) chunk_header_size = (value)
chunk_data_size = (value) chunk_data_size = (value)
chunk_size = (value)
chunk_value = (value)
chunk_size_offset = (value)
chunk_endian = LE|BE
``` ```
#### NAME TABLE #### NAME TABLE

View File

@ -1,4 +1,4 @@
# TXTP FORMAT # TXTP format
TXTP is a text file with commands, to improve support for games using audio in certain uncommon or undesirable ways. It's in the form of a mini-playlist or a wrapper with play settings, meant to do post-processing over playable files. TXTP is a text file with commands, to improve support for games using audio in certain uncommon or undesirable ways. It's in the form of a mini-playlist or a wrapper with play settings, meant to do post-processing over playable files.

View File

@ -103,8 +103,8 @@ Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream extensio
you need to manually fix it by going to **options > plugins > input > vgmstream** you need to manually fix it by going to **options > plugins > input > vgmstream**
and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,lwav,msf,p3d,rak,scd,txth,xvag` and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,lwav,msf,p3d,rak,scd,txth,xvag`
XMPlay cannot support subsongs due to player limitations (with any plugin), try XMPlay cannot support vgmstream's type of mixed subsongs due to player limitations
using *TXTP* instead (explained below). (with neither *xmp-vgmstream* nor *in_vgmstream* plugins), try using *TXTP* instead (explained below).
### Audacious plugin ### Audacious plugin
@ -148,12 +148,16 @@ handling.
### Subsongs ### Subsongs
Certain container formats have multiple audio files, usually called "subsongs", often Certain container formats have multiple audio files, usually called "subsongs", often
not meant to be extracted (no simple separation). not meant to be extracted (no simple separation from container).
With CLI tools, you can select a subsong using the `-s` flag followed by a number, for example: By default vgmstream plays first subsong and reports total subsongs, if the format
`text.exe -s 5 file.bank`. is able to contain them. Easiest to use would be the *foobar/winamp/Audacious*
plugins, that are able to "unpack" those subsongs automatically into the playlist.
For files containing multiple subsongs, you can write them all using some flags. With CLI tools, you can select a subsong using the `-s` flag followed by a number,
for example: `text.exe -s 5 file.bank` or `vgmstream123 -s 5 file.bank`.
Using *vgmstream-cli* you can convert multiple subsongs at once using the `-S` flag.
**WARNING, MAY TAKE A LOT OF SPACE!** Some files have been observed to contain +20000 **WARNING, MAY TAKE A LOT OF SPACE!** Some files have been observed to contain +20000
subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong
wildcards (or leave it alone for the defaults). wildcards (or leave it alone for the defaults).
@ -163,9 +167,9 @@ wildcards (or leave it alone for the defaults).
- `test.exe -s 1 -S 5 -o bgm.wav file.bank`: writes 5 subsongs, but all overwrite the same file = wrong. - `test.exe -s 1 -S 5 -o bgm.wav file.bank`: writes 5 subsongs, but all overwrite the same file = wrong.
- `test.exe -s 1 -S 5 -o bgm_?02s.wav file.bank`: writes 5 subsongs, each named differently = correct. - `test.exe -s 1 -S 5 -o bgm_?02s.wav file.bank`: writes 5 subsongs, each named differently = correct.
Some plugins are able to "unpack" those files automatically into the playlist. For others without For other players without support, or to play only a few choice subsongs, you
support, you can create multiple .txtp (explained below) to select one of the subsongs (like can create multiple `.txtp` (explained later) to select one, like `bgm.sxd#10.txtp`
`bgm.sxd#10.txtp`). (plays subsong 10 in `bgm.sxd`).
You can use this python script to autogenerate one `.txtp` per subsong: You can use this python script to autogenerate one `.txtp` per subsong:
https://github.com/vgmstream/vgmstream/tree/master/cli/tools/txtp_maker.py https://github.com/vgmstream/vgmstream/tree/master/cli/tools/txtp_maker.py

View File

@ -2,10 +2,16 @@
# example script that builds vgmstream with most libs enabled using CMake + make # example script that builds vgmstream with most libs enabled using CMake + make
sudo apt-get update sudo apt-get -y update
# base deps
sudo apt-get install gcc g++ make build-essential git cmake sudo apt-get install gcc g++ make build-essential git cmake
sudo apt-get install libao-dev audacious-dev libjansson-dev # optional: for extra formats (can be ommited to build with static libs)
sudo apt-get install libvorbis-dev libmpg123-dev libspeex-dev libavformat-dev libavcodec-dev libavutil-dev libswresample-dev sudo apt-get install libmpg123-dev libvorbis-dev libspeex-dev
sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
sudo apt-get install yasm libopus-dev
# optional: for vgmstream 123 and audacious
sudo apt-get install -y libao-dev audacious-dev
#sudo apt-get install libjansson-dev
mkdir -p build mkdir -p build
cd build cd build

1
msvc-build-package.bat Normal file
View File

@ -0,0 +1 @@
powershell -ExecutionPolicy Bypass -NoProfile -File .\msvc-build.ps1 Package

View File

@ -162,73 +162,57 @@ static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, S
static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
STREAMFILE* sf_setup = NULL; STREAMFILE* sf_setup = NULL;
/* get from artificial external file (used if compiled without codebooks) */
{ {
char setupname[PATH_LIMIT]; char setupname[0x20];
char pathname[PATH_LIMIT];
char *path;
/* read "(dir/).fvs_{setup_id}" */ snprintf(setupname, sizeof(setupname), ".fvs_%08x", setup_id);
sf->get_name(sf,pathname,sizeof(pathname)); sf_setup = open_streamfile_by_filename(sf, setupname);
path = strrchr(pathname,DIR_SEPARATOR);
if (path)
*(path+1) = '\0';
else
pathname[0] = '\0';
snprintf(setupname,PATH_LIMIT,"%s.fvs_%08x", pathname, setup_id);
sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
} }
/* get codebook and copy to buffer */
if (sf_setup) { if (sf_setup) {
/* file found, get contents into the buffer */
size_t bytes = sf_setup->get_size(sf_setup); size_t bytes = sf_setup->get_size(sf_setup);
if (bytes > bufsize) goto fail; if (bytes > bufsize) goto fail;
if (read_streamfile(buf, 0, bytes, sf_setup) != bytes) if (read_streamfile(buf, 0, bytes, sf_setup) != bytes)
goto fail; goto fail;
sf_setup->close(sf_setup); close_streamfile(sf_setup);
return bytes; return bytes;
} }
fail: fail:
if (sf_setup) sf_setup->close(sf_setup); close_streamfile(sf_setup);
return 0; return 0;
} }
static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
STREAMFILE* sf_setup = NULL; STREAMFILE* sf_setup = NULL;
/* from to get from artificial external file (used if compiled without codebooks) */
{ {
char setupname[PATH_LIMIT]; char setupname[0x20];
char pathname[PATH_LIMIT];
char* path;
/* read "(dir/).fvs" */ snprintf(setupname, sizeof(setupname), ".fvs");
sf->get_name(sf,pathname,sizeof(pathname)); sf_setup = open_streamfile_by_filename(sf, setupname);
path = strrchr(pathname,DIR_SEPARATOR);
if (path)
*(path+1) = '\0';
else
pathname[0] = '\0';
snprintf(setupname,PATH_LIMIT,"%s.fvs", pathname);
sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
} }
/* find codebook in mini-header (format by bnnm, feel free to change) */
if (sf_setup) { if (sf_setup) {
/* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */
int entries, i; int entries, i;
uint32_t offset = 0, size = 0; uint32_t offset = 0, size = 0;
if (read_32bitBE(0x0, sf_setup) != 0x56465653) goto fail; /* "VFVS" */ if (!is_id32be(0x00, sf_setup, "VFVS"))
entries = read_32bitLE(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */ goto fail;
entries = read_u32le(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */
if (entries <= 0) goto fail; if (entries <= 0) goto fail;
for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */ for (i = 0; i < entries; i++) { /* entry = id, offset, size, reserved */
if ((uint32_t)read_32bitLE(0x20 + i*0x10, sf_setup) == setup_id) { if (read_u32le(0x20 + i*0x10, sf_setup) == setup_id) {
offset = read_32bitLE(0x24 + i*0x10, sf_setup); offset = read_u32le(0x24 + i*0x10, sf_setup);
size = read_32bitLE(0x28 + i*0x10, sf_setup); size = read_u32le(0x28 + i*0x10, sf_setup);
break; break;
} }
} }

View File

@ -1144,27 +1144,18 @@ static int load_wvc(uint8_t* ibuf, size_t ibufsize, uint32_t codebook_id, wwise_
} }
static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf) { static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf) {
STREAMFILE* sfWvc = NULL; STREAMFILE* sf_setup = NULL;
size_t wvc_size = 0; size_t wvc_size = 0;
/* get from artificial external file (used if compiled without codebooks) */
{ {
char setupname[PATH_LIMIT]; char setupname[0x20];
char pathname[PATH_LIMIT];
char *path;
/* read "(dir/).wvc" */ snprintf(setupname, sizeof(setupname), ".wvc");
sf->get_name(sf,pathname,sizeof(pathname)); sf_setup = open_streamfile_by_filename(sf, setupname);
path = strrchr(pathname,DIR_SEPARATOR); if (!sf_setup) goto fail;
if (path)
*(path+1) = '\0';
else
pathname[0] = '\0';
snprintf(setupname,PATH_LIMIT,"%s.wvc", pathname); wvc_size = get_streamfile_size(sf_setup);
sfWvc = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!sfWvc) goto fail;
wvc_size = sfWvc->get_size(sfWvc);
} }
/* find codebook and copy to buffer */ /* find codebook and copy to buffer */
@ -1174,24 +1165,24 @@ static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STR
int codebook_count; int codebook_count;
/* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */ /* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */
table_start = read_u32le(wvc_size - 4, sfWvc); /* last offset */ table_start = read_u32le(wvc_size - 4, sf_setup); /* last offset */
codebook_count = ((wvc_size - table_start) / 4) - 1; codebook_count = ((wvc_size - table_start) / 4) - 1;
if (table_start > wvc_size || codebook_id >= codebook_count) goto fail; if (table_start > wvc_size || codebook_id >= codebook_count) goto fail;
codebook_offset = read_u32le(table_start + codebook_id*4, sfWvc); codebook_offset = read_u32le(table_start + codebook_id*4, sf_setup);
codebook_size = read_u32le(table_start + codebook_id*4 + 4, sfWvc) - codebook_offset; codebook_size = read_u32le(table_start + codebook_id*4 + 4, sf_setup) - codebook_offset;
if (codebook_size > bufsize) goto fail; if (codebook_size > bufsize) goto fail;
if (read_streamfile(buf, codebook_offset, codebook_size, sfWvc) != codebook_size) if (read_streamfile(buf, codebook_offset, codebook_size, sf_setup) != codebook_size)
goto fail; goto fail;
sfWvc->close(sfWvc);
close_streamfile(sf_setup);
return codebook_size; return codebook_size;
} }
fail: fail:
if (sfWvc) sfWvc->close(sfWvc); close_streamfile(sf_setup);
return 0; return 0;
} }

View File

@ -6,13 +6,20 @@
// May be implemented like the SNES/SPC700 BRR. // May be implemented like the SNES/SPC700 BRR.
/* XA ADPCM gain values */ /* XA ADPCM gain values */
#if 0 //#define XA_FLOAT 1
//#define XA_INTB 1
#if XA_FLOAT
static const float K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 }; static const float K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const float K1[4] = { 0.0, 0.0, -0.8125, -0.859375 }; static const float K1[4] = { 0.0, 0.0, -0.8125, -0.859375 };
#endif #elif XA_INTB
/* K0/1 floats to int, -K*2^10 = -K*(1<<10) = -K*1024 */ /* K0/1 floats to int, -K*2^10 = -K*(1<<10) = -K*1024 */
static const int IK0[4] = { 0, -960, -1840, -1568 }; static const int K0[4] = { 0, 60, 115, 98 };
static const int IK1[4] = { 0, 0, 832, 880 }; static const int K1[4] = { 0, 0, -52, -55 };
#else
/* K0/1 floats to int, -K*2^10 = -K*(1<<10) = -K*1024 */
static const int K0[4] = { 0, -960, -1840, -1568 };
static const int K1[4] = { 0, 0, 832, 880 };
#endif
/* Sony XA ADPCM, defined for CD-DA/CD-i in the "Red Book" (private) or "Green Book" (public) specs. /* Sony XA ADPCM, defined for CD-DA/CD-i in the "Red Book" (private) or "Green Book" (public) specs.
* The algorithm basically is BRR (Bit Rate Reduction) from the SNES SPC700, while the data layout is new. * The algorithm basically is BRR (Bit Rate Reduction) from the SNES SPC700, while the data layout is new.
@ -74,6 +81,9 @@ static const int IK1[4] = { 0, 0, 832, 880 };
* subframe 1: header @ 0x01 or 0x05/09/0d, 28 bytes @ 0x11,16,19,1d,21 ... 7d * subframe 1: header @ 0x01 or 0x05/09/0d, 28 bytes @ 0x11,16,19,1d,21 ... 7d
* ... * ...
* subframe 3: header @ 0x03 or 0x07/0b/0f, 28 bytes @ 0x13,17,1b,1f,23 ... 7f * subframe 3: header @ 0x03 or 0x07/0b/0f, 28 bytes @ 0x13,17,1b,1f,23 ... 7f
*
* Raw XA found in EA SAT games set subframes header like: 0..3 null + 0..3 ok + 4..7 ok + 4..7 null
* (maybe first/last are CD-error correction only?) so decoder only reads those.
*/ */
void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_xa8) { void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_xa8) {
@ -96,30 +106,34 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i
frame_offset = stream->offset + bytes_per_frame * frames_in; frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
VGM_ASSERT(get_u32be(frame+0x0) != get_u32be(frame+0x4) || get_u32be(frame+0x8) != get_u32be(frame+0xC), VGM_ASSERT_ONCE(get_u32be(frame+0x0) != get_u32be(frame+0x4) || get_u32be(frame+0x8) != get_u32be(frame+0xC),
"bad frames at %x\n", (uint32_t)frame_offset); "bad frames at %x\n", (uint32_t)frame_offset);
/* decode subframes */ /* decode subframes */
for (i = 0; i < subframes / channelspacing; i++) { for (i = 0; i < subframes / channelspacing; i++) {
#if XA_FLOAT
float coef1, coef2;
#else
int32_t coef1, coef2; int32_t coef1, coef2;
#endif
uint8_t coef_index, shift_factor; uint8_t coef_index, shift_factor;
/* parse current subframe (sound unit)'s header (sound parameters) */ /* parse current subframe (sound unit)'s header (sound parameters) */
sp_pos = is_xa8 ? sp_pos = is_xa8 ?
i*channelspacing + channel: i*channelspacing + channel :
0x04 + i*channelspacing + channel; 0x04 + i*channelspacing + channel;
coef_index = (frame[sp_pos] >> 4) & 0xf; coef_index = (frame[sp_pos] >> 4) & 0xf;
shift_factor = (frame[sp_pos] >> 0) & 0xf; shift_factor = (frame[sp_pos] >> 0) & 0xf;
/* mastered values like 0xFF exist [Micro Machines (CDi), demo and release] */ /* mastered values like 0xFF exist [Micro Machines (CDi), demo and release] */
VGM_ASSERT(coef_index > 4 || shift_factor > (is_xa8 ? 8 : 12), "XA: incorrect coefs/shift at %x\n", (uint32_t)frame_offset + sp_pos); VGM_ASSERT_ONCE(coef_index > 4 || shift_factor > (is_xa8 ? 8 : 12), "XA: incorrect coefs/shift at %x\n", (uint32_t)frame_offset + sp_pos);
if (coef_index > 4) if (coef_index > 4)
coef_index = 0; /* only 4 filters are used, rest is apparently 0 */ coef_index = 0; /* only 4 filters are used, rest is apparently 0 */
if (shift_factor > (is_xa8 ? 8 : 12)) if (shift_factor > (is_xa8 ? 8 : 12))
shift_factor = (is_xa8 ? 8 : 9); /* supposedly, from Nocash PSX docs (in 8-bit mode max range should be 8 though) */ shift_factor = (is_xa8 ? 8 : 9); /* supposedly, from Nocash PSX docs (in 8-bit mode max range should be 8 though) */
coef1 = IK0[coef_index]; coef1 = K0[coef_index];
coef2 = IK1[coef_index]; coef2 = K1[coef_index];
/* decode subframe nibbles */ /* decode subframe nibbles */
@ -156,8 +170,14 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i
sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */
} }
sample = sample << 4; /* scale for current IK */ sample = sample << 4; /* scale for K */
sample = sample - ((coef1*hist1 + coef2*hist2) >> 10); #if XA_FLOAT
sample = sample + (coef1 * hist1 + coef2 * hist2);
#elif XA_INTB
sample = sample + ((coef1 * hist1 + coef2 * hist2) >> 6);
#else
sample = sample - ((coef1 * hist1 + coef2 * hist2) >> 10);
#endif
hist2 = hist1; hist2 = hist1;
hist1 = sample; /* must go before clamp, somehow */ hist1 = sample; /* must go before clamp, somehow */

View File

@ -567,6 +567,7 @@ static const char* extension_list[] = {
"vsv", "vsv",
"vxn", "vxn",
"w",
"waa", "waa",
"wac", "wac",
"wad", "wad",
@ -1118,7 +1119,8 @@ static const meta_info meta_info_list[] = {
{meta_XBOX_HLWAV, "Half-Life 2 .WAV header"}, {meta_XBOX_HLWAV, "Half-Life 2 .WAV header"},
{meta_MYSPD, "U-Sing .MYSPD header"}, {meta_MYSPD, "U-Sing .MYSPD header"},
{meta_HIS, "Her Interactive HIS header"}, {meta_HIS, "Her Interactive HIS header"},
{meta_PS2_AST, "KOEI AST header"}, {meta_AST_MV, "MicroVision AST header"},
{meta_AST_MMV, "Marvelous AST header"},
{meta_CAPDSP, "Capcom DSP header"}, {meta_CAPDSP, "Capcom DSP header"},
{meta_DMSG, "Microsoft RIFF DMSG header"}, {meta_DMSG, "Microsoft RIFF DMSG header"},
{meta_PONA_3DO, "Policenauts BGM header"}, {meta_PONA_3DO, "Policenauts BGM header"},
@ -1146,7 +1148,7 @@ static const meta_info meta_info_list[] = {
{meta_DSP_XIII, "XIII dsp header"}, {meta_DSP_XIII, "XIII dsp header"},
{meta_DSP_CABELAS, "Cabelas games .DSP header"}, {meta_DSP_CABELAS, "Cabelas games .DSP header"},
{meta_PS2_ADM, "Dragon Quest V .ADM raw header"}, {meta_PS2_ADM, "Dragon Quest V .ADM raw header"},
{meta_PS2_LPCM, "LPCM header"}, {meta_LPCM_SHADE, "Shade LPCM header"},
{meta_PS2_VMS, "VMS Header"}, {meta_PS2_VMS, "VMS Header"},
{meta_XAU, "XPEC XAU header"}, {meta_XAU, "XPEC XAU header"},
{meta_GH3_BAR, "Guitar Hero III Mobile .bar"}, {meta_GH3_BAR, "Guitar Hero III Mobile .bar"},

View File

@ -1,6 +1,7 @@
#include "layout.h" #include "layout.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../vgmstream.h" #include "../vgmstream.h"
#include "../util/endianness.h"
/* set up for the block at the given offset */ /* set up for the block at the given offset */
void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) { void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
@ -8,7 +9,7 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
int i; int i;
uint32_t block_id; uint32_t block_id;
size_t block_size = 0, block_header = 0, audio_size = 0; size_t block_size = 0, block_header = 0, audio_size = 0;
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE; read_s32_t read_s32 = vgmstream->codec_endian ? read_s32be : read_s32le;
/* EOF reads: signal we have nothing and let the layout fail */ /* EOF reads: signal we have nothing and let the layout fail */
@ -19,27 +20,29 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
return; return;
} }
block_id = read_32bitBE(block_offset + 0x00, sf); block_id = read_u32be(block_offset + 0x00, sf);
/* BE in SAT, but one file may have both BE and LE chunks [FIFA 98 (SAT): movie LE, audio BE] */ /* BE in SAT, but one file may have both BE and LE chunks [FIFA 98 (SAT): movie LE, audio BE] */
if (guess_endianness32bit(block_offset + 0x04, sf)) if (guess_endian32(block_offset + 0x04, sf))
block_size = read_32bitBE(block_offset + 0x04, sf); block_size = read_u32be(block_offset + 0x04, sf);
else else
block_size = read_32bitLE(block_offset + 0x04, sf); block_size = read_u32le(block_offset + 0x04, sf);
block_header = 0; block_header = 0;
if (block_id == 0x31534E68 || block_id == 0x53454144) { /* "1SNh" "SEAD" audio header */ if (block_id == get_id32be("1SNh") || block_id == get_id32be("SEAD")) { /* audio header */
int is_sead = (block_id == 0x53454144); int is_sead = (block_id == get_id32be("SEAD"));
int is_eacs = read_32bitBE(block_offset + 0x08, sf) == 0x45414353; int is_eacs = is_id32be(block_offset + 0x08, sf, "EACS");
int is_zero = read_32bitBE(block_offset + 0x08, sf) == 0x00; int is_zero = read_u32be(block_offset + 0x08, sf) == 0x00;
block_header = (is_eacs || is_zero) ? 0x28 : (is_sead ? 0x14 : 0x2c); block_header = (is_eacs || is_zero) ? 0x28 : (is_sead ? 0x14 : 0x2c);
if (block_header >= block_size) /* sometimes has audio data after header */ if (block_header >= block_size) /* sometimes has audio data after header */
block_header = 0; block_header = 0;
} else if (block_id == 0x31534E64 || block_id == 0x534E4443) { /* "1SNd" "SNDC" audio data */ }
else if (block_id == get_id32be("1SNd") || block_id == get_id32be("SNDC")) {
block_header = 0x08; block_header = 0x08;
} else if (block_id == 0x00000000 || block_id == 0xFFFFFFFF || block_id == 0x31534E65) { /* EOF or "1SNe" */ }
else if (block_id == 0x00000000 || block_id == 0xFFFFFFFF || block_id == get_id32be("1SNe")) { /* EOF */
vgmstream->current_block_samples = -1; vgmstream->current_block_samples = -1;
return; return;
} }
@ -72,20 +75,25 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
break; break;
case coding_PSX: case coding_PSX:
if (vgmstream->codec_config == 1) {/* extra field */
block_header += 0x04;
audio_size -= 0x04;
}
vgmstream->current_block_samples = ps_bytes_to_samples(audio_size, vgmstream->channels); vgmstream->current_block_samples = ps_bytes_to_samples(audio_size, vgmstream->channels);
for (i=0;i<vgmstream->channels;i++) { for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = block_offset + block_header + i*(audio_size/vgmstream->channels); vgmstream->ch[i].offset = block_offset + block_header + i*(audio_size/vgmstream->channels);
} }
break; break;
case coding_DVI_IMA: case coding_DVI_IMA:
if (vgmstream->codec_config == 1) { /* ADPCM hist */ if (vgmstream->codec_config == 1) { /* ADPCM hist */
vgmstream->current_block_samples = read_32bit(block_offset + block_header, sf); vgmstream->current_block_samples = read_s32(block_offset + block_header, sf);
for(i = 0; i < vgmstream->channels; i++) { for(i = 0; i < vgmstream->channels; i++) {
off_t adpcm_offset = block_offset + block_header + 0x04; off_t adpcm_offset = block_offset + block_header + 0x04;
vgmstream->ch[i].adpcm_step_index = read_32bit(adpcm_offset + i*0x04 + 0x00*vgmstream->channels, sf); vgmstream->ch[i].adpcm_step_index = read_s32(adpcm_offset + i*0x04 + 0x00*vgmstream->channels, sf);
vgmstream->ch[i].adpcm_history1_32 = read_32bit(adpcm_offset + i*0x04 + 0x04*vgmstream->channels, sf); vgmstream->ch[i].adpcm_history1_32 = read_s32(adpcm_offset + i*0x04 + 0x04*vgmstream->channels, sf);
vgmstream->ch[i].offset = adpcm_offset + 0x08*vgmstream->channels; vgmstream->ch[i].offset = adpcm_offset + 0x08*vgmstream->channels;
} }

View File

@ -449,7 +449,8 @@
<ClCompile Include="meta\ps2_adm.c" /> <ClCompile Include="meta\ps2_adm.c" />
<ClCompile Include="meta\ads.c" /> <ClCompile Include="meta\ads.c" />
<ClCompile Include="meta\ps2_ass.c" /> <ClCompile Include="meta\ps2_ass.c" />
<ClCompile Include="meta\ps2_ast.c" /> <ClCompile Include="meta\ast_mv.c" />
<ClCompile Include="meta\ast_mmv.c" />
<ClCompile Include="meta\aus.c" /> <ClCompile Include="meta\aus.c" />
<ClCompile Include="meta\ps2_b1s.c" /> <ClCompile Include="meta\ps2_b1s.c" />
<ClCompile Include="meta\ps2_bg00.c" /> <ClCompile Include="meta\ps2_bg00.c" />
@ -470,7 +471,7 @@
<ClCompile Include="meta\jstm.c" /> <ClCompile Include="meta\jstm.c" />
<ClCompile Include="meta\ps2_kces.c" /> <ClCompile Include="meta\ps2_kces.c" />
<ClCompile Include="meta\ps2_leg.c" /> <ClCompile Include="meta\ps2_leg.c" />
<ClCompile Include="meta\ps2_lpcm.c" /> <ClCompile Include="meta\lpcm_shade.c" />
<ClCompile Include="meta\ps2_mcg.c" /> <ClCompile Include="meta\ps2_mcg.c" />
<ClCompile Include="meta\ps_headerless.c" /> <ClCompile Include="meta\ps_headerless.c" />
<ClCompile Include="meta\ps2_mic.c" /> <ClCompile Include="meta\ps2_mic.c" />

View File

@ -829,7 +829,10 @@
<ClCompile Include="meta\ps2_ass.c"> <ClCompile Include="meta\ps2_ass.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ps2_ast.c"> <ClCompile Include="meta\ast_mv.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ast_mmv.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\aus.c"> <ClCompile Include="meta\aus.c">
@ -892,7 +895,7 @@
<ClCompile Include="meta\ps2_leg.c"> <ClCompile Include="meta\ps2_leg.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ps2_lpcm.c"> <ClCompile Include="meta\lpcm_shade.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ps2_mcg.c"> <ClCompile Include="meta\ps2_mcg.c">

View File

@ -80,14 +80,14 @@ VGMSTREAM* init_vgmstream_adx_subkey(STREAMFILE* sf, uint16_t subkey) {
/* encryption */ /* encryption */
if (version == 0x0408) { if (version == 0x0408) {
if (find_adx_key(sf, 8, &xor_start, &xor_mult, &xor_add, 0)) { if (!find_adx_key(sf, 8, &xor_start, &xor_mult, &xor_add, 0)) {
vgm_logi("ADX: decryption keystring not found\n"); vgm_logi("ADX: decryption keystring not found\n");
} }
coding_type = coding_CRI_ADX_enc_8; coding_type = coding_CRI_ADX_enc_8;
version = 0x0400; version = 0x0400;
} }
else if (version == 0x0409) { else if (version == 0x0409) {
if (find_adx_key(sf, 9, &xor_start, &xor_mult, &xor_add, subkey)) { if (!find_adx_key(sf, 9, &xor_start, &xor_mult, &xor_add, subkey)) {
vgm_logi("ADX: decryption keycode not found\n"); vgm_logi("ADX: decryption keycode not found\n");
} }
coding_type = coding_CRI_ADX_enc_9; coding_type = coding_CRI_ADX_enc_9;

55
src/meta/ast_mmv.c Normal file
View File

@ -0,0 +1,55 @@
#include "meta.h"
#include "../coding/coding.h"
/* AST - from Marvelous(?) games [Katekyou Hitman Reborn! Dream Hyper Battle! (PS2), Binchou-tan: Shiawasegoyomi (PS2)] */
VGMSTREAM* init_vgmstream_ast_mmv(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset, data_size;
int loop_flag, channels, sample_rate, interleave;
/* checks */
if (!is_id32be(0x00,sf, "AST\0"))
goto fail;
/* .ast: from executables (usually found in bigfiles) */
if (!check_extensions(sf,"ast"))
goto fail;
data_size = read_u32le(0x04, sf);
if (data_size != get_streamfile_size(sf))
goto fail;
sample_rate = read_s32le(0x08,sf);
channels = read_32bitLE(0x0C,sf);
interleave = read_u32le(0x10,sf);
/* 0x14: number of blocks */
/* 0x18: ? (not fully related to size/time) */
/* 0x1c: f32 time */
loop_flag = 0;
start_offset = 0x100;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_AST_MMV;
vgmstream->num_samples = ps_bytes_to_samples(data_size - start_offset, channels);
vgmstream->sample_rate = sample_rate;
vgmstream->interleave_block_size = interleave;
vgmstream->layout_type = layout_interleave;
vgmstream->coding_type = coding_PSX;
read_string(vgmstream->stream_name,0x20, 0x20,sf);
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

53
src/meta/ast_mv.c Normal file
View File

@ -0,0 +1,53 @@
#include "meta.h"
#include "../coding/coding.h"
/* AST - from MicroVision lib games [P.T.O. IV (PS2), Naval Ops: Warship Gunner (PS2)] */
VGMSTREAM* init_vgmstream_ast_mv(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset, data_size, check;
int loop_flag, channels, interleave, sample_rate;
/* checks */
if (!is_id32be(0x00,sf, "AST\0"))
goto fail;
if (!check_extensions(sf,"ast"))
goto fail;
channels = 2;
sample_rate = read_s32le(0x04, sf);
interleave = read_u32le(0x08,sf);
data_size = read_u32le(0x0c,sf); /* may have garbage */
check = read_u32be(0x10, sf);
/* rest: null/garbage */
loop_flag = 0;
start_offset = 0x800;
/* there is a variation in .ikm (Zwei), with loops and different start offset */
if (check != 0x20002000 && /* NO:WG (garbage up to start) */
check != 0x00000000) /* PTO4 */
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ps_bytes_to_samples(data_size - start_offset, channels);
vgmstream->interleave_block_size = interleave;
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->meta_type = meta_AST_MV;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -21,7 +21,7 @@ typedef struct _BARSTREAMFILE {
/*static*/ STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file); /*static*/ STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file);
static size_t read_bar(BARSTREAMFILE *streamFile, uint8_t *dest, off_t offset, size_t length) { static size_t read_bar(BARSTREAMFILE *streamFile, uint8_t *dest, offv_t offset, size_t length) {
off_t i; off_t i;
size_t read_length = streamFile->real_file->read(streamFile->real_file, dest, offset, length); size_t read_length = streamFile->real_file->read(streamFile->real_file, dest, offset, length);
@ -36,7 +36,7 @@ static size_t get_size_bar(BARSTREAMFILE *streamFile) {
return streamFile->real_file->get_size(streamFile->real_file); return streamFile->real_file->get_size(streamFile->real_file);
} }
static size_t get_offset_bar(BARSTREAMFILE *streamFile) { static offv_t get_offset_bar(BARSTREAMFILE *streamFile) {
return streamFile->real_file->get_offset(streamFile->real_file); return streamFile->real_file->get_offset(streamFile->real_file);
} }

View File

@ -1,6 +1,7 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../layout/layout.h" #include "../layout/layout.h"
#include "../util/endianness.h"
#define EA_CODEC_PCM 0x00 #define EA_CODEC_PCM 0x00
#define EA_CODEC_ULAW 0x01 #define EA_CODEC_ULAW 0x01
@ -17,7 +18,9 @@ typedef struct {
int32_t loop_start; int32_t loop_start;
int32_t loop_end; int32_t loop_end;
int32_t loop_start_offset; int32_t loop_start_offset;
int32_t data_offset; uint32_t data_offset;
uint32_t base_size;
int big_endian; int big_endian;
int loop_flag; int loop_flag;
@ -27,7 +30,7 @@ typedef struct {
int total_subsongs; int total_subsongs;
} eacs_header; } eacs_header;
static int parse_header(STREAMFILE* sf, eacs_header* ea, off_t begin_offset); static int parse_header(STREAMFILE* sf, eacs_header* ea, uint32_t begin_offset);
static VGMSTREAM* init_vgmstream_main(STREAMFILE* sf, eacs_header* ea); static VGMSTREAM* init_vgmstream_main(STREAMFILE* sf, eacs_header* ea);
static void set_ea_1snh_num_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, eacs_header* ea, int find_loop); static void set_ea_1snh_num_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, eacs_header* ea, int find_loop);
@ -35,24 +38,12 @@ static int get_ea_1snh_ima_version(STREAMFILE* sf, off_t start_offset, const eac
/* EA 1SNh - from early EA games, stream (~1996, ex. Need for Speed) */ /* EA 1SNh - from early EA games, stream (~1996, ex. Need for Speed) */
VGMSTREAM* init_vgmstream_ea_1snh(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_ea_1snh(STREAMFILE* sf) {
eacs_header ea = { 0 }; eacs_header ea = {0};
off_t offset, eacs_offset; off_t offset = 0x00, eacs_offset;
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
/* checks */ /* checks */
/* .asf/as4: common,
* .lasf: fake for plugins
* .sng: fake for plugins (for .asf issues)
* .cnk: some PS1 games
* .uv/tgq: some SAT videos
* .tgv: videos
* (extensionless): Need for Speed (SAT) videos */
if (!check_extensions(sf, "asf,lasf,sng,as4,cnk,uv,tgq,tgv,"))
goto fail;
offset = 0x00;
/* in TGV videos, either TGVk or 1SNh block comes first */ /* in TGV videos, either TGVk or 1SNh block comes first */
if (is_id32be(0x00, sf, "TGVk")) { if (is_id32be(0x00, sf, "TGVk")) {
offset = read_u32be(0x04, sf); offset = read_u32be(0x04, sf);
@ -64,14 +55,28 @@ VGMSTREAM* init_vgmstream_ea_1snh(STREAMFILE* sf) {
!is_id32be(offset + 0x00, sf, "SEAD")) !is_id32be(offset + 0x00, sf, "SEAD"))
goto fail; goto fail;
/* .asf/as4: common,
* .lasf: fake for plugins
* .sng: fake for plugins (for .asf issues)
* .cnk: some PS1 games [Triple Play 97 (PS1), FIFA 97 (PS1)]
* .uv/tgq: some SAT videos
* .tgv: videos
* (extensionless): Need for Speed (SAT) videos */
if (!check_extensions(sf, "asf,lasf,sng,as4,cnk,uv,tgq,tgv,"))
goto fail;
/* stream is divided into blocks/chunks: 1SNh=audio header, 1SNd=data xN, 1SNl=loop end, 1SNe=end. /* stream is divided into blocks/chunks: 1SNh=audio header, 1SNd=data xN, 1SNl=loop end, 1SNe=end.
* Video uses various blocks (TGVk/TGVf/MUVf/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */ * Video uses various blocks (TGVk/TGVf/MUVf/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */
ea.is_sead = is_id32be(offset + 0x00, sf, "SEAD"); ea.is_sead = is_id32be(offset + 0x00, sf, "SEAD");
if (!ea.is_sead)
ea.base_size = read_u32le(offset + 0x04, sf);
eacs_offset = offset + 0x08; /* after 1SNh block id+size */ eacs_offset = offset + 0x08; /* after 1SNh block id+size */
if (!parse_header(sf, &ea, eacs_offset)) if (!parse_header(sf, &ea, eacs_offset))
goto fail; goto fail;
vgmstream = init_vgmstream_main(sf, &ea); vgmstream = init_vgmstream_main(sf, &ea);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
@ -98,17 +103,19 @@ VGMSTREAM* init_vgmstream_ea_eacs(STREAMFILE* sf) {
/* checks */ /* checks */
if (!is_id32be(0x00,sf, "EACS") &&
read_u32be(0x00,sf) != 0 && !is_id32be(0x228,sf, "EACS"))
goto fail;
/* .eas: single bank [Need for Speed (PC)] /* .eas: single bank [Need for Speed (PC)]
* .bnk: multi bank [Need for Speed (PC)] */ * .bnk: multi bank [Need for Speed (PC)]
if (!check_extensions(sf,"eas,bnk")) * .as4: single [NBA Live 96 (PC)] */
if (!check_extensions(sf,"eas,bnk,as4"))
goto fail; goto fail;
/* plain data without blocks, can contain N*(EACS header) + N*(data), or N (EACS header + data) */ /* plain data without blocks, can contain N*(EACS header) + N*(data), or N (EACS header + data) */
ea.is_bank = 1; ea.is_bank = 1;
/* use ??? as endian marker (Saturn = BE) */
//ea.big_endian = guess_endianness32bit(0x04,sf);
if (is_id32be(0x00,sf, "EACS")) { if (is_id32be(0x00,sf, "EACS")) {
/* single bank variant */ /* single bank variant */
eacs_offset = 0x00; eacs_offset = 0x00;
@ -121,8 +128,8 @@ VGMSTREAM* init_vgmstream_ea_eacs(STREAMFILE* sf) {
if (target_subsong == 0) target_subsong = 1; if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0) goto fail; if (target_subsong < 0) goto fail;
/* offsets to EACSs are scattered in the first 0x200 /* offsets to EACSs are scattered in the first 0x200, then 0x28 info + EACS per subsong.
* this looks dumb but seems like the only way */ * This looks dumb but seems like the only way. */
eacs_offset = 0; eacs_offset = 0;
for (i = 0x00; i < 0x200; i += 0x04) { for (i = 0x00; i < 0x200; i += 0x04) {
off_t bank_offset = read_u32le(i, sf); off_t bank_offset = read_u32le(i, sf);
@ -134,7 +141,7 @@ VGMSTREAM* init_vgmstream_ea_eacs(STREAMFILE* sf) {
/* parse mini bank header */ /* parse mini bank header */
if (ea.total_subsongs == target_subsong) { if (ea.total_subsongs == target_subsong) {
/* 0x00: some id or flags? */ /* 0x00: some id or flags? */
eacs_offset = read_u32le(bank_offset + 0x04, sf); eacs_offset = read_u32le(bank_offset + 0x04, sf); /* always after 0x28 from bank_offset */
if (!is_id32be(eacs_offset, sf, "EACS")) if (!is_id32be(eacs_offset, sf, "EACS"))
goto fail; goto fail;
/* rest: not sure if part of this header */ /* rest: not sure if part of this header */
@ -193,10 +200,11 @@ static VGMSTREAM* init_vgmstream_main(STREAMFILE* sf, eacs_header* ea) {
case EA_CODEC_PSX: /* Need for Speed (PS1) */ case EA_CODEC_PSX: /* Need for Speed (PS1) */
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
vgmstream->codec_config = ea->codec_config;
break; break;
default: default:
VGM_LOG("EA EACS: unknown codec 0x%02x\n", ea->codec); vgm_logi("EA EACS: unknown codec 0x%02x\n", ea->codec);
goto fail; goto fail;
} }
@ -210,13 +218,13 @@ fail:
return NULL; return NULL;
} }
static int parse_header(STREAMFILE* sf, eacs_header* ea, off_t offset) { static int parse_header(STREAMFILE* sf, eacs_header* ea, uint32_t offset) {
/* audio header endianness doesn't always match block headers, use sample rate to detect */ /* audio header endianness doesn't always match block headers, use sample rate to detect */
int32_t (*read_s32)(off_t,STREAMFILE*); read_s32_t read_s32;
if (is_id32be(offset+0x00,sf, "EACS")) { if (is_id32be(offset+0x00,sf, "EACS")) {
/* EACS subheader (PC, SAT) */ /* EACS subheader (PC, SAT) */
ea->big_endian = guess_endianness32bit(offset + 0x04, sf); ea->big_endian = guess_endian32(offset + 0x04, sf);
read_s32 = ea->big_endian ? read_s32be : read_s32le; read_s32 = ea->big_endian ? read_s32be : read_s32le;
ea->sample_rate = read_s32(offset+0x04, sf); ea->sample_rate = read_s32(offset+0x04, sf);
@ -236,9 +244,37 @@ static int parse_header(STREAMFILE* sf, eacs_header* ea, off_t offset) {
ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea); ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea);
/* EACS banks with empty values exist but will be rejected later */ /* EACS banks with empty values exist but will be rejected later */
} }
else if (read_u32be(offset + 0x00, sf) == 0x00) { else if (ea->is_sead) {
/* alt subheader (found in some PC videos) */
ea->big_endian = guess_endian32(offset + 0x00, sf);
read_s32 = ea->big_endian ? read_s32be : read_s32le;
ea->sample_rate = read_s32(offset+0x00, sf);
ea->channels = read_s32(offset+0x04, sf);
ea->codec = read_s32(offset+0x08, sf);
if (ea->codec == EA_CODEC_IMA)
ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea);
}
else if (ea->base_size == 0x2c) {
/* [NBA Live 96 (PS1), Need for Speed (PS1)] */
ea->sample_rate = read_s32le(offset+0x00, sf);
ea->channels = read_u8(offset+0x18, sf);
ea->codec = EA_CODEC_PSX;
ea->codec_config = 0;
}
else if (ea->base_size == 0x30) {
/* [FIFA 97 (PS1), Triple Play 97 (PS1)] */
/* 0x00: 0 or some id? (same for N files) */
ea->sample_rate = read_s32le(offset+0x04, sf);
ea->channels = read_u8(offset+0x1c, sf);
ea->codec = EA_CODEC_PSX;
ea->codec_config = 1;
}
else {
//TODO: test
/* found in early videos, similar to EACS */ /* found in early videos, similar to EACS */
ea->big_endian = guess_endianness32bit(offset + 0x04, sf); ea->big_endian = guess_endian32(offset + 0x04, sf);
read_s32 = ea->big_endian ? read_s32be : read_s32le; read_s32 = ea->big_endian ? read_s32be : read_s32le;
ea->sample_rate = read_s32(offset + 0x04, sf); ea->sample_rate = read_s32(offset + 0x04, sf);
@ -250,27 +286,6 @@ static int parse_header(STREAMFILE* sf, eacs_header* ea, off_t offset) {
if (ea->codec == EA_CODEC_IMA) if (ea->codec == EA_CODEC_IMA)
ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea); ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea);
} }
else if (ea->is_sead) {
/* alt subheader (found in some PC videos) */
ea->big_endian = guess_endianness32bit(offset + 0x00, sf);
read_s32 = ea->big_endian ? read_s32be : read_s32le;
ea->sample_rate = read_s32(offset+0x00, sf);
ea->channels = read_s32(offset+0x04, sf);
ea->codec = read_s32(offset+0x08, sf);
if (ea->codec == EA_CODEC_IMA)
ea->codec_config = get_ea_1snh_ima_version(sf, 0x00, ea);
}
else {
/* alt subheader (PS1) */
ea->big_endian = guess_endianness32bit(offset + 0x00, sf);
read_s32 = ea->big_endian ? read_s32be : read_s32le;
ea->sample_rate = read_s32(offset+0x00, sf);
ea->channels = read_u8(offset+0x18, sf);
ea->codec = EA_CODEC_PSX;
}
ea->loop_flag = (ea->loop_end > 0); ea->loop_flag = (ea->loop_end > 0);
@ -281,26 +296,28 @@ static int parse_header(STREAMFILE* sf, eacs_header* ea, off_t offset) {
static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE* sf, eacs_header* ea, int find_loop) { static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE* sf, eacs_header* ea, int find_loop) {
int32_t num_samples = 0, block_id; int32_t num_samples = 0, block_id;
size_t file_size; size_t file_size;
int32_t(*read_s32)(off_t, STREAMFILE *) = ea->big_endian ? read_s32be : read_s32le; read_s32_t read_s32 = ea->big_endian ? read_s32be : read_s32le;
int loop_end_found = 0; int loop_end_found = 0;
file_size = get_streamfile_size(sf); file_size = get_streamfile_size(sf);
vgmstream->next_block_offset = ea->data_offset; vgmstream->next_block_offset = ea->data_offset;
while (vgmstream->next_block_offset < file_size) { while (vgmstream->next_block_offset < file_size) {
block_update_ea_1snh(vgmstream->next_block_offset, vgmstream); block_update(vgmstream->next_block_offset, vgmstream);
if (vgmstream->current_block_samples < 0) if (vgmstream->current_block_samples < 0)
break; break;
block_id = read_u32be(vgmstream->current_block_offset, sf); block_id = read_u32be(vgmstream->current_block_offset, sf);
if (find_loop) { if (find_loop) {
if (vgmstream->current_block_offset == ea->loop_start_offset) { if (vgmstream->current_block_offset == ea->loop_start_offset) {
ea->loop_start = num_samples; ea->loop_start = num_samples;
ea->loop_flag = 1; ea->loop_flag = 1;
block_update_ea_1snh(ea->data_offset, vgmstream); block_update(ea->data_offset, vgmstream);
return; return;
} }
} else { }
else {
if (block_id == get_id32be("1SNl") ) { /* loop point found */ if (block_id == get_id32be("1SNl") ) { /* loop point found */
ea->loop_start_offset = read_s32(vgmstream->current_block_offset + 0x08, sf); ea->loop_start_offset = read_s32(vgmstream->current_block_offset + 0x08, sf);
ea->loop_end = num_samples; ea->loop_end = num_samples;
@ -314,7 +331,7 @@ static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE* sf, eacs_h
ea->num_samples = num_samples; ea->num_samples = num_samples;
/* reset once we're done */ /* reset once we're done */
block_update_ea_1snh(ea->data_offset, vgmstream); block_update(ea->data_offset, vgmstream);
if (loop_end_found) { if (loop_end_found) {
/* recurse to find loop start sample */ /* recurse to find loop start sample */
@ -326,21 +343,27 @@ static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE* sf, eacs_h
static int get_ea_1snh_ima_version(STREAMFILE* sf, off_t start_offset, const eacs_header* ea) { static int get_ea_1snh_ima_version(STREAMFILE* sf, off_t start_offset, const eacs_header* ea) {
off_t block_offset = start_offset; off_t block_offset = start_offset;
size_t file_size = get_streamfile_size(sf); size_t file_size = get_streamfile_size(sf);
int32_t (*read_s32)(off_t,STREAMFILE*) = ea->big_endian ? read_s32be : read_s32le; read_s32_t read_s32 = ea->big_endian ? read_s32be : read_s32le;
if (ea->type == 0xFF) /* bnk */
return 0;
while (block_offset < file_size) { while (block_offset < file_size) {
uint32_t id = read_u32be(block_offset+0x00,sf); uint32_t id = read_u32be(block_offset+0x00,sf);
size_t block_size; size_t block_size;
/* BE in SAT, but one file may have both BE and LE chunks */ /* BE in SAT, but one file may have both BE and LE chunks */
if (guess_endianness32bit(block_offset + 0x04, sf)) if (guess_endian32(block_offset + 0x04, sf))
block_size = read_u32be(block_offset + 0x04, sf); block_size = read_u32be(block_offset + 0x04, sf);
else else
block_size = read_u32le(block_offset + 0x04, sf); block_size = read_u32le(block_offset + 0x04, sf);
if (id == 0x31534E64 || id == 0x534E4443) { /* "1SNd" "SNDC" audio data */ if (block_size == 0 || block_size == -1)
size_t ima_samples = read_s32(block_offset + 0x08, sf); break;
size_t expected_samples = (block_size - 0x08 - 0x04 - 0x08*ea->channels) * 2 / ea->channels;
if (id == get_id32be("1SNd") || id == get_id32be("SNDC")) {
int32_t ima_samples = read_s32(block_offset + 0x08, sf);
int32_t expected_samples = (block_size - 0x08 - 0x04 - 0x08*ea->channels) * 2 / ea->channels;
if (ima_samples == expected_samples) { if (ima_samples == expected_samples) {
return 1; /* has ADPCM hist (hopefully) */ return 1; /* has ADPCM hist (hopefully) */

View File

@ -1,118 +1,155 @@
#include "meta.h" #include "meta.h"
#include "../layout/layout.h" #include "../layout/layout.h"
#include "../coding/coding.h" #include "../coding/coding.h"
/* Possibly the same as EA_CODEC_x in variable SCHl */ /* Possibly the same as EA_CODEC_x in variable SCHl */
#define EA_CODEC_PCM 0x00 #define EA_CODEC_PCM 0x00
#define EA_CODEC_IMA 0x02 #define EA_CODEC_IMA 0x02
#define EA_CODEC_PSX 0x06
typedef struct {
int8_t version; typedef struct {
int8_t bps; int8_t version;
int8_t channels; int8_t bps;
int8_t codec; int8_t channels;
int16_t sample_rate; int8_t codec;
int32_t num_samples; int sample_rate;
int32_t num_samples;
int big_endian;
int loop_flag; int big_endian;
} ea_fixed_header; int loop_flag;
} ea_fixed_header;
static int parse_fixed_header(STREAMFILE* streamFile, ea_fixed_header* ea, off_t begin_offset);
static int parse_fixed_header(STREAMFILE* sf, ea_fixed_header* ea);
/* EA SCHl with fixed header - from EA games (~1997? ex. NHL 97 PC) */
VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE *streamFile) { /* EA SCHl with fixed header - from EA games (~1997?) */
VGMSTREAM * vgmstream = NULL; VGMSTREAM* init_vgmstream_ea_schl_fixed(STREAMFILE* sf) {
off_t start_offset; VGMSTREAM* vgmstream = NULL;
size_t header_size; off_t start_offset;
ea_fixed_header ea = {0}; size_t header_size;
ea_fixed_header ea = {0};
/* checks */
/* .asf: original /* checks */
* .lasf: fake for plugins */ if (!is_id32be(0x00,sf, "SCHl"))
if (!check_extensions(streamFile,"asf,lasf")) goto fail;
goto fail;
/* .asf: original [NHK 97 (PC)]
/* check header (see ea_schl.c for more info about blocks) */ * .lasf: fake for plugins
if (read_32bitBE(0x00,streamFile) != 0x5343486C) /* "SCHl" */ * .cnk: ps1 [NBA Live 97 (PS1)] */
goto fail; if (!check_extensions(sf,"asf,lasf,cnk"))
goto fail;
header_size = read_32bitLE(0x04,streamFile);
/* see ea_schl.c for more info about blocks */
if (!parse_fixed_header(streamFile,&ea, 0x08)) //TODO: handle SCCl? [NBA Live 97 (PS1)]
goto fail;
header_size = read_u32le(0x04,sf);
start_offset = header_size;
if (!parse_fixed_header(sf, &ea))
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(ea.channels, ea.loop_flag); start_offset = header_size;
if (!vgmstream) goto fail;
vgmstream->sample_rate = ea.sample_rate; /* build the VGMSTREAM */
vgmstream->num_samples = ea.num_samples; vgmstream = allocate_vgmstream(ea.channels, ea.loop_flag);
//vgmstream->loop_start_sample = ea.loop_start; if (!vgmstream) goto fail;
//vgmstream->loop_end_sample = ea.loop_end;
vgmstream->sample_rate = ea.sample_rate;
vgmstream->codec_endian = ea.big_endian; vgmstream->num_samples = ea.num_samples;
//vgmstream->loop_start_sample = ea.loop_start;
vgmstream->meta_type = meta_EA_SCHL_fixed; //vgmstream->loop_end_sample = ea.loop_end;
vgmstream->layout_type = layout_blocked_ea_schl; vgmstream->codec_endian = ea.big_endian;
switch (ea.codec) { vgmstream->meta_type = meta_EA_SCHL_fixed;
case EA_CODEC_PCM: vgmstream->layout_type = layout_blocked_ea_schl;
vgmstream->coding_type = ea.bps==8 ? coding_PCM8 : (ea.big_endian ? coding_PCM16BE : coding_PCM16LE);
break; switch (ea.codec) {
case EA_CODEC_PCM:
case EA_CODEC_IMA: vgmstream->coding_type = ea.bps==8 ? coding_PCM8 : (ea.big_endian ? coding_PCM16BE : coding_PCM16LE);
vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */ break;
break;
case EA_CODEC_IMA:
default: vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */
VGM_LOG("EA: unknown codec 0x%02x\n", ea.codec); break;
goto fail;
} case EA_CODEC_PSX:
vgmstream->coding_type = coding_PSX;
break;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail; default:
return vgmstream; VGM_LOG("EA: unknown codec 0x%02x\n", ea.codec);
goto fail;
fail: }
close_vgmstream(vgmstream);
return NULL;
} if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
static int parse_fixed_header(STREAMFILE* streamFile, ea_fixed_header* ea, off_t begin_offset) {
off_t offset = begin_offset; fail:
close_vgmstream(vgmstream);
if (read_32bitBE(offset+0x00, streamFile) != 0x5041546C && /* "PATl" */ return NULL;
read_32bitBE(offset+0x38, streamFile) != 0x544D706C) /* "TMpl" */ }
goto fail;
offset += 0x3c; /* after TMpl */ static int parse_fixed_header(STREAMFILE* sf, ea_fixed_header* ea) {
ea->version = read_8bit(offset+0x00, streamFile); uint32_t offset = 0x00, size = 0;
ea->bps = read_8bit(offset+0x01, streamFile);
ea->channels = read_8bit(offset+0x02, streamFile); if (is_id32be(offset+0x08, sf, "PATl"))
ea->codec = read_8bit(offset+0x03, streamFile); offset = 0x08;
VGM_ASSERT(read_16bitLE(offset+0x04, streamFile) != 0, "EA SCHl fixed: unknown1 found\n"); else if (is_id32be(offset+0x0c, sf, "PATl"))
/* 0x04(16): unknown */ offset = 0x0c; /* extra field in PS1 */
ea->sample_rate = (uint16_t)read_16bitLE(offset+0x06, streamFile); else
ea->num_samples = read_32bitLE(offset+0x08, streamFile); goto fail;
VGM_ASSERT(read_32bitLE(offset+0x0c, streamFile) != -1, "EA SCHl fixed: unknown2 found\n"); /* loop start? */
VGM_ASSERT(read_32bitLE(offset+0x10, streamFile) != -1, "EA SCHl fixed: unknown3 found\n"); /* loop end? */ size = read_u32le(offset+0x34, sf);
VGM_ASSERT(read_32bitLE(offset+0x14, streamFile) != 0, "EA SCHl fixed: unknown4 found\n"); /* data start? */ if (size == 0x20 && is_id32be(offset+0x38, sf, "TMpl")) { /* PC LE? */
VGM_ASSERT(read_32bitLE(offset+0x18, streamFile) != -1, "EA SCHl fixed: unknown5 found\n"); offset += 0x3c;
VGM_ASSERT(read_32bitLE(offset+0x1c, streamFile) != 0x7F, "EA SCHl fixed: unknown6 found\n");
ea->version = read_u8 (offset+0x00, sf);
//ea->loop_flag = (ea->loop_end_sample); ea->bps = read_u8 (offset+0x01, sf);
ea->channels = read_u8 (offset+0x02, sf);
return 1; ea->codec = read_u8 (offset+0x03, sf);
/* 0x04: 0? */
fail: ea->sample_rate = read_u16le(offset+0x06, sf);
return 0; ea->num_samples = read_s32le(offset+0x08, sf);
} /* 0x0c: -1? loop_start? */
/* 0x10: -1? loop_end? */
/* 0x14: 0? data start? */
/* 0x18: -1? */
/* 0x1c: volume? (always 128) */
}
else if (size == 0x38 && is_id32be(offset+0x38, sf, "TMxl")) { /* PSX LE? */
offset += 0x3c;
ea->version = read_u8 (offset+0x00, sf);
ea->bps = read_u8 (offset+0x01, sf);
ea->channels = read_u8 (offset+0x02, sf);
ea->codec = read_u8 (offset+0x03, sf);
/* 0x04: 0? */
ea->sample_rate = read_u16le(offset+0x06, sf);
/* 0x08: 0x20C? */
ea->num_samples = read_s32le(offset+0x0c, sf);
/* 0x10: -1? loop_start? */
/* 0x14: -1? loop_end? */
/* 0x18: 0x20C? */
/* 0x1c: 0? */
/* 0x20: 0? */
/* 0x24: 0? */
/* 0x28: -1? */
/* 0x2c: -1? */
/* 0x30: -1? */
/* 0x34: volume? (always 128) */
}
else {
goto fail;
}
//ea->loop_flag = (ea->loop_end_sample);
return 1;
fail:
return 0;
}

View File

@ -3,7 +3,10 @@
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../coding/hca_decoder_clhca.h" #include "../coding/hca_decoder_clhca.h"
#ifdef VGM_DEBUG_OUTPUT
//#define HCA_BRUTEFORCE //#define HCA_BRUTEFORCE
#endif
#ifdef HCA_BRUTEFORCE #ifdef HCA_BRUTEFORCE
static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigned long long* p_keycode, uint16_t subkey); static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigned long long* p_keycode, uint16_t subkey);
#endif #endif

View File

@ -48,7 +48,7 @@ static const hcakey_info hcakey_list[] = {
// - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita) // - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita)
{1234253142}, // 0000000049913556 {1234253142}, // 0000000049913556
// Idolm@ster Cinderella Stage (iOS/Android) // THE iDOLM@STER Cinderella Girls: Starlight Stage (iOS/Android)
// Shadowverse (iOS/Android) // Shadowverse (iOS/Android)
{59751358413602}, // 00003657F27E3B22 {59751358413602}, // 00003657F27E3B22
@ -76,7 +76,8 @@ static const hcakey_info hcakey_list[] = {
// Raramagi (iOS/Android) // Raramagi (iOS/Android)
{45719322}, // 0000000002B99F1A {45719322}, // 0000000002B99F1A
// Idolm@ster Million Live (iOS/Android) // THE iDOLM@STER Million Live! (iOS/Android)
// THE iDOLM@STER SideM GROWING STARS (Android)
{765765765765765}, // 0002B875BC731A85 {765765765765765}, // 0002B875BC731A85
// Kurokishi to Shiro no Maou (iOS/Android) // Kurokishi to Shiro no Maou (iOS/Android)
@ -775,7 +776,7 @@ static const hcakey_info hcakey_list[] = {
// Nogizaka 46 Fractal (Android) // Nogizaka 46 Fractal (Android)
{984635491346198130}, // 0DAA20C336EEAE72 {984635491346198130}, // 0DAA20C336EEAE72
}; };
#endif/*_HCA_KEYS_H_*/ #endif/*_HCA_KEYS_H_*/

View File

@ -2,74 +2,50 @@
#include "../coding/coding.h" #include "../coding/coding.h"
/* IKM - MiCROViSiON PS2 container [Zwei (PS2)] */ static VGMSTREAM* init_vgmstream_ikm_ps2(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_ikm_ps2(STREAMFILE* sf) { VGMSTREAM* v = NULL;
VGMSTREAM* vgmstream = NULL;
off_t start_offset; off_t start_offset;
int loop_flag, channel_count; int loop_flag, channels;
/* checks */
if (!is_id32be(0x00,sf, "IKM\0"))
goto fail;
if (!check_extensions(sf,"ikm"))
goto fail;
/* 0x20: type 03? */
if (!is_id32be(0x40,sf, "AST\0")) if (!is_id32be(0x40,sf, "AST\0"))
goto fail; goto fail;
loop_flag = (read_s32le(0x14, sf) > 0); loop_flag = (read_s32le(0x14, sf) > 0);
channel_count = read_s32le(0x50, sf); channels = read_s32le(0x50, sf);
start_offset = 0x800; start_offset = 0x800;
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag); v = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail; if (!v) goto fail;
vgmstream->meta_type = meta_IKM; v->meta_type = meta_IKM;
vgmstream->sample_rate = read_s32le(0x44, sf); v->sample_rate = read_s32le(0x44, sf);
vgmstream->num_samples = ps_bytes_to_samples(read_s32le(0x4c, sf), channel_count); v->num_samples = ps_bytes_to_samples(read_s32le(0x4c, sf), channels);
vgmstream->loop_start_sample = read_s32le(0x14, sf); v->loop_start_sample = read_s32le(0x14, sf);
vgmstream->loop_end_sample = read_s32le(0x18, sf); v->loop_end_sample = read_s32le(0x18, sf);
vgmstream->coding_type = coding_PSX; v->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave; v->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10; /* @0x40 / channels */ v->interleave_block_size = 0x10; /* @0x40 / channels */
if (!vgmstream_open_stream(vgmstream, sf, start_offset)) if (!vgmstream_open_stream(v, sf, start_offset))
goto fail; goto fail;
return vgmstream; return v;
fail: fail:
close_vgmstream(vgmstream); close_vgmstream(v);
return NULL; return NULL;
} }
/* IKM - MiCROViSiON PC container [Chaos Legion (PC), Legend of Galactic Heroes (PC)] */ static VGMSTREAM* init_vgmstream_ikm_pc(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_ikm_pc(STREAMFILE* sf) { VGMSTREAM* v = NULL;
VGMSTREAM* vgmstream = NULL;
off_t start_offset; off_t start_offset;
/* checks */
if (!is_id32be(0x00,sf, "IKM\0"))
goto fail;
if (!check_extensions(sf,"ikm"))
goto fail;
/* 0x20: type 01? */
/* find "OggS" start */ /* find "OggS" start */
if (is_id32be(0x30,sf, "OggS")) { if (is_id32be(0x30,sf, "OggS"))
start_offset = 0x30; /* Chaos Legion (PC) */ start_offset = 0x30; /* Chaos Legion (PC) */
} else
else if (is_id32be(0x800,sf, "OggS")) {
start_offset = 0x800; /* Legend of Galactic Heroes (PC) */ start_offset = 0x800; /* Legend of Galactic Heroes (PC) */
}
else {
goto fail;
}
{ {
ogg_vorbis_meta_info_t ovmi = {0}; ogg_vorbis_meta_info_t ovmi = {0};
@ -81,31 +57,23 @@ VGMSTREAM* init_vgmstream_ikm_pc(STREAMFILE* sf) {
ovmi.loop_flag = ovmi.loop_end > 0; ovmi.loop_flag = ovmi.loop_end > 0;
ovmi.stream_size = read_s32le(0x24, sf); ovmi.stream_size = read_s32le(0x24, sf);
vgmstream = init_vgmstream_ogg_vorbis_config(sf, start_offset, &ovmi); v = init_vgmstream_ogg_vorbis_config(sf, start_offset, &ovmi);
if (!v) goto fail;
} }
return vgmstream; return v;
fail: fail:
close_vgmstream(vgmstream); close_vgmstream(v);
return NULL; return NULL;
} }
/* IKM - MiCROViSiON PSP container [The Legend of Heroes: A Tear of Vermillion (PSP)] */ static VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) { VGMSTREAM* v = NULL;
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL; STREAMFILE* temp_sf = NULL;
off_t start_offset; off_t start_offset;
size_t data_size; size_t data_size;
/* checks */
if (!is_id32be(0x00,sf, "IKM\0"))
goto fail;
if (!check_extensions(sf,"ikm"))
goto fail;
/* 0x20: type 00? */
if (!is_id32be(0x800,sf, "RIFF")) if (!is_id32be(0x800,sf, "RIFF"))
goto fail; goto fail;
@ -116,16 +84,43 @@ VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) {
temp_sf = setup_subfile_streamfile(sf, start_offset, data_size, "at3"); temp_sf = setup_subfile_streamfile(sf, start_offset, data_size, "at3");
if (!temp_sf) goto fail; if (!temp_sf) goto fail;
vgmstream = init_vgmstream_riff(temp_sf); v = init_vgmstream_riff(temp_sf);
if (!vgmstream) goto fail; if (!v) goto fail;
vgmstream->meta_type = meta_IKM; v->meta_type = meta_IKM;
close_streamfile(temp_sf); close_streamfile(temp_sf);
return vgmstream; return v;
fail: fail:
close_streamfile(temp_sf); close_streamfile(temp_sf);
close_vgmstream(vgmstream); close_vgmstream(v);
return NULL;
}
/* IKM - MiCROViSiON container */
VGMSTREAM* init_vgmstream_ikm(STREAMFILE* sf) {
uint32_t type;
/* checks */
if (!is_id32be(0x00,sf, "IKM\0"))
goto fail;
if (!check_extensions(sf,"ikm"))
goto fail;
type = read_u32le(0x20, sf);
switch(type) {
case 0x00: /* The Legend of Heroes: A Tear of Vermillion (PSP) */
return init_vgmstream_ikm_psp(sf);
case 0x01: /* Chaos Legion (PC), Legend of Galactic Heroes (PC) */
return init_vgmstream_ikm_pc(sf);
case 0x03: /* Zwei (PS2) */
return init_vgmstream_ikm_ps2(sf);
default:
goto fail;
}
fail:
return NULL; return NULL;
} }

View File

@ -30,7 +30,13 @@ VGMSTREAM* init_vgmstream_lopu_fb(STREAMFILE* sf) {
/* rest: null */ /* rest: null */
loop_flag = (loop_end > 0); /* -1 if no loop */ loop_flag = (loop_end > 0); /* -1 if no loop */
/* Must remove skip or some files decode past limit. loop_end equals to PC (.ogg) version's max
* samples, but in some case (stage_park) goes slightly past max but is still valid.
* (loops shouldn't remove skip as they wouldn't match PC/bgm.txt loop times) */
num_samples -= skip; num_samples -= skip;
if (num_samples < loop_end)
num_samples = loop_end;
/* build the VGMSTREAM */ /* build the VGMSTREAM */

51
src/meta/lpcm_shade.c Normal file
View File

@ -0,0 +1,51 @@
#include "meta.h"
/* LPCM - from Shade's 'Shade game library' (ShdLib) [Ah! My Goddess (PS2), Warship Gunner (PS2)] */
VGMSTREAM* init_vgmstream_lpcm_shade(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
uint32_t loop_flag, channels;
/* checks */
if (!is_id32be(0x00, sf, "LPCM"))
goto fail;
/* .w: real extension
* .lpcm: fake (header id) */
if (!check_extensions(sf, "w,lpcm"))
goto fail;
/* extra checks since header is kind of simple */
if (read_s32le(0x04,sf) * 0x02 * 2 > get_streamfile_size(sf)) /* data size is less than total samples */
goto fail;
if (read_u32le(0x10,sf) != 0) /* just in case */
goto fail;
start_offset = 0x800; /* assumed, closer to num_samples */
loop_flag = read_s32le(0x8,sf) != 0;
channels = 2;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_LPCM_SHADE;
vgmstream->sample_rate = 48000;
vgmstream->num_samples = read_s32le(0x4,sf);
vgmstream->loop_start_sample = read_s32le(0x8,sf);
vgmstream->loop_end_sample = read_s32le(0xc,sf);
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View File

@ -222,9 +222,7 @@ VGMSTREAM * init_vgmstream_leg(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_filp(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_filp(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ikm_ps2(STREAMFILE * streamFile); VGMSTREAM* init_vgmstream_ikm(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_ikm_pc(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ikm_psp(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_sfs(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_sfs(STREAMFILE * streamFile);
@ -444,7 +442,9 @@ VGMSTREAM * init_vgmstream_myspd(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_his(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_his(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_ast(STREAMFILE* streamFile); VGMSTREAM* init_vgmstream_ast_mv(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ast_mmv(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_dmsg(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_dmsg(STREAMFILE* streamFile);
@ -480,7 +480,7 @@ VGMSTREAM * init_vgmstream_ps2_wad(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_adm(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps2_adm(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_lpcm(STREAMFILE* streamFile); VGMSTREAM* init_vgmstream_lpcm_shade(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_dsp_bdsp(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_dsp_bdsp(STREAMFILE* streamFile);

View File

@ -30,7 +30,7 @@ static VGMSTREAM* init_vgmstream_opus(STREAMFILE* sf, meta_t meta_type, off_t of
/* 0x80000002: 'offset info' chunk (seek table?), not seen */ /* 0x80000002: 'offset info' chunk (seek table?), not seen */
/* 'context info' chunk, rare [Famicom Detective Club (Switch)] */ /* 'context info' chunk, rare [Famicom Detective Club (Switch), SINce Memories (Switch)] */
if (context_offset && read_u32le(offset + context_offset, sf) == 0x80000003) { if (context_offset && read_u32le(offset + context_offset, sf) == 0x80000003) {
/* maybe should give priority to external info? */ /* maybe should give priority to external info? */
context_offset += offset; context_offset += offset;
@ -52,6 +52,12 @@ static VGMSTREAM* init_vgmstream_opus(STREAMFILE* sf, meta_t meta_type, off_t of
multistream_offset = offset + 0x20; multistream_offset = offset + 0x20;
} }
/* Opus can only do 48000 but some games store original rate [Grandia HD Collection, Lego Marvel] */
if (sample_rate != 48000) {
VGM_LOG("OPUS: ignored non-standard sample rate of %i\n", sample_rate);
sample_rate = 48000;
}
/* 'data info' chunk */ /* 'data info' chunk */
data_offset += offset; data_offset += offset;
@ -69,8 +75,6 @@ static VGMSTREAM* init_vgmstream_opus(STREAMFILE* sf, meta_t meta_type, off_t of
vgmstream->meta_type = meta_type; vgmstream->meta_type = meta_type;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
if (vgmstream->sample_rate == 16000)
vgmstream->sample_rate = 48000; // Grandia HD Collection contains a false sample_rate in header
vgmstream->num_samples = num_samples; vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start; vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end; vgmstream->loop_end_sample = loop_end;
@ -124,6 +128,9 @@ VGMSTREAM* init_vgmstream_opus_std(STREAMFILE* sf) {
int num_samples, loop_start, loop_end; int num_samples, loop_start, loop_end;
/* checks */ /* checks */
if (read_u32le(0x00,sf) != 0x80000001) /* 'basic info' chunk */
goto fail;
/* .opus: standard /* .opus: standard
* .bgm: Cotton Reboot (Switch) */ * .bgm: Cotton Reboot (Switch) */
if (!check_extensions(sf,"opus,lopus,bgm")) if (!check_extensions(sf,"opus,lopus,bgm"))
@ -157,11 +164,11 @@ VGMSTREAM* init_vgmstream_opus_n1(STREAMFILE* sf) {
int num_samples, loop_start, loop_end; int num_samples, loop_start, loop_end;
/* checks */ /* checks */
if (!check_extensions(sf,"opus,lopus"))
goto fail;
if (!((read_u32be(0x04,sf) == 0x00000000 && read_u32be(0x0c,sf) == 0x00000000) || if (!((read_u32be(0x04,sf) == 0x00000000 && read_u32be(0x0c,sf) == 0x00000000) ||
(read_u32be(0x04,sf) == 0xFFFFFFFF && read_u32be(0x0c,sf) == 0xFFFFFFFF))) (read_u32be(0x04,sf) == 0xFFFFFFFF && read_u32be(0x0c,sf) == 0xFFFFFFFF)))
goto fail; goto fail;
if (!check_extensions(sf,"opus,lopus"))
goto fail;
offset = 0x10; offset = 0x10;
num_samples = 0; num_samples = 0;
@ -181,7 +188,7 @@ VGMSTREAM* init_vgmstream_opus_capcom(STREAMFILE* sf) {
int channels; int channels;
/* checks */ /* checks */
if ( !check_extensions(sf,"opus,lopus")) if (!check_extensions(sf,"opus,lopus"))
goto fail; goto fail;
channels = read_32bitLE(0x04,sf); channels = read_32bitLE(0x04,sf);
@ -259,10 +266,10 @@ VGMSTREAM* init_vgmstream_opus_nop(STREAMFILE* sf) {
int num_samples, loop_start = 0, loop_end = 0, loop_flag; int num_samples, loop_start = 0, loop_end = 0, loop_flag;
/* checks */ /* checks */
if (!check_extensions(sf,"nop")) if (!is_id32be(0x00, sf, "sadf") ||
!is_id32be(0x08, sf, "opus"))
goto fail; goto fail;
if (read_32bitBE(0x00, sf) != 0x73616466 || /* "sadf" */ if (!check_extensions(sf,"nop"))
read_32bitBE(0x08, sf) != 0x6f707573) /* "opus" */
goto fail; goto fail;
offset = read_32bitLE(0x1c, sf); offset = read_32bitLE(0x1c, sf);
@ -284,9 +291,9 @@ VGMSTREAM* init_vgmstream_opus_shinen(STREAMFILE* sf) {
int num_samples, loop_start, loop_end; int num_samples, loop_start, loop_end;
/* checks */ /* checks */
if ( !check_extensions(sf,"opus,lopus")) if (read_u32be(0x08,sf) != 0x01000080)
goto fail; goto fail;
if (read_32bitBE(0x08,sf) != 0x01000080) if ( !check_extensions(sf,"opus,lopus"))
goto fail; goto fail;
offset = 0x08; offset = 0x08;
@ -308,11 +315,12 @@ VGMSTREAM* init_vgmstream_opus_nus3(STREAMFILE* sf) {
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
/* checks */ /* checks */
if (!is_id32be(0x00, sf, "OPUS"))
goto fail;
/* .opus: header ID (they only exist inside .nus3bank) */ /* .opus: header ID (they only exist inside .nus3bank) */
if (!check_extensions(sf, "opus,lopus")) if (!check_extensions(sf, "opus,lopus"))
goto fail; goto fail;
if (read_32bitBE(0x00, sf) != 0x4F505553) /* "OPUS" */
goto fail;
/* Here's an interesting quirk, OPUS header contains big endian values /* Here's an interesting quirk, OPUS header contains big endian values
while the Nintendo Opus header and data that follows remain little endian as usual */ while the Nintendo Opus header and data that follows remain little endian as usual */
@ -337,13 +345,13 @@ VGMSTREAM* init_vgmstream_opus_sps_n1(STREAMFILE* sf) {
int num_samples, loop_start = 0, loop_end = 0, loop_flag; int num_samples, loop_start = 0, loop_end = 0, loop_flag;
/* checks */ /* checks */
if (read_u32be(0x00, sf) != 0x09000000) /* file type (see other N1 SPS) */
goto fail;
/* .sps: Labyrinth of Refrain: Coven of Dusk (Switch) /* .sps: Labyrinth of Refrain: Coven of Dusk (Switch)
* .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) * .nlsd: Disgaea Refine (Switch), Ys VIII (Switch)
* .at9: void tRrLM(); //Void Terrarium (Switch) */ * .at9: void tRrLM(); //Void Terrarium (Switch) */
if (!check_extensions(sf, "sps,nlsd,at9")) if (!check_extensions(sf, "sps,nlsd,at9"))
goto fail; goto fail;
if (read_32bitBE(0x00, sf) != 0x09000000) /* file type (see other N1 SPS) */
goto fail;
num_samples = read_32bitLE(0x0C, sf); num_samples = read_32bitLE(0x0C, sf);
@ -382,9 +390,9 @@ VGMSTREAM* init_vgmstream_opus_opusx(STREAMFILE* sf) {
float modifier; float modifier;
/* checks */ /* checks */
if (!check_extensions(sf, "opusx")) if (!is_id32be(0x00, sf, "OPUS"))
goto fail; goto fail;
if (read_32bitBE(0x00, sf) != 0x4F505553) /* "OPUS" */ if (!check_extensions(sf, "opusx"))
goto fail; goto fail;
offset = 0x10; offset = 0x10;
@ -414,10 +422,11 @@ VGMSTREAM* init_vgmstream_opus_prototype(STREAMFILE* sf) {
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
/* checks */ /* checks */
if (!is_id32be(0x00, sf, "OPUS"))
goto fail;
if (!check_extensions(sf, "opus,lopus")) if (!check_extensions(sf, "opus,lopus"))
goto fail; goto fail;
if (read_32bitBE(0x00, sf) != 0x4F505553 || /* "OPUS" */ if (read_32bitBE(0x18, sf) != 0x01000080)
read_32bitBE(0x18, sf) != 0x01000080)
goto fail; goto fail;
offset = 0x18; offset = 0x18;
@ -441,9 +450,9 @@ VGMSTREAM* init_vgmstream_opus_opusnx(STREAMFILE* sf) {
int num_samples = 0, loop_start = 0, loop_end = 0; int num_samples = 0, loop_start = 0, loop_end = 0;
/* checks */ /* checks */
if (!check_extensions(sf, "opus,lopus")) if (!is_id64be(0x00, sf,"OPUSNX\0\0"))
goto fail; goto fail;
if (read_64bitBE(0x00, sf) != 0x4F5055534E580000) /* "OPUSNX\0\0" */ if (!check_extensions(sf, "opus,lopus"))
goto fail; goto fail;
offset = 0x10; offset = 0x10;
@ -462,9 +471,9 @@ VGMSTREAM* init_vgmstream_opus_nsopus(STREAMFILE* sf) {
int num_samples = 0, loop_start = 0, loop_end = 0; int num_samples = 0, loop_start = 0, loop_end = 0;
/* checks */ /* checks */
if (!check_extensions(sf, "nsopus")) if (!is_id32be(0x00, sf,"EWNO"))
goto fail; goto fail;
if (read_u32be(0x00, sf) != 0x45574E4F) /* "EWNO" */ if (!check_extensions(sf, "nsopus"))
goto fail; goto fail;
offset = 0x08; offset = 0x08;
@ -481,12 +490,12 @@ VGMSTREAM* init_vgmstream_opus_sqex(STREAMFILE* sf) {
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
/* checks */ /* checks */
/* .wav: default
* .opus: fake? */
if (!check_extensions(sf, "wav,lwav,opus,lopus"))
goto fail;
if (read_u32be(0x00, sf) != 0x01000000) if (read_u32be(0x00, sf) != 0x01000000)
goto fail; goto fail;
/* .wav: original */
if (!check_extensions(sf, "wav,lwav"))
goto fail;
/* 0x04: channels */ /* 0x04: channels */
/* 0x08: data_size */ /* 0x08: data_size */
offset = read_32bitLE(0x0C, sf); offset = read_32bitLE(0x0C, sf);

View File

@ -1,62 +0,0 @@
#include "meta.h"
#include "../coding/coding.h"
/* AST - from Koei and Marvelous games (same internal dev?) */
VGMSTREAM * init_vgmstream_ps2_ast(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count, variant_type;
/* check extension */
if (!check_extensions(streamFile,"ast")) goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x41535400) /* "AST\0" */
goto fail;
/* determine variant (after 0x10 is garbage/code data in type 1 until 0x800, but consistent in all songs) */
if (read_32bitBE(0x10,streamFile) == 0x00000000 || read_32bitBE(0x10,streamFile) == 0x20002000) {
variant_type = 1; /* Koei: P.T.O. IV (0x00000000), Naval Ops: Warship Gunner (0x20002000) */
channel_count = 2;
}
else {
variant_type = 2; /* Marvelous: Katekyoo Hitman Reborn! Dream Hyper Battle!, Binchou-tan: Shiawasegoyomi */
channel_count = read_32bitLE(0x0C,streamFile);
}
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
if (variant_type == 1) {
start_offset = 0x800;
vgmstream->sample_rate = read_32bitLE(0x04,streamFile);
vgmstream->num_samples = ps_bytes_to_samples(read_32bitLE(0x0C,streamFile)-start_offset,channel_count);
vgmstream->interleave_block_size = read_32bitLE(0x08,streamFile);
}
else if (variant_type == 2) {
start_offset = 0x100;
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);
vgmstream->num_samples = ps_bytes_to_samples(read_32bitLE(0x04,streamFile)-start_offset,channel_count);
vgmstream->interleave_block_size = read_32bitLE(0x10,streamFile);
}
else {
goto fail;
}
vgmstream->layout_type = layout_interleave;
vgmstream->coding_type = coding_PSX;
vgmstream->meta_type = meta_PS2_AST;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -1,64 +0,0 @@
#include "meta.h"
#include "../util.h"
/* LPCM (from Ah! My Goddess (PS2)) */
VGMSTREAM * init_vgmstream_ps2_lpcm(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("lpcm",filename_extension(filename))) goto fail;
/* check header */
if (read_32bitBE(0,streamFile) != 0x4C50434D) /* LPCM */
goto fail;
loop_flag = read_32bitLE(0x8,streamFile);
channel_count = 2;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = 0x10;
vgmstream->channels = channel_count;
vgmstream->sample_rate = 48000;
vgmstream->coding_type = coding_PCM16LE;
vgmstream->num_samples = read_32bitLE(0x4,streamFile);
if (loop_flag) {
vgmstream->loop_start_sample = read_32bitLE(0x8,streamFile);
vgmstream->loop_end_sample = read_32bitLE(0xc,streamFile);
}
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 2;
vgmstream->meta_type = meta_PS2_LPCM;
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
return vgmstream;
fail:
/* clean up anything we may have opened */
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View File

@ -40,6 +40,7 @@ typedef enum {
ASF = 30, /* Argonaut ASF 4-bit ADPCM */ ASF = 30, /* Argonaut ASF 4-bit ADPCM */
EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */ EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */
OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */
XA = 33,
UNKNOWN = 99, UNKNOWN = 99,
} txth_codec_t; } txth_codec_t;
@ -117,6 +118,9 @@ typedef struct {
uint32_t chunk_count; uint32_t chunk_count;
uint32_t chunk_header_size; uint32_t chunk_header_size;
uint32_t chunk_data_size; uint32_t chunk_data_size;
uint32_t chunk_value;
uint32_t chunk_size_offset;
uint32_t chunk_be;
int chunk_start_set; int chunk_start_set;
int chunk_size_set; int chunk_size_set;
int chunk_count_set; int chunk_count_set;
@ -256,6 +260,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case TGC: coding = coding_TGC; break; case TGC: coding = coding_TGC; break;
case ASF: coding = coding_ASF; break; case ASF: coding = coding_ASF; break;
case EAXA: coding = coding_EA_XA; break; case EAXA: coding = coding_EA_XA; break;
case XA: coding = coding_XA; break;
default: default:
goto fail; goto fail;
} }
@ -367,6 +372,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case coding_OKI16: case coding_OKI16:
case coding_OKI4S: case coding_OKI4S:
case coding_XA:
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
break; break;
@ -648,7 +654,7 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
* - etc * - etc
* to avoid it we set a particular fake extension and detect it when reading .txth * to avoid it we set a particular fake extension and detect it when reading .txth
*/ */
strcpy(extension, "subfile_txth."); strcpy(extension, ".subfile_txth.");
strcat(extension, txth->subfile_extension); strcat(extension, txth->subfile_extension);
sf_sub = setup_subfile_streamfile(txth->sf_body, txth->subfile_offset, txth->subfile_size, extension); sf_sub = setup_subfile_streamfile(txth->sf_body, txth->subfile_offset, txth->subfile_size, extension);
@ -722,50 +728,43 @@ fail:
static STREAMFILE* open_txth(STREAMFILE* sf) { static STREAMFILE* open_txth(STREAMFILE* sf) {
char basename[PATH_LIMIT];
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
char fileext[PATH_LIMIT]; const char* base_ext;
const char *subext; const char* txth_ext;
STREAMFILE* sf_text; STREAMFILE* sf_text;
/* try "(path/)(name.ext).txth" */
get_streamfile_name(sf,filename,PATH_LIMIT); get_streamfile_name(sf, filename, sizeof(filename));
if (strstr(filename, "subfile_txth") != NULL) if (strstr(filename, ".subfile_txth") != NULL)
return NULL; /* detect special case of subfile-within-subfile */ return NULL; /* detect special case of subfile-within-subfile */
strcat(filename, ".txth");
sf_text = open_streamfile(sf,filename);
if (sf_text) return sf_text;
/* try "(path/)(.sub.ext).txth" */ base_ext = filename_extension(filename);
get_streamfile_basename(sf,basename,PATH_LIMIT); concatn(sizeof(filename), filename, ".txth");
subext = filename_extension(basename); txth_ext = filename_extension(filename);
if (subext != NULL && subext[0] != '\0') {
get_streamfile_path(sf,filename,PATH_LIMIT);
get_streamfile_ext(sf,fileext,PATH_LIMIT);
strcat(filename,".");
strcat(filename, subext);
strcat(filename,".");
strcat(filename, fileext);
strcat(filename, ".txth");
sf_text = open_streamfile(sf,filename); /* try "(path/)(name.ext).txth" */
{
/* full filename, already prepared */
sf_text = open_streamfile(sf, filename);
if (sf_text) return sf_text; if (sf_text) return sf_text;
} }
/* try "(path/)(.ext).txth" */ /* try "(path/)(.ext).txth" */
get_streamfile_path(sf,filename,PATH_LIMIT); if (base_ext) {
get_streamfile_ext(sf,fileext,PATH_LIMIT); base_ext--; //get_streamfile_path(sf, filename, sizeof(filename));
strcat(filename,".");
strcat(filename, fileext); sf_text = open_streamfile_by_filename(sf, base_ext);
strcat(filename, ".txth"); if (sf_text) return sf_text;
sf_text = open_streamfile(sf,filename); }
if (sf_text) return sf_text;
/* try "(path/).txth" */ /* try "(path/).txth" */
get_streamfile_path(sf,filename,PATH_LIMIT); if (txth_ext) {
strcat(filename, ".txth"); txth_ext--; /* points to "txth" due to the concat */
sf_text = open_streamfile(sf,filename);
if (sf_text) return sf_text; sf_text = open_streamfile_by_filename(sf, txth_ext);
if (sf_text) return sf_text;
}
/* not found */ /* not found */
return NULL; return NULL;
@ -788,7 +787,9 @@ static void set_body_chunk(txth_header* txth) {
//todo maybe should only be done once, or have some count to retrigger to simplify? //todo maybe should only be done once, or have some count to retrigger to simplify?
if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set) if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set)
return; return;
if (txth->chunk_size == 0 || txth->chunk_start > txth->data_size || txth->chunk_count == 0) if ((txth->chunk_size == 0 && ! txth->chunk_size_offset) ||
txth->chunk_start > txth->data_size ||
txth->chunk_count == 0)
return; return;
if (!txth->sf_body) if (!txth->sf_body)
return; return;
@ -804,18 +805,22 @@ static void set_body_chunk(txth_header* txth) {
{ {
txth_io_config_data cfg = {0}; txth_io_config_data cfg = {0};
cfg.chunk_start = txth->chunk_start; cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */
cfg.chunk_header_size = txth->chunk_header_size; cfg.chunk_header_size = txth->chunk_header_size;
cfg.chunk_data_size = txth->chunk_data_size; cfg.chunk_data_size = txth->chunk_data_size;
cfg.chunk_value = txth->chunk_value;
cfg.chunk_size_offset = txth->chunk_size_offset;
cfg.chunk_be = txth->chunk_be;
cfg.chunk_start = txth->chunk_start;
cfg.chunk_size = txth->chunk_size; cfg.chunk_size = txth->chunk_size;
cfg.chunk_count = txth->chunk_count; cfg.chunk_count = txth->chunk_count;
cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */
temp_sf = setup_txth_streamfile(txth->sf_body, cfg, txth->sf_body_opened); temp_sf = setup_txth_streamfile(txth->sf_body, cfg, txth->sf_body_opened);
if (!temp_sf) return; if (!temp_sf) return;
} }
/* closing is handled by temp_sf */ /* closing is handled by temp_sf */
//if (txth->sf_body_opened) { //if (txth->sf_body_opened) {
// close_streamfile(txth->sf_body); // close_streamfile(txth->sf_body);
@ -939,6 +944,7 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
else if (is_string(val,"GCOM_ADPCM")) return TGC; else if (is_string(val,"GCOM_ADPCM")) return TGC;
else if (is_string(val,"ASF")) return ASF; else if (is_string(val,"ASF")) return ASF;
else if (is_string(val,"EAXA")) return EAXA; else if (is_string(val,"EAXA")) return EAXA;
else if (is_string(val,"XA")) return XA;
/* special handling */ /* special handling */
else if (is_string(val,"name_value")) return txth->name_values[0]; else if (is_string(val,"name_value")) return txth->name_values[0];
else if (is_string(val,"name_value1")) return txth->name_values[0]; else if (is_string(val,"name_value1")) return txth->name_values[0];
@ -949,6 +955,19 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
return UNKNOWN; return UNKNOWN;
} }
static int parse_be(txth_header* txth, const char* val, uint32_t* p_value) {
if (is_string(val, "BE"))
*p_value = 1;
else if (is_string(val, "LE"))
*p_value = 0;
else
if (!parse_num(txth->sf_head,txth,val, p_value))
goto fail;
return 1;
fail:
return 0;
}
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) { static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) {
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val); //;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
@ -1181,11 +1200,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_num(txth->sf_head,txth,val, &txth->coef_spacing)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->coef_spacing)) goto fail;
} }
else if (is_string(key,"coef_endianness")) { else if (is_string(key,"coef_endianness")) {
if (is_string(val, "BE")) if (!parse_be(txth, val, &txth->coef_big_endian)) goto fail;
txth->coef_big_endian = 1;
else if (is_string(val, "LE"))
txth->coef_big_endian = 0;
else if (!parse_num(txth->sf_head,txth,val, &txth->coef_big_endian)) goto fail;
} }
else if (is_string(key,"coef_mode")) { else if (is_string(key,"coef_mode")) {
if (!parse_num(txth->sf_head,txth,val, &txth->coef_mode)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->coef_mode)) goto fail;
@ -1208,11 +1223,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_num(txth->sf_head,txth,val, &txth->hist_spacing)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->hist_spacing)) goto fail;
} }
else if (is_string(key,"hist_endianness")) { else if (is_string(key,"hist_endianness")) {
if (is_string(val, "BE")) if (!parse_be(txth, val, &txth->hist_big_endian)) goto fail;
txth->hist_big_endian = 1;
else if (is_string(val, "LE"))
txth->hist_big_endian = 0;
else if (!parse_num(txth->sf_head,txth,val, &txth->hist_big_endian)) goto fail;
} }
/* SUBSONGS */ /* SUBSONGS */
@ -1336,34 +1347,41 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
} }
/* CHUNKS */ /* CHUNKS */
else if (is_string(key,"chunk_number")) { else if (is_string(key,"chunk_count")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->chunk_count)) goto fail;
txth->chunk_count_set = 1;
set_body_chunk(txth);
} }
else if (is_string(key,"chunk_start")) { else if (is_string(key,"chunk_start")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_start)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->chunk_start)) goto fail;
txth->chunk_start_set = 1; txth->chunk_start_set = 1;
set_body_chunk(txth); set_body_chunk(txth);
} }
else if (is_string(key,"chunk_header_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_header_size)) goto fail;
//txth->chunk_header_size_set = 1;
//set_body_chunk(txth); /* optional and should go before chunk_size */
}
else if (is_string(key,"chunk_data_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_data_size)) goto fail;
//txth->chunk_data_size_set = 1;
//set_body_chunk(txth); /* optional and should go before chunk_size */
}
else if (is_string(key,"chunk_size")) { else if (is_string(key,"chunk_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size)) goto fail; if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size)) goto fail;
txth->chunk_size_set = 1; txth->chunk_size_set = 1;
set_body_chunk(txth); set_body_chunk(txth);
} }
else if (is_string(key,"chunk_count")) { /* optional and should go before the above */
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_count)) goto fail; else if (is_string(key,"chunk_number")) {
txth->chunk_count_set = 1; if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail;
set_body_chunk(txth);
} }
else if (is_string(key,"chunk_header_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_header_size)) goto fail;
}
else if (is_string(key,"chunk_data_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_data_size)) goto fail;
}
else if (is_string(key,"chunk_value")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_value)) goto fail;
}
else if (is_string(key,"chunk_size_offset")) {
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size_offset)) goto fail;
}
else if (is_string(key,"chunk_endianness")) {
if (!parse_be(txth, val, &txth->chunk_be)) goto fail;
}
/* BASE OFFSET */ /* BASE OFFSET */
else if (is_string(key,"base_offset")) { else if (is_string(key,"base_offset")) {
@ -2001,6 +2019,8 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
return asf_bytes_to_samples(bytes, txth->channels); return asf_bytes_to_samples(bytes, txth->channels);
case EAXA: case EAXA:
return ea_xa_bytes_to_samples(bytes, txth->channels); return ea_xa_bytes_to_samples(bytes, txth->channels);
case XA:
return xa_bytes_to_samples(bytes, txth->channels, 0, 0, 4);
/* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */
case XMA1: case XMA1:

View File

@ -1,30 +1,36 @@
#ifndef _TXTH_STREAMFILE_H_ #ifndef _TXTH_STREAMFILE_H_
#define _TXTH_STREAMFILE_H_ #define _TXTH_STREAMFILE_H_
#include "../streamfile.h" #include "../streamfile.h"
#include "../util/endianness.h"
typedef struct { typedef struct {
off_t chunk_start; uint32_t chunk_start;
size_t chunk_size; uint32_t chunk_size;
size_t chunk_header_size; uint32_t chunk_header_size;
size_t chunk_data_size; uint32_t chunk_data_size;
int chunk_count; int chunk_count;
int chunk_number; int chunk_number;
uint32_t chunk_value;
uint32_t chunk_size_offset;
int chunk_be;
} txth_io_config_data; } txth_io_config_data;
typedef struct { typedef struct {
/* config */ /* config */
txth_io_config_data cfg; txth_io_config_data cfg;
size_t stream_size; uint32_t stream_size;
/* state */ /* state */
off_t logical_offset; /* fake offset */ uint32_t logical_offset; /* fake offset */
off_t physical_offset; /* actual offset */ uint32_t physical_offset; /* actual offset */
size_t block_size; /* current size */ uint32_t block_size; /* current size */
size_t skip_size; /* size from block start to reach data */ uint32_t skip_size; /* size from block start to reach data */
size_t data_size; /* usable size in a block */ uint32_t data_size; /* usable size in a block */
size_t logical_size; uint32_t logical_size;
} txth_io_data; } txth_io_data;
@ -64,6 +70,27 @@ static size_t txth_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t l
data->data_size = data->cfg.chunk_data_size; data->data_size = data->cfg.chunk_data_size;
} }
/* chunk size reader (overwrites the above) */
if (data->cfg.chunk_header_size && data->cfg.chunk_size_offset) {
read_u32_t read_u32 = data->cfg.chunk_be ? read_u32be : read_u32le;
data->block_size = read_u32(data->physical_offset + data->cfg.chunk_size_offset, sf);
data->data_size = data->block_size - data->cfg.chunk_header_size;
VGM_LOG("bs %x = %x\n", data->physical_offset, data->block_size);
/* skip chunk if doesn't match expected header value */
if (data->cfg.chunk_value) {
uint32_t value = read_u32(data->physical_offset + 0x00, sf);
if (value != data->cfg.chunk_value) {
VGM_LOG("skip %x vs %x at %x\n", value, data->cfg.chunk_value, data->physical_offset);
data->data_size = 0;
}
}
else {
VGM_LOG("not skip at %x\n", data->physical_offset) ;
}
}
/* clamp for games where last block is smaller */ //todo not correct for all cases /* clamp for games where last block is smaller */ //todo not correct for all cases
if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) { if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) {
data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;

View File

@ -6,53 +6,45 @@
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2); static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2);
static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked); static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked);
/* XA - from Sony PS1 and Philips CD-i CD audio, also Saturn streams */ /* XA - from Sony PS1 and Philips CD-i CD audio */
VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset; off_t start_offset;
int loop_flag = 0, channels, sample_rate, bps; int loop_flag = 0, channels, sample_rate, bps;
int is_riff = 0, is_blocked = 0, is_form2 = 0; int is_riff = 0, is_form2 = 0, is_blocked;
size_t stream_size = 0; size_t stream_size = 0;
int total_subsongs = 0, target_subsong = sf->stream_index; int total_subsongs = 0, target_subsong = sf->stream_index;
uint16_t target_config = 0; uint16_t target_config = 0;
/* checks */ /* checks */
/* .xa: common if (read_u32be(0x00,sf) == 0x00FFFFFF && read_u32be(0x04,sf) == 0xFFFFFFFF && read_u32be(0x08,sf) == 0xFFFFFF00) {
* .str: often videos and sometimes speech/music /* sector sync word = raw data */
* .adp: Phantasy Star Collection (SAT) raw XA is_blocked = 1;
* .pxa: Mortal Kombat 4 (PS1) start_offset = 0x00;
* .grn: Micro Machines (CDi) }
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */ else if (is_id32be(0x00,sf, "RIFF") && is_id32be(0x08,sf, "CDXA") && is_id32be(0x0C,sf, "fmt ")) {
if (!check_extensions(sf,"xa,str,adp,pxa,grn,")) /* RIFF header = raw with header (optional, added by CD drivers when copying and not part of the CD data) */
goto fail;
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
* Also has minimal support for headerless (ISO 2048 mode1/data) mode. */
/* check RIFF header = raw (optional, added when ripping and not part of the CD data) */
if (read_u32be(0x00,sf) == 0x52494646 && /* "RIFF" */
read_u32be(0x08,sf) == 0x43445841 && /* "CDXA" */
read_u32be(0x0C,sf) == 0x666D7420) { /* "fmt " */
is_blocked = 1; is_blocked = 1;
is_riff = 1; is_riff = 1;
start_offset = 0x2c; /* after "data", ignore RIFF values as often are wrong */ start_offset = 0x2c; /* after "data", ignore RIFF values as often are wrong */
} }
else { else {
/* sector sync word = raw */ /* non-blocked (ISO 2048 mode1/data) or incorrectly ripped: use TXTH */
if (read_u32be(0x00,sf) == 0x00FFFFFF && goto fail;
read_u32be(0x04,sf) == 0xFFFFFFFF &&
read_u32be(0x08,sf) == 0xFFFFFF00) {
is_blocked = 1;
start_offset = 0x00;
}
else {
/* headerless or possibly incorrectly ripped */
start_offset = 0x00;
}
} }
/* .xa: common
* .str: often videos and sometimes speech/music
* .pxa: Mortal Kombat 4 (PS1)
* .grn: Micro Machines (CDi)
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
if (!check_extensions(sf,"xa,str,pxa,grn,"))
goto fail;
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
* For headerless XA (ISO 2048 mode1/data) mode use TXTH. */
/* test for XA data, since format is raw-ish (with RIFF it's assumed to be ok) */ /* test for XA data, since format is raw-ish (with RIFF it's assumed to be ok) */
if (!is_riff && !xa_check_format(sf, start_offset, is_blocked)) if (!is_riff && !xa_check_format(sf, start_offset, is_blocked))
goto fail; goto fail;
@ -89,42 +81,18 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */ switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */
case 0: break; case 0: break;
default: /* shouldn't be used by games */ default: /* shouldn't be used by games */
VGM_LOG("XA: unknown emphasis found\n"); vgm_logi("XA: unknown emphasis found\n");
break; goto fail;
} }
switch((xa_header >> 7) & 1) { /* 7: reserved */ switch((xa_header >> 7) & 1) { /* 7: reserved */
case 0: break; case 0: break;
default: default:
VGM_LOG("XA: unknown reserved bit found\n"); vgm_logi("XA: unknown reserved bit found\n");
break; goto fail;
} }
} }
else { else {
/* headerless */ goto fail;
if (check_extensions(sf,"adp")) {
/* Phantasy Star Collection (SAT) raw files */
/* most are stereo, though a few (mainly sfx banks, sometimes using .bin) are mono */
char filename[PATH_LIMIT] = {0};
get_streamfile_filename(sf, filename,PATH_LIMIT);
/* detect PS1 mono files, very lame but whatevs, no way to detect XA mono/stereo */
if (filename[0]=='P' && filename[1]=='S' && filename[2]=='1' && filename[3]=='S') {
channels = 1;
sample_rate = 22050;
}
else {
channels = 2;
sample_rate = 44100;
}
bps = 4;
}
else {
/* incorrectly ripped standard XA */
channels = 2;
sample_rate = 37800;
bps = 4;
}
} }
/* untested */ /* untested */
@ -161,7 +129,9 @@ fail:
return NULL; return NULL;
} }
static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) { static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
uint8_t frame_hdr[0x10];
int i, j, sector = 0, skip = 0; int i, j, sector = 0, skip = 0;
off_t test_offset = offset; off_t test_offset = offset;
const size_t sector_size = (is_blocked ? 0x900 : 0x800); const size_t sector_size = (is_blocked ? 0x900 : 0x800);
@ -172,23 +142,27 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
/* test frames inside CD sectors */ /* test frames inside CD sectors */
while (sector < sector_max) { while (sector < sector_max) {
uint8_t xa_submode = read_u8(test_offset + 0x12, sf); if (is_blocked) {
int is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02); uint8_t xa_submode = read_u8(test_offset + 0x12, sf);
int is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
if (is_blocked && !is_audio) { if (is_blocked && !is_audio) {
skip++; skip++;
if (sector == 0 && skip > skip_max) /* no a single audio sector found */ if (sector == 0 && skip > skip_max) /* no a single audio sector found */
goto fail; goto fail;
test_offset += sector_size + extra_size + extra_size; test_offset += sector_size + extra_size + extra_size;
continue; continue;
}
} }
test_offset += extra_size; /* header */ test_offset += extra_size; /* header */
for (i = 0; i < (sector_size / frame_size); i++) { for (i = 0; i < (sector_size / frame_size); i++) {
read_streamfile(frame_hdr, test_offset, sizeof(frame_hdr), sf);
/* XA frame checks: filter indexes should be 0..3, and shifts 0..C */ /* XA frame checks: filter indexes should be 0..3, and shifts 0..C */
for (j = 0; j < 16; j++) { for (j = 0; j < 16; j++) {
uint8_t header = read_u8(test_offset + j, sf); uint8_t header = get_u8(frame_hdr + j);
if (((header >> 4) & 0xF) > 0x03) if (((header >> 4) & 0xF) > 0x03)
goto fail; goto fail;
if (((header >> 0) & 0xF) > 0x0c) if (((header >> 0) & 0xF) > 0x0c)
@ -196,14 +170,14 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
} }
/* XA headers pairs are repeated */ /* XA headers pairs are repeated */
if (read_u32be(test_offset+0x00, sf) != read_u32be(test_offset+0x04, sf) || if (get_u32be(frame_hdr+0x00) != get_u32be(frame_hdr+0x04) ||
read_u32be(test_offset+0x08, sf) != read_u32be(test_offset+0x0c, sf)) get_u32be(frame_hdr+0x08) != get_u32be(frame_hdr+0x0c))
goto fail; goto fail;
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */ /* blank frames should always use 0x0c0c0c0c (due to how shift works) */
if (read_u32be(test_offset+0x00, sf) == 0 && if (get_u32be(frame_hdr+0x00) == 0 &&
read_u32be(test_offset+0x04, sf) == 0 && get_u32be(frame_hdr+0x04) == 0 &&
read_u32be(test_offset+0x08, sf) == 0 && get_u32be(frame_hdr+0x08) == 0 &&
read_u32be(test_offset+0x0c, sf) == 0) get_u32be(frame_hdr+0x0c) == 0)
goto fail; goto fail;
test_offset += 0x80; test_offset += 0x80;

View File

@ -1,12 +1,22 @@
#include "streamfile.h" #include "streamfile.h"
#include "util.h" #include "util.h"
#include "vgmstream.h" #include "vgmstream.h"
#include <string.h>
/* for dup/fdopen in some systems */ /* for dup/fdopen in some systems */
#ifndef _MSC_VER #ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
#endif #endif
//TODO: move
#ifndef DIR_SEPARATOR
#if defined (_WIN32) || defined (WIN32)
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
#endif
/* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available /* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available
* but may depend on compiler. * but may depend on compiler.
* - MSVC: +VS2008 should work * - MSVC: +VS2008 should work

View File

@ -1,7 +1,6 @@
/* /*
* streamfile.h - definitions for buffered file reading with STREAMFILE * streamfile.h - definitions for buffered file reading with STREAMFILE
*/ */
#ifndef _STREAMFILE_H #ifndef _STREAMFILE_H
#define _STREAMFILE_H #define _STREAMFILE_H
@ -9,9 +8,14 @@
#define _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_DEPRECATE
#endif #endif
//TODO cleanup
//NULL, allocs
#include <stdlib.h> #include <stdlib.h>
//FILE
#include <stdio.h> #include <stdio.h>
//string functions in meta and so on
#include <string.h> #include <string.h>
//off_t
#include <sys/types.h> #include <sys/types.h>
#include "streamtypes.h" #include "streamtypes.h"
#include "util.h" #include "util.h"
@ -22,14 +26,6 @@
#include <io.h> #include <io.h>
#endif #endif
#ifndef DIR_SEPARATOR
#if defined (_WIN32) || defined (WIN32)
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
#endif
/* 64-bit offset is needed for banks that hit +2.5GB (like .fsb or .ktsl2stbin). /* 64-bit offset is needed for banks that hit +2.5GB (like .fsb or .ktsl2stbin).
* Leave as typedef to toggle since it's theoretically slower when compiled as 32-bit. * Leave as typedef to toggle since it's theoretically slower when compiled as 32-bit.
* ATM it's only used in choice places until more performance tests are done. * ATM it's only used in choice places until more performance tests are done.

View File

@ -7,4 +7,8 @@ typedef uint32_t (*read_u32_t)(off_t, STREAMFILE*);
typedef int32_t (*read_s32_t)(off_t, STREAMFILE*); typedef int32_t (*read_s32_t)(off_t, STREAMFILE*);
typedef uint16_t (*read_u16_t)(off_t, STREAMFILE*); typedef uint16_t (*read_u16_t)(off_t, STREAMFILE*);
//todo move here
#define guess_endian32 guess_endianness32bit
#define guess_endian16 guess_endianness16bit
#endif #endif

View File

@ -92,9 +92,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_musx, init_vgmstream_musx,
init_vgmstream_leg, init_vgmstream_leg,
init_vgmstream_filp, init_vgmstream_filp,
init_vgmstream_ikm_ps2, init_vgmstream_ikm,
init_vgmstream_ikm_pc,
init_vgmstream_ikm_psp,
init_vgmstream_sfs, init_vgmstream_sfs,
init_vgmstream_bg00, init_vgmstream_bg00,
init_vgmstream_sat_dvi, init_vgmstream_sat_dvi,
@ -214,7 +212,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_xbox_hlwav, init_vgmstream_xbox_hlwav,
init_vgmstream_myspd, init_vgmstream_myspd,
init_vgmstream_his, init_vgmstream_his,
init_vgmstream_ps2_ast, init_vgmstream_ast_mmv,
init_vgmstream_ast_mv,
init_vgmstream_dmsg, init_vgmstream_dmsg,
init_vgmstream_ngc_dsp_aaap, init_vgmstream_ngc_dsp_aaap,
init_vgmstream_ngc_dsp_konami, init_vgmstream_ngc_dsp_konami,
@ -237,7 +236,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_dsp_xiii, init_vgmstream_dsp_xiii,
init_vgmstream_dsp_cabelas, init_vgmstream_dsp_cabelas,
init_vgmstream_ps2_adm, init_vgmstream_ps2_adm,
init_vgmstream_ps2_lpcm, init_vgmstream_lpcm_shade,
init_vgmstream_dsp_bdsp, init_vgmstream_dsp_bdsp,
init_vgmstream_ps2_vms, init_vgmstream_ps2_vms,
init_vgmstream_xau, init_vgmstream_xau,

View File

@ -507,7 +507,8 @@ typedef enum {
meta_PONA_3DO, /* Policenauts (3DO) */ meta_PONA_3DO, /* Policenauts (3DO) */
meta_PONA_PSX, /* Policenauts (PSX) */ meta_PONA_PSX, /* Policenauts (PSX) */
meta_XBOX_HLWAV, /* Half Life 2 (XBOX) */ meta_XBOX_HLWAV, /* Half Life 2 (XBOX) */
meta_PS2_AST, /* Some KOEI game (PS2) */ meta_AST_MV,
meta_AST_MMV,
meta_DMSG, /* Nightcaster II - Equinox (XBOX) */ meta_DMSG, /* Nightcaster II - Equinox (XBOX) */
meta_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */ meta_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */
meta_PS2_STER, /* Juuni Kokuki: Kakukaku Taru Ou Michi Beni Midori no Uka */ meta_PS2_STER, /* Juuni Kokuki: Kakukaku Taru Ou Michi Beni Midori no Uka */
@ -530,7 +531,7 @@ typedef enum {
meta_DSP_XIII, /* XIII, possibly more (Ubisoft header???) */ meta_DSP_XIII, /* XIII, possibly more (Ubisoft header???) */
meta_DSP_CABELAS, /* Cabelas games */ meta_DSP_CABELAS, /* Cabelas games */
meta_PS2_ADM, /* Dragon Quest V (PS2) */ meta_PS2_ADM, /* Dragon Quest V (PS2) */
meta_PS2_LPCM, /* Ah! My Goddess */ meta_LPCM_SHADE,
meta_DSP_BDSP, /* Ah! My Goddess */ meta_DSP_BDSP, /* Ah! My Goddess */
meta_PS2_VMS, /* Autobahn Raser - Police Madness */ meta_PS2_VMS, /* Autobahn Raser - Police Madness */
meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */ meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */