mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 08:20:54 +01:00
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:
commit
08dc607e2d
13
.gitignore
vendored
13
.gitignore
vendored
@ -5,6 +5,11 @@
|
||||
*.o
|
||||
*.a
|
||||
|
||||
# gcc (-MMD -save-temps)
|
||||
*.d
|
||||
*.i
|
||||
*.s
|
||||
|
||||
# VS stuff
|
||||
ipch
|
||||
.vs
|
||||
@ -43,14 +48,18 @@ Release
|
||||
/xmplay/Debug
|
||||
/xmplay/Release
|
||||
/xmplay/*.dll
|
||||
/dependencies
|
||||
/version_auto.h
|
||||
|
||||
/msvc-build.config.ps1
|
||||
/msvc-build.log
|
||||
# for test batchs, note that already tracked files are never ignored
|
||||
/msvc-build-*.bat
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# build
|
||||
/version_auto.h
|
||||
/dependencies
|
||||
/bin/**/*
|
||||
/tmp/**/*
|
||||
/**/vgmstream-win.zip
|
||||
|
@ -119,7 +119,7 @@ class App(object):
|
||||
count += 1
|
||||
|
||||
if not count:
|
||||
print("%s: no .txtp found" % (filename, count))
|
||||
print("%s: no .txtp found" % (filename))
|
||||
else:
|
||||
print("%s: total %i .txtp" % (filename, count))
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
# !/usr/bin/python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import argparse
|
||||
import re
|
||||
import os, glob, argparse, re
|
||||
|
||||
|
||||
def parse():
|
||||
@ -12,28 +8,34 @@ def parse():
|
||||
"creates segmented .txtp from a list of files obtained using wildcards"
|
||||
)
|
||||
epilog = (
|
||||
"examples:\n"
|
||||
"%(prog)s bgm_*.ogg\n"
|
||||
"- get all files that start with bgm_ and end with .ogg\n"
|
||||
"%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n"
|
||||
"- get all files that start with bgm_ and end with 2 chars plus any extension\n"
|
||||
"%(prog)s files/bgm_*_all.ogg -s\n"
|
||||
"- create single .txtp per every bgm_(something)_all.ogg inside files dir\n"
|
||||
"%(prog)s **/*.ogg -l\n"
|
||||
"- find all .ogg in all subdirs but list only\n"
|
||||
"%(prog)s files/*.ogg -f .+(a|all)[.]ogg$\n"
|
||||
"- find all .ogg in files except those that end with 'a.ogg' or 'all.ogg'\n"
|
||||
"%(prog)s files/*.ogg -f .+(00[01])[.]ogg$\n"
|
||||
"- find all .ogg in files that end with '0.ogg' or '1.ogg'\n"
|
||||
'examples:\n'
|
||||
'%(prog)s bgm_*.ogg\n'
|
||||
'- get all files that start with bgm_ and end with .ogg\n'
|
||||
'%(prog)s bgm_??.* -n bgm_main.txtp -cla\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'
|
||||
'- create single .txtp per every bgm_(something)_all.ogg inside files dir\n'
|
||||
'%(prog)s **/*.ogg -l\n'
|
||||
'- list results only\n'
|
||||
'%(prog)s files/*.ogg -fi .+(00[01])[.]ogg$\n'
|
||||
'- find all .ogg in files including those that end with 0.ogg or 1.ogg\n'
|
||||
'%(prog)s files/*.ogg -fe .+(a|all)[.]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.add_argument("files", help="files to match")
|
||||
parser.add_argument("-n","--name", help="generated txtp name (adapts 'files' by default)")
|
||||
parser.add_argument("-f","--filter", help="filter matched files with regex and keep rest")
|
||||
parser.add_argument("-i","--include", help="include matched files with regex and ignore rest")
|
||||
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("-fi","--filter-include", help="include files matched 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("-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("-cle","--command-loop-end", help="sets loop end segment")
|
||||
parser.add_argument("-cv","--command-volume", help="sets volume")
|
||||
@ -42,35 +44,36 @@ def parse():
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def is_file_ok(args, glob_file):
|
||||
if not os.path.isfile(glob_file):
|
||||
def is_file_ok(args, file):
|
||||
if not os.path.isfile(file):
|
||||
return False
|
||||
|
||||
if glob_file.endswith(".py"):
|
||||
if file.endswith(".py"):
|
||||
return False
|
||||
|
||||
if args.filter:
|
||||
filename_test = os.path.basename(glob_file)
|
||||
p = re.compile(args.filter)
|
||||
if p.match(filename_test) != None:
|
||||
file_test = os.path.basename(file)
|
||||
if args.filter_exclude:
|
||||
if args.p_exclude.match(file_test) != None:
|
||||
return False
|
||||
|
||||
if args.include:
|
||||
filename_test = os.path.basename(glob_file)
|
||||
p = re.compile(args.include)
|
||||
if p.match(filename_test) == None:
|
||||
if args.filter_include:
|
||||
if args.p_include.match(file_test) == None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_txtp_name(args, segment):
|
||||
def get_txtp_name(args, file):
|
||||
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
|
||||
|
||||
elif args.single:
|
||||
txtp_name = os.path.splitext(os.path.basename(segment))[0]
|
||||
txtp_name = os.path.splitext(os.path.basename(file))[0]
|
||||
|
||||
else:
|
||||
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('_'):
|
||||
txtp_name = txtp_name[:-1]
|
||||
if txtp_name == '':
|
||||
txtp_name = 'bgm'
|
||||
|
||||
if not txtp_name:
|
||||
txtp_name = 'bgm'
|
||||
|
||||
if not txtp_name.endswith(".txtp"):
|
||||
txtp_name += ".txtp"
|
||||
|
||||
return txtp_name
|
||||
|
||||
def main():
|
||||
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
|
||||
glob_files = glob.glob(args.files)
|
||||
files = glob.glob(args.files)
|
||||
|
||||
# process matches and add to output list
|
||||
files = []
|
||||
segments = []
|
||||
for glob_file in glob_files:
|
||||
if not is_file_ok(args, glob_file):
|
||||
txtps = {}
|
||||
for file in files:
|
||||
if not is_file_ok(args, file):
|
||||
continue
|
||||
|
||||
if args.single:
|
||||
name = get_txtp_name(args, glob_file)
|
||||
segments = [glob_file]
|
||||
files.append( (name,segments) )
|
||||
name = get_txtp_name(args, file)
|
||||
if not name in txtps:
|
||||
txtps[name] = []
|
||||
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")
|
||||
exit()
|
||||
|
||||
|
||||
# list info
|
||||
for name, segments in files:
|
||||
for name, segments in txtps.items():
|
||||
print("file: " + name)
|
||||
for segment in segments:
|
||||
print(" " + segment)
|
||||
@ -127,10 +130,17 @@ def main():
|
||||
exit()
|
||||
|
||||
# write resulting files
|
||||
for name, segments in files:
|
||||
for name, segments in txtps.items():
|
||||
len_segments = len(segments)
|
||||
with open(name,"w+") as ftxtp:
|
||||
for segment in segments:
|
||||
ftxtp.write(segment + "\n")
|
||||
for i, segment in enumerate(segments):
|
||||
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:
|
||||
ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n")
|
||||
if args.command_loop_end:
|
||||
|
@ -125,66 +125,6 @@ static int record_interrupt(void) {
|
||||
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
|
||||
*/
|
||||
@ -657,6 +597,70 @@ static void add_driver_option(const char *key_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 error = 0;
|
||||
int opt;
|
||||
@ -673,7 +677,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (argc == 1) {
|
||||
/* We were invoked with no arguments */
|
||||
usage(argv[0]);
|
||||
usage(argv[0], 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -683,7 +687,7 @@ again_opts:
|
||||
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) {
|
||||
case 1:
|
||||
/* glibc getopt extension
|
||||
@ -701,20 +705,20 @@ again_opts:
|
||||
}
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
case 'd':
|
||||
cfg.fade_delay = atof(optarg);
|
||||
break;
|
||||
case 'F':
|
||||
case 'f':
|
||||
cfg.fade_time = atof(optarg);
|
||||
break;
|
||||
case 'L':
|
||||
case 'l':
|
||||
cfg.loop_count = atof(optarg);
|
||||
break;
|
||||
case 'M':
|
||||
cfg.min_time = atof(optarg);
|
||||
cfg.loop_count = -1.0;
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
cfg.stream_index = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
@ -726,15 +730,15 @@ again_opts:
|
||||
case 'E':
|
||||
cfg.really_force_loop = 1;
|
||||
break;
|
||||
case 'p':
|
||||
case 'c':
|
||||
cfg.play_forever = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
case 'B':
|
||||
if (!buffer)
|
||||
buffer_size_kb = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
driver_id = ao_driver_id(optarg);
|
||||
if (driver_id < 0) {
|
||||
fprintf(stderr, "Invalid output driver \"%s\"\n", optarg);
|
||||
@ -742,19 +746,19 @@ again_opts:
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
case 'o':
|
||||
out_filename = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
usage(argv[0], 1);
|
||||
goto done;
|
||||
case 'o':
|
||||
case 'P':
|
||||
add_driver_option(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
repeat = 1;
|
||||
break;
|
||||
case 'v':
|
||||
case 'm':
|
||||
verbose = 1;
|
||||
break;
|
||||
default:
|
||||
|
@ -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 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"
|
||||
"Options:\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"
|
||||
" -I: print requested file info as JSON\n"
|
||||
#endif
|
||||
, name);
|
||||
if (!is_full)
|
||||
, progname);
|
||||
if (!is_help)
|
||||
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"
|
||||
" -x: decode and print adxencd command line to encode as ADX\n"
|
||||
" -g: decode and print oggenc command line to encode as OGG\n"
|
||||
|
119
cli/vrts.bat
119
cli/vrts.bat
@ -10,9 +10,7 @@ REM # extensions found unless specified (except a few).
|
||||
REM #
|
||||
REM # Options: see below.
|
||||
REM #-------------------------------------------------------------------------
|
||||
REM #TODO: escape & ! % in file/folder names
|
||||
|
||||
setlocal enableDelayedExpansion
|
||||
|
||||
REM #-------------------------------------------------------------------------
|
||||
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 # -Pmo: performance test old (only parse meta)
|
||||
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
|
||||
|
||||
|
||||
@ -60,9 +59,9 @@ goto set_options
|
||||
:end_options
|
||||
|
||||
REM # output color defs
|
||||
set C_W=0e
|
||||
set C_E=0c
|
||||
set C_O=0f
|
||||
set COLOR_WN=0e
|
||||
set COLOR_ER=0c
|
||||
set COLOR_OK=0f
|
||||
|
||||
REM # remove command options and possibly "
|
||||
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 (
|
||||
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%" == "" (
|
||||
call :process_file "!CMD_FILE!"
|
||||
) else (
|
||||
call :performance_file "!CMD_FILE!"
|
||||
)
|
||||
|
||||
endlocal
|
||||
|
||||
)
|
||||
|
||||
REM # find time elapsed
|
||||
@ -131,58 +136,59 @@ REM # ########################################################################
|
||||
set CMD_SHORTNAME=%~n1
|
||||
if "%CMD_SHORTNAME%" == "" goto process_file_continue
|
||||
|
||||
REM # get file
|
||||
set CMD_FILE=%1
|
||||
set CMD_FILE=%CMD_FILE:"=%
|
||||
REM echo VTRS: file %CMD_FILE%
|
||||
|
||||
REM # get file (not from arg %1)
|
||||
set CMD_FILE=!CMD_FILE!
|
||||
set CMD_FILE=!CMD_FILE:"=!
|
||||
REM echo VTRS: file !CMD_FILE!
|
||||
|
||||
REM # old/new temp output
|
||||
set WAV_OLD=%CMD_FILE%.old.wav
|
||||
set TXT_OLD=%CMD_FILE%.old.txt
|
||||
set CMD_VGM_OLD="%OP_CMD_OLD%" -o "%WAV_OLD%" "%CMD_FILE%"
|
||||
%CMD_VGM_OLD% 1> "%TXT_OLD%" 2>&1 & REM || goto error
|
||||
set WAV_OLD=!CMD_FILE!.old.wav
|
||||
set TXT_OLD=!CMD_FILE!.old.txt
|
||||
set CMD_VGM_OLD="%OP_CMD_OLD%" -o "!WAV_OLD!" "!CMD_FILE!"
|
||||
!CMD_VGM_OLD! 1> "!TXT_OLD!" 2>&1 & REM || goto error
|
||||
|
||||
set WAV_NEW=%CMD_FILE%.new.wav
|
||||
set TXT_NEW=%CMD_FILE%.new.txt
|
||||
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%"
|
||||
%CMD_VGM_NEW% 1> "%TXT_NEW%" 2>&1 & REM || goto error
|
||||
set WAV_NEW=!CMD_FILE!.new.wav
|
||||
set TXT_NEW=!CMD_FILE!.new.txt
|
||||
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "!WAV_NEW!" "!CMD_FILE!"
|
||||
!CMD_VGM_NEW! 1> "!TXT_NEW!" 2>&1 & REM || goto error
|
||||
|
||||
REM # ignore if no files are created (unsupported formats)
|
||||
if not exist "%WAV_NEW%" (
|
||||
if not exist "%WAV_OLD%" (
|
||||
REM echo VRTS: nothing created for file %CMD_FILE%
|
||||
if exist "%TXT_NEW%" del /a:a "%TXT_NEW%"
|
||||
if exist "%TXT_OLD%" del /a:a "%TXT_OLD%"
|
||||
if not exist "!WAV_NEW!" (
|
||||
if not exist "!WAV_OLD!" (
|
||||
REM echo VRTS: nothing created for file !CMD_FILE!
|
||||
if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
|
||||
if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
|
||||
goto process_file_continue
|
||||
)
|
||||
)
|
||||
|
||||
REM # compare files (without /b may to be faster for small files?)
|
||||
set CMP_WAV=%OP_CMD_FC% "%WAV_OLD%" "%WAV_NEW%"
|
||||
set CMP_TXT=%OP_CMD_FC% "%TXT_OLD%" "%TXT_NEW%"
|
||||
set CMP_WAV=%OP_CMD_FC% "!WAV_OLD!" "!WAV_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
|
||||
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
|
||||
if %ERRORLEVEL% NEQ 0 set CMP_TXT_ERROR=1
|
||||
|
||||
REM # print output
|
||||
if %CMP_WAV_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 (
|
||||
call :echo_color %C_E% "%CMD_FILE%" "wav diffs"
|
||||
call :echo_color %COLOR_ER% "!CMD_FILE!" "wav diffs"
|
||||
)
|
||||
set /a "FILES_KO+=1"
|
||||
) else (
|
||||
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 (
|
||||
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"
|
||||
@ -190,10 +196,10 @@ REM # ########################################################################
|
||||
|
||||
REM # delete temp files
|
||||
if "%OP_NODELETE%" == "" (
|
||||
if exist "%WAV_OLD%" del /a:a "%WAV_OLD%"
|
||||
if exist "%TXT_OLD%" del /a:a "%TXT_OLD%"
|
||||
if exist "%WAV_NEW%" del /a:a "%WAV_NEW%"
|
||||
if exist "%TXT_NEW%" del /a:a "%TXT_NEW%"
|
||||
if exist "!WAV_OLD!" del /a:a "!WAV_OLD!"
|
||||
if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
|
||||
if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
|
||||
if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
|
||||
)
|
||||
|
||||
:process_file_continue
|
||||
@ -209,39 +215,39 @@ REM # ########################################################################
|
||||
set CMD_SHORTNAME=%~n1
|
||||
if "%CMD_SHORTNAME%" == "" goto performance_file_continue
|
||||
|
||||
REM # get file
|
||||
set CMD_FILE=%1
|
||||
set CMD_FILE=%CMD_FILE:"=%
|
||||
REM echo VTRS: file %CMD_FILE%
|
||||
REM # get file (not from arg %1)
|
||||
set CMD_FILE=!CMD_FILE!
|
||||
set CMD_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" (
|
||||
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" (
|
||||
set CMD_VGM="%OP_CMD_NEW%" -O "%CMD_FILE%"
|
||||
set CMD_VGM="%OP_CMD_NEW%" -O "!CMD_FILE!"
|
||||
)
|
||||
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" (
|
||||
set CMD_VGM="%OP_CMD_OLD%" -O "%CMD_FILE%"
|
||||
set CMD_VGM="%OP_CMD_OLD%" -O "!CMD_FILE!"
|
||||
)
|
||||
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" (
|
||||
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
|
||||
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
|
||||
if exist "%WAV_NEW%" del /a:a "%WAV_NEW%"
|
||||
if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
|
||||
|
||||
:performance_file_continue
|
||||
exit /B
|
||||
@ -252,14 +258,17 @@ REM # ########################################################################
|
||||
REM # hack to get colored output in Windows CMD using findstr + temp file
|
||||
REM # ########################################################################
|
||||
:echo_color
|
||||
set TEMP_FILE=%2-result
|
||||
set TEMP_FILE=%TEMP_FILE:"=%
|
||||
REM # don't use %2 but !CMD_FILE!
|
||||
set TEMP_FILE=!CMD_FILE!-result
|
||||
set TEMP_FILE=!TEMP_FILE:"=!
|
||||
set TEMP_TEXT=%3
|
||||
set TEMP_TEXT=%TEMP_TEXT:"=%
|
||||
echo %TEMP_TEXT% > "%TEMP_FILE%"
|
||||
set TEMP_TEXT=!TEMP_TEXT:"=!
|
||||
echo !TEMP_TEXT! > "!TEMP_FILE!"
|
||||
|
||||
REM # show colored filename + any text in temp file
|
||||
findstr /v /a:%1 /r "^$" "%TEMP_FILE%" nul
|
||||
del "%TEMP_FILE%"
|
||||
findstr /a:%1 /r ".*" "!TEMP_FILE!" nul
|
||||
del "!TEMP_FILE!"
|
||||
|
||||
exit /B
|
||||
REM :echo_color end, continue from last call
|
||||
|
||||
|
229
doc/BUILD.md
229
doc/BUILD.md
@ -3,32 +3,33 @@ This document explains how to build each of vgmstream's components and libraries
|
||||
|
||||
|
||||
## 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)
|
||||
- **Linux**: [Cmake](#cmake-builds) + [GCC](#gcc--make-compiler)
|
||||
- **macOS**: [Cmake](#cmake-builds) + [Clang](#clang-compiler)
|
||||
- **Web**: [Cmake](#cmake-builds) + [Emscripten](#emscripten-compiler)
|
||||
- **Linux**: [CMake](#cmake-builds) + [GCC](#gcc--make-compiler)
|
||||
- **macOS**: [CMake](#cmake-builds) + [Clang](#clang-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).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### 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/
|
||||
## Quick guide
|
||||
|
||||
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
|
||||
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 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 cmake
|
||||
# optional: for vgmstream 123 and audacious
|
||||
sudo apt-get install -y libao-dev audacious-dev
|
||||
|
||||
git clone https://github.com/vgmstream/vgmstream
|
||||
cd vgmstream
|
||||
@ -38,13 +39,108 @@ cd build
|
||||
cmake ..
|
||||
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.
|
||||
|
||||
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)
|
||||
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:
|
||||
```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
|
||||
$toolset = "142"
|
||||
$sdk = "10.0"
|
||||
@ -81,6 +177,22 @@ chmod +x version-get.sh version-make.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)
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
### 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)
|
||||
Code version control for development. Optional, used to auto-generate version numbers:
|
||||
- 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.
|
||||
|
||||
### 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).
|
||||
|
||||
@ -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).
|
||||
|
||||
*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/
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# CMake Build Instructions
|
||||
# CMake build help
|
||||
|
||||
## Build requirements
|
||||
|
||||
|
56
doc/TXTH.md
56
doc/TXTH.md
@ -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.
|
||||
|
||||
@ -28,7 +28,7 @@ Also check the [examples](#examples) section for some quick recipes, of varying
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@ -74,7 +74,7 @@ as explained below, but often will use default values. Accepted codec strings:
|
||||
# * For many XBOX games, and some PC games
|
||||
# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo)
|
||||
# - 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
|
||||
# * Must set decoding coefficients (coef_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)
|
||||
# * Interleave is multiple of 0x2 (default)
|
||||
# - 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
|
||||
# * For some games (usually on PC)
|
||||
# * 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
|
||||
# * For some PS2 and PS3 games
|
||||
# * 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
|
||||
# * For many PSP games and rare PS3 games
|
||||
# * Interleave (frame size) can be: [required]
|
||||
# Mono: 0x0118|0178|0230|02E8
|
||||
# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800
|
||||
# 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
|
||||
# * For early X360 games
|
||||
# - 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
|
||||
# * Variation with modified encoding
|
||||
# - 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
|
||||
# * 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
|
||||
# * Should set skip_samples (typically 1024 but varies, 2112 is also common)
|
||||
# - TGC Tiger Game.com 4-bit ADPCM
|
||||
# * For Tiger Game.com
|
||||
# * For Tiger Game.com games
|
||||
# - ASF Argonaut ASF ADPCM
|
||||
# * For rare Argonaut games [Croc (SAT)]
|
||||
# - EAXA Electronic Arts EA-XA ADPCM
|
||||
# * 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)
|
||||
```
|
||||
|
||||
@ -408,28 +410,42 @@ subfile_extension = (string)
|
||||
```
|
||||
|
||||
#### 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).
|
||||
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.
|
||||
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" 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_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_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_number = (value)
|
||||
chunk_start = (value)
|
||||
chunk_size = (value)
|
||||
|
||||
chunk_number = (value)
|
||||
chunk_header_size = (value)
|
||||
chunk_data_size = (value)
|
||||
chunk_size = (value)
|
||||
|
||||
chunk_value = (value)
|
||||
chunk_size_offset = (value)
|
||||
chunk_endian = LE|BE
|
||||
```
|
||||
|
||||
#### NAME TABLE
|
||||
|
@ -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.
|
||||
|
||||
|
22
doc/USAGE.md
22
doc/USAGE.md
@ -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**
|
||||
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
|
||||
using *TXTP* instead (explained below).
|
||||
XMPlay cannot support vgmstream's type of mixed subsongs due to player limitations
|
||||
(with neither *xmp-vgmstream* nor *in_vgmstream* plugins), try using *TXTP* instead (explained below).
|
||||
|
||||
|
||||
### Audacious plugin
|
||||
@ -148,12 +148,16 @@ handling.
|
||||
|
||||
### Subsongs
|
||||
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:
|
||||
`text.exe -s 5 file.bank`.
|
||||
By default vgmstream plays first subsong and reports total subsongs, if the format
|
||||
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
|
||||
subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong
|
||||
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_?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
|
||||
support, you can create multiple .txtp (explained below) to select one of the subsongs (like
|
||||
`bgm.sxd#10.txtp`).
|
||||
For other players without support, or to play only a few choice subsongs, you
|
||||
can create multiple `.txtp` (explained later) to select one, like `bgm.sxd#10.txtp`
|
||||
(plays subsong 10 in `bgm.sxd`).
|
||||
|
||||
You can use this python script to autogenerate one `.txtp` per subsong:
|
||||
https://github.com/vgmstream/vgmstream/tree/master/cli/tools/txtp_maker.py
|
||||
|
@ -2,10 +2,16 @@
|
||||
|
||||
# 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 libao-dev audacious-dev libjansson-dev
|
||||
sudo apt-get install libvorbis-dev libmpg123-dev libspeex-dev libavformat-dev libavcodec-dev libavutil-dev libswresample-dev
|
||||
# optional: for extra formats (can be ommited to build with static libs)
|
||||
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
|
||||
cd build
|
||||
|
1
msvc-build-package.bat
Normal file
1
msvc-build-package.bat
Normal file
@ -0,0 +1 @@
|
||||
powershell -ExecutionPolicy Bypass -NoProfile -File .\msvc-build.ps1 Package
|
@ -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) {
|
||||
STREAMFILE* sf_setup = NULL;
|
||||
|
||||
/* get from artificial external file (used if compiled without codebooks) */
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char *path;
|
||||
char setupname[0x20];
|
||||
|
||||
/* read "(dir/).fvs_{setup_id}" */
|
||||
sf->get_name(sf,pathname,sizeof(pathname));
|
||||
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);
|
||||
snprintf(setupname, sizeof(setupname), ".fvs_%08x", setup_id);
|
||||
sf_setup = open_streamfile_by_filename(sf, setupname);
|
||||
}
|
||||
|
||||
/* get codebook and copy to buffer */
|
||||
if (sf_setup) {
|
||||
/* file found, get contents into the buffer */
|
||||
size_t bytes = sf_setup->get_size(sf_setup);
|
||||
if (bytes > bufsize) goto fail;
|
||||
|
||||
if (read_streamfile(buf, 0, bytes, sf_setup) != bytes)
|
||||
goto fail;
|
||||
|
||||
sf_setup->close(sf_setup);
|
||||
close_streamfile(sf_setup);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (sf_setup) sf_setup->close(sf_setup);
|
||||
close_streamfile(sf_setup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) {
|
||||
STREAMFILE* sf_setup = NULL;
|
||||
|
||||
/* from to get from artificial external file (used if compiled without codebooks) */
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char* path;
|
||||
char setupname[0x20];
|
||||
|
||||
/* read "(dir/).fvs" */
|
||||
sf->get_name(sf,pathname,sizeof(pathname));
|
||||
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);
|
||||
snprintf(setupname, sizeof(setupname), ".fvs");
|
||||
sf_setup = open_streamfile_by_filename(sf, setupname);
|
||||
}
|
||||
|
||||
/* find codebook in mini-header (format by bnnm, feel free to change) */
|
||||
if (sf_setup) {
|
||||
/* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */
|
||||
int entries, i;
|
||||
uint32_t offset = 0, size = 0;
|
||||
|
||||
if (read_32bitBE(0x0, sf_setup) != 0x56465653) goto fail; /* "VFVS" */
|
||||
entries = read_32bitLE(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */
|
||||
if (!is_id32be(0x00, sf_setup, "VFVS"))
|
||||
goto fail;
|
||||
|
||||
entries = read_u32le(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */
|
||||
if (entries <= 0) goto fail;
|
||||
|
||||
for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */
|
||||
if ((uint32_t)read_32bitLE(0x20 + i*0x10, sf_setup) == setup_id) {
|
||||
offset = read_32bitLE(0x24 + i*0x10, sf_setup);
|
||||
size = read_32bitLE(0x28 + i*0x10, sf_setup);
|
||||
for (i = 0; i < entries; i++) { /* entry = id, offset, size, reserved */
|
||||
if (read_u32le(0x20 + i*0x10, sf_setup) == setup_id) {
|
||||
offset = read_u32le(0x24 + i*0x10, sf_setup);
|
||||
size = read_u32le(0x28 + i*0x10, sf_setup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
STREAMFILE* sfWvc = NULL;
|
||||
STREAMFILE* sf_setup = NULL;
|
||||
size_t wvc_size = 0;
|
||||
|
||||
/* get from artificial external file (used if compiled without codebooks) */
|
||||
{
|
||||
char setupname[PATH_LIMIT];
|
||||
char pathname[PATH_LIMIT];
|
||||
char *path;
|
||||
char setupname[0x20];
|
||||
|
||||
/* read "(dir/).wvc" */
|
||||
sf->get_name(sf,pathname,sizeof(pathname));
|
||||
path = strrchr(pathname,DIR_SEPARATOR);
|
||||
if (path)
|
||||
*(path+1) = '\0';
|
||||
else
|
||||
pathname[0] = '\0';
|
||||
snprintf(setupname, sizeof(setupname), ".wvc");
|
||||
sf_setup = open_streamfile_by_filename(sf, setupname);
|
||||
if (!sf_setup) goto fail;
|
||||
|
||||
snprintf(setupname,PATH_LIMIT,"%s.wvc", pathname);
|
||||
sfWvc = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!sfWvc) goto fail;
|
||||
|
||||
wvc_size = sfWvc->get_size(sfWvc);
|
||||
wvc_size = get_streamfile_size(sf_setup);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
if (table_start > wvc_size || codebook_id >= codebook_count) goto fail;
|
||||
|
||||
codebook_offset = read_u32le(table_start + codebook_id*4, sfWvc);
|
||||
codebook_size = read_u32le(table_start + codebook_id*4 + 4, sfWvc) - codebook_offset;
|
||||
codebook_offset = read_u32le(table_start + codebook_id*4, sf_setup);
|
||||
codebook_size = read_u32le(table_start + codebook_id*4 + 4, sf_setup) - codebook_offset;
|
||||
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;
|
||||
sfWvc->close(sfWvc);
|
||||
|
||||
close_streamfile(sf_setup);
|
||||
return codebook_size;
|
||||
}
|
||||
|
||||
|
||||
fail:
|
||||
if (sfWvc) sfWvc->close(sfWvc);
|
||||
close_streamfile(sf_setup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -6,13 +6,20 @@
|
||||
// May be implemented like the SNES/SPC700 BRR.
|
||||
|
||||
/* 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 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 */
|
||||
static const int IK0[4] = { 0, -960, -1840, -1568 };
|
||||
static const int IK1[4] = { 0, 0, 832, 880 };
|
||||
static const int K0[4] = { 0, 60, 115, 98 };
|
||||
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.
|
||||
* 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 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) {
|
||||
@ -96,30 +106,34 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
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);
|
||||
|
||||
/* decode subframes */
|
||||
for (i = 0; i < subframes / channelspacing; i++) {
|
||||
#if XA_FLOAT
|
||||
float coef1, coef2;
|
||||
#else
|
||||
int32_t coef1, coef2;
|
||||
#endif
|
||||
uint8_t coef_index, shift_factor;
|
||||
|
||||
/* parse current subframe (sound unit)'s header (sound parameters) */
|
||||
sp_pos = is_xa8 ?
|
||||
i*channelspacing + channel:
|
||||
i*channelspacing + channel :
|
||||
0x04 + i*channelspacing + channel;
|
||||
coef_index = (frame[sp_pos] >> 4) & 0xf;
|
||||
shift_factor = (frame[sp_pos] >> 0) & 0xf;
|
||||
|
||||
/* 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)
|
||||
coef_index = 0; /* only 4 filters are used, rest is apparently 0 */
|
||||
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) */
|
||||
|
||||
coef1 = IK0[coef_index];
|
||||
coef2 = IK1[coef_index];
|
||||
coef1 = K0[coef_index];
|
||||
coef2 = K1[coef_index];
|
||||
|
||||
|
||||
/* 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 = sample << 4; /* scale for current IK */
|
||||
sample = sample - ((coef1*hist1 + coef2*hist2) >> 10);
|
||||
sample = sample << 4; /* scale for K */
|
||||
#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;
|
||||
hist1 = sample; /* must go before clamp, somehow */
|
||||
|
@ -567,6 +567,7 @@ static const char* extension_list[] = {
|
||||
"vsv",
|
||||
"vxn",
|
||||
|
||||
"w",
|
||||
"waa",
|
||||
"wac",
|
||||
"wad",
|
||||
@ -1118,7 +1119,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_XBOX_HLWAV, "Half-Life 2 .WAV header"},
|
||||
{meta_MYSPD, "U-Sing .MYSPD 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_DMSG, "Microsoft RIFF DMSG 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_CABELAS, "Cabelas games .DSP 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_XAU, "XPEC XAU header"},
|
||||
{meta_GH3_BAR, "Guitar Hero III Mobile .bar"},
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "layout.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
/* set up for the block at the given offset */
|
||||
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;
|
||||
uint32_t block_id;
|
||||
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 */
|
||||
@ -19,27 +20,29 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
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] */
|
||||
if (guess_endianness32bit(block_offset + 0x04, sf))
|
||||
block_size = read_32bitBE(block_offset + 0x04, sf);
|
||||
if (guess_endian32(block_offset + 0x04, sf))
|
||||
block_size = read_u32be(block_offset + 0x04, sf);
|
||||
else
|
||||
block_size = read_32bitLE(block_offset + 0x04, sf);
|
||||
block_size = read_u32le(block_offset + 0x04, sf);
|
||||
|
||||
block_header = 0;
|
||||
|
||||
if (block_id == 0x31534E68 || block_id == 0x53454144) { /* "1SNh" "SEAD" audio header */
|
||||
int is_sead = (block_id == 0x53454144);
|
||||
int is_eacs = read_32bitBE(block_offset + 0x08, sf) == 0x45414353;
|
||||
int is_zero = read_32bitBE(block_offset + 0x08, sf) == 0x00;
|
||||
if (block_id == get_id32be("1SNh") || block_id == get_id32be("SEAD")) { /* audio header */
|
||||
int is_sead = (block_id == get_id32be("SEAD"));
|
||||
int is_eacs = is_id32be(block_offset + 0x08, sf, "EACS");
|
||||
int is_zero = read_u32be(block_offset + 0x08, sf) == 0x00;
|
||||
|
||||
block_header = (is_eacs || is_zero) ? 0x28 : (is_sead ? 0x14 : 0x2c);
|
||||
if (block_header >= block_size) /* sometimes has audio data after header */
|
||||
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;
|
||||
} 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;
|
||||
return;
|
||||
}
|
||||
@ -72,20 +75,25 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
break;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_DVI_IMA:
|
||||
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++) {
|
||||
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_history1_32 = read_32bit(adpcm_offset + i*0x04 + 0x04*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_s32(adpcm_offset + i*0x04 + 0x04*vgmstream->channels, sf);
|
||||
vgmstream->ch[i].offset = adpcm_offset + 0x08*vgmstream->channels;
|
||||
}
|
||||
|
||||
|
@ -449,7 +449,8 @@
|
||||
<ClCompile Include="meta\ps2_adm.c" />
|
||||
<ClCompile Include="meta\ads.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\ps2_b1s.c" />
|
||||
<ClCompile Include="meta\ps2_bg00.c" />
|
||||
@ -470,7 +471,7 @@
|
||||
<ClCompile Include="meta\jstm.c" />
|
||||
<ClCompile Include="meta\ps2_kces.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\ps_headerless.c" />
|
||||
<ClCompile Include="meta\ps2_mic.c" />
|
||||
|
@ -829,7 +829,10 @@
|
||||
<ClCompile Include="meta\ps2_ass.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</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>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\aus.c">
|
||||
@ -892,7 +895,7 @@
|
||||
<ClCompile Include="meta\ps2_leg.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ps2_lpcm.c">
|
||||
<ClCompile Include="meta\lpcm_shade.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ps2_mcg.c">
|
||||
|
@ -80,14 +80,14 @@ VGMSTREAM* init_vgmstream_adx_subkey(STREAMFILE* sf, uint16_t subkey) {
|
||||
/* encryption */
|
||||
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");
|
||||
}
|
||||
coding_type = coding_CRI_ADX_enc_8;
|
||||
version = 0x0400;
|
||||
}
|
||||
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");
|
||||
}
|
||||
coding_type = coding_CRI_ADX_enc_9;
|
||||
|
55
src/meta/ast_mmv.c
Normal file
55
src/meta/ast_mmv.c
Normal 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
53
src/meta/ast_mv.c
Normal 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;
|
||||
}
|
@ -21,7 +21,7 @@ typedef struct _BARSTREAMFILE {
|
||||
/*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;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
#define EA_CODEC_PCM 0x00
|
||||
#define EA_CODEC_ULAW 0x01
|
||||
@ -17,7 +18,9 @@ typedef struct {
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int32_t loop_start_offset;
|
||||
int32_t data_offset;
|
||||
uint32_t data_offset;
|
||||
|
||||
uint32_t base_size;
|
||||
|
||||
int big_endian;
|
||||
int loop_flag;
|
||||
@ -27,7 +30,7 @@ typedef struct {
|
||||
int total_subsongs;
|
||||
} 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 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) */
|
||||
VGMSTREAM* init_vgmstream_ea_1snh(STREAMFILE* sf) {
|
||||
eacs_header ea = { 0 };
|
||||
off_t offset, eacs_offset;
|
||||
eacs_header ea = {0};
|
||||
off_t offset = 0x00, eacs_offset;
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
||||
|
||||
/* 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 */
|
||||
if (is_id32be(0x00, sf, "TGVk")) {
|
||||
offset = read_u32be(0x04, sf);
|
||||
@ -64,14 +55,28 @@ VGMSTREAM* init_vgmstream_ea_1snh(STREAMFILE* sf) {
|
||||
!is_id32be(offset + 0x00, sf, "SEAD"))
|
||||
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.
|
||||
* 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");
|
||||
|
||||
if (!ea.is_sead)
|
||||
ea.base_size = read_u32le(offset + 0x04, sf);
|
||||
|
||||
eacs_offset = offset + 0x08; /* after 1SNh block id+size */
|
||||
|
||||
if (!parse_header(sf, &ea, eacs_offset))
|
||||
goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_main(sf, &ea);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
@ -98,17 +103,19 @@ VGMSTREAM* init_vgmstream_ea_eacs(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* 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)]
|
||||
* .bnk: multi bank [Need for Speed (PC)] */
|
||||
if (!check_extensions(sf,"eas,bnk"))
|
||||
* .bnk: multi bank [Need for Speed (PC)]
|
||||
* .as4: single [NBA Live 96 (PC)] */
|
||||
if (!check_extensions(sf,"eas,bnk,as4"))
|
||||
goto fail;
|
||||
|
||||
/* plain data without blocks, can contain N*(EACS header) + N*(data), or N (EACS header + data) */
|
||||
ea.is_bank = 1;
|
||||
|
||||
/* use ??? as endian marker (Saturn = BE) */
|
||||
//ea.big_endian = guess_endianness32bit(0x04,sf);
|
||||
|
||||
if (is_id32be(0x00,sf, "EACS")) {
|
||||
/* single bank variant */
|
||||
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) goto fail;
|
||||
|
||||
/* offsets to EACSs are scattered in the first 0x200
|
||||
* this looks dumb but seems like the only way */
|
||||
/* offsets to EACSs are scattered in the first 0x200, then 0x28 info + EACS per subsong.
|
||||
* This looks dumb but seems like the only way. */
|
||||
eacs_offset = 0;
|
||||
for (i = 0x00; i < 0x200; i += 0x04) {
|
||||
off_t bank_offset = read_u32le(i, sf);
|
||||
@ -134,7 +141,7 @@ VGMSTREAM* init_vgmstream_ea_eacs(STREAMFILE* sf) {
|
||||
/* parse mini bank header */
|
||||
if (ea.total_subsongs == target_subsong) {
|
||||
/* 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"))
|
||||
goto fail;
|
||||
/* 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) */
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->codec_config = ea->codec_config;
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -210,13 +218,13 @@ fail:
|
||||
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 */
|
||||
int32_t (*read_s32)(off_t,STREAMFILE*);
|
||||
read_s32_t read_s32;
|
||||
|
||||
if (is_id32be(offset+0x00,sf, "EACS")) {
|
||||
/* 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;
|
||||
|
||||
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);
|
||||
/* 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 */
|
||||
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;
|
||||
|
||||
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)
|
||||
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);
|
||||
|
||||
@ -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) {
|
||||
int32_t num_samples = 0, block_id;
|
||||
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;
|
||||
|
||||
file_size = get_streamfile_size(sf);
|
||||
vgmstream->next_block_offset = ea->data_offset;
|
||||
|
||||
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)
|
||||
break;
|
||||
|
||||
block_id = read_u32be(vgmstream->current_block_offset, sf);
|
||||
|
||||
if (find_loop) {
|
||||
if (vgmstream->current_block_offset == ea->loop_start_offset) {
|
||||
ea->loop_start = num_samples;
|
||||
ea->loop_flag = 1;
|
||||
block_update_ea_1snh(ea->data_offset, vgmstream);
|
||||
block_update(ea->data_offset, vgmstream);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (block_id == get_id32be("1SNl") ) { /* loop point found */
|
||||
ea->loop_start_offset = read_s32(vgmstream->current_block_offset + 0x08, sf);
|
||||
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;
|
||||
|
||||
/* reset once we're done */
|
||||
block_update_ea_1snh(ea->data_offset, vgmstream);
|
||||
block_update(ea->data_offset, vgmstream);
|
||||
|
||||
if (loop_end_found) {
|
||||
/* 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) {
|
||||
off_t block_offset = start_offset;
|
||||
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) {
|
||||
uint32_t id = read_u32be(block_offset+0x00,sf);
|
||||
size_t block_size;
|
||||
|
||||
/* 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);
|
||||
else
|
||||
block_size = read_u32le(block_offset + 0x04, sf);
|
||||
|
||||
if (id == 0x31534E64 || id == 0x534E4443) { /* "1SNd" "SNDC" audio data */
|
||||
size_t ima_samples = read_s32(block_offset + 0x08, sf);
|
||||
size_t expected_samples = (block_size - 0x08 - 0x04 - 0x08*ea->channels) * 2 / ea->channels;
|
||||
if (block_size == 0 || block_size == -1)
|
||||
break;
|
||||
|
||||
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) {
|
||||
return 1; /* has ADPCM hist (hopefully) */
|
||||
|
@ -5,43 +5,47 @@
|
||||
/* Possibly the same as EA_CODEC_x in variable SCHl */
|
||||
#define EA_CODEC_PCM 0x00
|
||||
#define EA_CODEC_IMA 0x02
|
||||
#define EA_CODEC_PSX 0x06
|
||||
|
||||
typedef struct {
|
||||
int8_t version;
|
||||
int8_t bps;
|
||||
int8_t channels;
|
||||
int8_t codec;
|
||||
int16_t sample_rate;
|
||||
int sample_rate;
|
||||
int32_t num_samples;
|
||||
|
||||
int big_endian;
|
||||
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) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
/* EA SCHl with fixed header - from EA games (~1997?) */
|
||||
VGMSTREAM* init_vgmstream_ea_schl_fixed(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
size_t header_size;
|
||||
ea_fixed_header ea = {0};
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .asf: original
|
||||
* .lasf: fake for plugins */
|
||||
if (!check_extensions(streamFile,"asf,lasf"))
|
||||
if (!is_id32be(0x00,sf, "SCHl"))
|
||||
goto fail;
|
||||
|
||||
/* check header (see ea_schl.c for more info about blocks) */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x5343486C) /* "SCHl" */
|
||||
/* .asf: original [NHK 97 (PC)]
|
||||
* .lasf: fake for plugins
|
||||
* .cnk: ps1 [NBA Live 97 (PS1)] */
|
||||
if (!check_extensions(sf,"asf,lasf,cnk"))
|
||||
goto fail;
|
||||
|
||||
header_size = read_32bitLE(0x04,streamFile);
|
||||
/* see ea_schl.c for more info about blocks */
|
||||
//TODO: handle SCCl? [NBA Live 97 (PS1)]
|
||||
|
||||
if (!parse_fixed_header(streamFile,&ea, 0x08))
|
||||
header_size = read_u32le(0x04,sf);
|
||||
|
||||
if (!parse_fixed_header(sf, &ea))
|
||||
goto fail;
|
||||
|
||||
start_offset = header_size;
|
||||
@ -59,7 +63,6 @@ VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE *streamFile) {
|
||||
vgmstream->codec_endian = ea.big_endian;
|
||||
|
||||
vgmstream->meta_type = meta_EA_SCHL_fixed;
|
||||
|
||||
vgmstream->layout_type = layout_blocked_ea_schl;
|
||||
|
||||
switch (ea.codec) {
|
||||
@ -71,13 +74,17 @@ VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE *streamFile) {
|
||||
vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */
|
||||
break;
|
||||
|
||||
case EA_CODEC_PSX:
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("EA: unknown codec 0x%02x\n", ea.codec);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -87,32 +94,62 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
static int parse_fixed_header(STREAMFILE* streamFile, ea_fixed_header* ea, off_t begin_offset) {
|
||||
off_t offset = begin_offset;
|
||||
static int parse_fixed_header(STREAMFILE* sf, ea_fixed_header* ea) {
|
||||
uint32_t offset = 0x00, size = 0;
|
||||
|
||||
if (read_32bitBE(offset+0x00, streamFile) != 0x5041546C && /* "PATl" */
|
||||
read_32bitBE(offset+0x38, streamFile) != 0x544D706C) /* "TMpl" */
|
||||
if (is_id32be(offset+0x08, sf, "PATl"))
|
||||
offset = 0x08;
|
||||
else if (is_id32be(offset+0x0c, sf, "PATl"))
|
||||
offset = 0x0c; /* extra field in PS1 */
|
||||
else
|
||||
goto fail;
|
||||
|
||||
offset += 0x3c; /* after TMpl */
|
||||
ea->version = read_8bit(offset+0x00, streamFile);
|
||||
ea->bps = read_8bit(offset+0x01, streamFile);
|
||||
ea->channels = read_8bit(offset+0x02, streamFile);
|
||||
ea->codec = read_8bit(offset+0x03, streamFile);
|
||||
VGM_ASSERT(read_16bitLE(offset+0x04, streamFile) != 0, "EA SCHl fixed: unknown1 found\n");
|
||||
/* 0x04(16): unknown */
|
||||
ea->sample_rate = (uint16_t)read_16bitLE(offset+0x06, streamFile);
|
||||
ea->num_samples = read_32bitLE(offset+0x08, streamFile);
|
||||
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? */
|
||||
VGM_ASSERT(read_32bitLE(offset+0x14, streamFile) != 0, "EA SCHl fixed: unknown4 found\n"); /* data start? */
|
||||
VGM_ASSERT(read_32bitLE(offset+0x18, streamFile) != -1, "EA SCHl fixed: unknown5 found\n");
|
||||
VGM_ASSERT(read_32bitLE(offset+0x1c, streamFile) != 0x7F, "EA SCHl fixed: unknown6 found\n");
|
||||
size = read_u32le(offset+0x34, sf);
|
||||
if (size == 0x20 && is_id32be(offset+0x38, sf, "TMpl")) { /* PC 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);
|
||||
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;
|
||||
}
|
||||
|
@ -3,7 +3,10 @@
|
||||
#include "../coding/coding.h"
|
||||
#include "../coding/hca_decoder_clhca.h"
|
||||
|
||||
#ifdef VGM_DEBUG_OUTPUT
|
||||
//#define HCA_BRUTEFORCE
|
||||
#endif
|
||||
|
||||
#ifdef HCA_BRUTEFORCE
|
||||
static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigned long long* p_keycode, uint16_t subkey);
|
||||
#endif
|
||||
|
@ -48,7 +48,7 @@ static const hcakey_info hcakey_list[] = {
|
||||
// - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita)
|
||||
{1234253142}, // 0000000049913556
|
||||
|
||||
// Idolm@ster Cinderella Stage (iOS/Android)
|
||||
// THE iDOLM@STER Cinderella Girls: Starlight Stage (iOS/Android)
|
||||
// Shadowverse (iOS/Android)
|
||||
{59751358413602}, // 00003657F27E3B22
|
||||
|
||||
@ -76,7 +76,8 @@ static const hcakey_info hcakey_list[] = {
|
||||
// Raramagi (iOS/Android)
|
||||
{45719322}, // 0000000002B99F1A
|
||||
|
||||
// Idolm@ster Million Live (iOS/Android)
|
||||
// THE iDOLM@STER Million Live! (iOS/Android)
|
||||
// THE iDOLM@STER SideM GROWING STARS (Android)
|
||||
{765765765765765}, // 0002B875BC731A85
|
||||
|
||||
// Kurokishi to Shiro no Maou (iOS/Android)
|
||||
|
123
src/meta/ikm.c
123
src/meta/ikm.c
@ -2,74 +2,50 @@
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* IKM - MiCROViSiON PS2 container [Zwei (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_ikm_ps2(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
static VGMSTREAM* init_vgmstream_ikm_ps2(STREAMFILE* sf) {
|
||||
VGMSTREAM* v = NULL;
|
||||
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"))
|
||||
goto fail;
|
||||
|
||||
loop_flag = (read_s32le(0x14, sf) > 0);
|
||||
channel_count = read_s32le(0x50, sf);
|
||||
channels = read_s32le(0x50, sf);
|
||||
start_offset = 0x800;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
v = allocate_vgmstream(channels, loop_flag);
|
||||
if (!v) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_IKM;
|
||||
vgmstream->sample_rate = read_s32le(0x44, sf);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(read_s32le(0x4c, sf), channel_count);
|
||||
vgmstream->loop_start_sample = read_s32le(0x14, sf);
|
||||
vgmstream->loop_end_sample = read_s32le(0x18, sf);
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10; /* @0x40 / channels */
|
||||
v->meta_type = meta_IKM;
|
||||
v->sample_rate = read_s32le(0x44, sf);
|
||||
v->num_samples = ps_bytes_to_samples(read_s32le(0x4c, sf), channels);
|
||||
v->loop_start_sample = read_s32le(0x14, sf);
|
||||
v->loop_end_sample = read_s32le(0x18, sf);
|
||||
v->coding_type = coding_PSX;
|
||||
v->layout_type = layout_interleave;
|
||||
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;
|
||||
return vgmstream;
|
||||
return v;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
close_vgmstream(v);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* IKM - MiCROViSiON PC container [Chaos Legion (PC), Legend of Galactic Heroes (PC)] */
|
||||
VGMSTREAM* init_vgmstream_ikm_pc(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
static VGMSTREAM* init_vgmstream_ikm_pc(STREAMFILE* sf) {
|
||||
VGMSTREAM* v = NULL;
|
||||
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 */
|
||||
if (is_id32be(0x30,sf, "OggS")) {
|
||||
if (is_id32be(0x30,sf, "OggS"))
|
||||
start_offset = 0x30; /* Chaos Legion (PC) */
|
||||
}
|
||||
else if (is_id32be(0x800,sf, "OggS")) {
|
||||
else
|
||||
start_offset = 0x800; /* Legend of Galactic Heroes (PC) */
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
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.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:
|
||||
close_vgmstream(vgmstream);
|
||||
close_vgmstream(v);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* IKM - MiCROViSiON PSP container [The Legend of Heroes: A Tear of Vermillion (PSP)] */
|
||||
VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
static VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) {
|
||||
VGMSTREAM* v = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t start_offset;
|
||||
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"))
|
||||
goto fail;
|
||||
|
||||
@ -116,16 +84,43 @@ VGMSTREAM* init_vgmstream_ikm_psp(STREAMFILE* sf) {
|
||||
temp_sf = setup_subfile_streamfile(sf, start_offset, data_size, "at3");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
v = init_vgmstream_riff(temp_sf);
|
||||
if (!v) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_IKM;
|
||||
v->meta_type = meta_IKM;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
return v;
|
||||
|
||||
fail:
|
||||
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;
|
||||
}
|
||||
|
@ -30,7 +30,13 @@ VGMSTREAM* init_vgmstream_lopu_fb(STREAMFILE* sf) {
|
||||
/* rest: null */
|
||||
|
||||
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;
|
||||
if (num_samples < loop_end)
|
||||
num_samples = loop_end;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
51
src/meta/lpcm_shade.c
Normal file
51
src/meta/lpcm_shade.c
Normal 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;
|
||||
}
|
@ -222,9 +222,7 @@ VGMSTREAM * init_vgmstream_leg(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_filp(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ikm_ps2(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_ikm_pc(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_ikm_psp(STREAMFILE * streamFile);
|
||||
VGMSTREAM* init_vgmstream_ikm(STREAMFILE* sf);
|
||||
|
||||
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_ps2_ast(STREAMFILE* streamFile);
|
||||
VGMSTREAM* init_vgmstream_ast_mv(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_ast_mmv(STREAMFILE* sf);
|
||||
|
||||
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_lpcm(STREAMFILE* streamFile);
|
||||
VGMSTREAM* init_vgmstream_lpcm_shade(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_dsp_bdsp(STREAMFILE* streamFile);
|
||||
|
||||
|
@ -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 */
|
||||
|
||||
/* '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) {
|
||||
/* maybe should give priority to external info? */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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_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->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->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
@ -124,6 +128,9 @@ VGMSTREAM* init_vgmstream_opus_std(STREAMFILE* sf) {
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if (read_u32le(0x00,sf) != 0x80000001) /* 'basic info' chunk */
|
||||
goto fail;
|
||||
|
||||
/* .opus: standard
|
||||
* .bgm: Cotton Reboot (Switch) */
|
||||
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;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"opus,lopus"))
|
||||
goto fail;
|
||||
if (!((read_u32be(0x04,sf) == 0x00000000 && read_u32be(0x0c,sf) == 0x00000000) ||
|
||||
(read_u32be(0x04,sf) == 0xFFFFFFFF && read_u32be(0x0c,sf) == 0xFFFFFFFF)))
|
||||
goto fail;
|
||||
if (!check_extensions(sf,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
num_samples = 0;
|
||||
@ -181,7 +188,7 @@ VGMSTREAM* init_vgmstream_opus_capcom(STREAMFILE* sf) {
|
||||
int channels;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(sf,"opus,lopus"))
|
||||
if (!check_extensions(sf,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
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;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"nop"))
|
||||
if (!is_id32be(0x00, sf, "sadf") ||
|
||||
!is_id32be(0x08, sf, "opus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, sf) != 0x73616466 || /* "sadf" */
|
||||
read_32bitBE(0x08, sf) != 0x6f707573) /* "opus" */
|
||||
if (!check_extensions(sf,"nop"))
|
||||
goto fail;
|
||||
|
||||
offset = read_32bitLE(0x1c, sf);
|
||||
@ -284,9 +291,9 @@ VGMSTREAM* init_vgmstream_opus_shinen(STREAMFILE* sf) {
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(sf,"opus,lopus"))
|
||||
if (read_u32be(0x08,sf) != 0x01000080)
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,sf) != 0x01000080)
|
||||
if ( !check_extensions(sf,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
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;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "OPUS"))
|
||||
goto fail;
|
||||
|
||||
/* .opus: header ID (they only exist inside .nus3bank) */
|
||||
if (!check_extensions(sf, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, sf) != 0x4F505553) /* "OPUS" */
|
||||
goto fail;
|
||||
|
||||
/* 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 */
|
||||
@ -337,13 +345,13 @@ VGMSTREAM* init_vgmstream_opus_sps_n1(STREAMFILE* sf) {
|
||||
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (read_u32be(0x00, sf) != 0x09000000) /* file type (see other N1 SPS) */
|
||||
goto fail;
|
||||
/* .sps: Labyrinth of Refrain: Coven of Dusk (Switch)
|
||||
* .nlsd: Disgaea Refine (Switch), Ys VIII (Switch)
|
||||
* .at9: void tRrLM(); //Void Terrarium (Switch) */
|
||||
if (!check_extensions(sf, "sps,nlsd,at9"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, sf) != 0x09000000) /* file type (see other N1 SPS) */
|
||||
goto fail;
|
||||
|
||||
num_samples = read_32bitLE(0x0C, sf);
|
||||
|
||||
@ -382,9 +390,9 @@ VGMSTREAM* init_vgmstream_opus_opusx(STREAMFILE* sf) {
|
||||
float modifier;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "opusx"))
|
||||
if (!is_id32be(0x00, sf, "OPUS"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, sf) != 0x4F505553) /* "OPUS" */
|
||||
if (!check_extensions(sf, "opusx"))
|
||||
goto fail;
|
||||
|
||||
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;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "OPUS"))
|
||||
goto fail;
|
||||
if (!check_extensions(sf, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, sf) != 0x4F505553 || /* "OPUS" */
|
||||
read_32bitBE(0x18, sf) != 0x01000080)
|
||||
if (read_32bitBE(0x18, sf) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
offset = 0x18;
|
||||
@ -441,9 +450,9 @@ VGMSTREAM* init_vgmstream_opus_opusnx(STREAMFILE* sf) {
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "opus,lopus"))
|
||||
if (!is_id64be(0x00, sf,"OPUSNX\0\0"))
|
||||
goto fail;
|
||||
if (read_64bitBE(0x00, sf) != 0x4F5055534E580000) /* "OPUSNX\0\0" */
|
||||
if (!check_extensions(sf, "opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
@ -462,9 +471,9 @@ VGMSTREAM* init_vgmstream_opus_nsopus(STREAMFILE* sf) {
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "nsopus"))
|
||||
if (!is_id32be(0x00, sf,"EWNO"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x45574E4F) /* "EWNO" */
|
||||
if (!check_extensions(sf, "nsopus"))
|
||||
goto fail;
|
||||
|
||||
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;
|
||||
|
||||
/* checks */
|
||||
/* .wav: default
|
||||
* .opus: fake? */
|
||||
if (!check_extensions(sf, "wav,lwav,opus,lopus"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x01000000)
|
||||
goto fail;
|
||||
|
||||
/* .wav: original */
|
||||
if (!check_extensions(sf, "wav,lwav"))
|
||||
goto fail;
|
||||
/* 0x04: channels */
|
||||
/* 0x08: data_size */
|
||||
offset = read_32bitLE(0x0C, sf);
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
146
src/meta/txth.c
146
src/meta/txth.c
@ -40,6 +40,7 @@ typedef enum {
|
||||
ASF = 30, /* Argonaut ASF 4-bit ADPCM */
|
||||
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) */
|
||||
XA = 33,
|
||||
|
||||
UNKNOWN = 99,
|
||||
} txth_codec_t;
|
||||
@ -117,6 +118,9 @@ typedef struct {
|
||||
uint32_t chunk_count;
|
||||
uint32_t chunk_header_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_size_set;
|
||||
int chunk_count_set;
|
||||
@ -256,6 +260,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
|
||||
case TGC: coding = coding_TGC; break;
|
||||
case ASF: coding = coding_ASF; break;
|
||||
case EAXA: coding = coding_EA_XA; break;
|
||||
case XA: coding = coding_XA; break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
@ -367,6 +372,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
|
||||
|
||||
case coding_OKI16:
|
||||
case coding_OKI4S:
|
||||
case coding_XA:
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
|
||||
@ -648,7 +654,7 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
|
||||
* - etc
|
||||
* 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);
|
||||
|
||||
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) {
|
||||
char basename[PATH_LIMIT];
|
||||
char filename[PATH_LIMIT];
|
||||
char fileext[PATH_LIMIT];
|
||||
const char *subext;
|
||||
const char* base_ext;
|
||||
const char* txth_ext;
|
||||
STREAMFILE* sf_text;
|
||||
|
||||
/* try "(path/)(name.ext).txth" */
|
||||
get_streamfile_name(sf,filename,PATH_LIMIT);
|
||||
if (strstr(filename, "subfile_txth") != NULL)
|
||||
|
||||
get_streamfile_name(sf, filename, sizeof(filename));
|
||||
if (strstr(filename, ".subfile_txth") != NULL)
|
||||
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" */
|
||||
get_streamfile_basename(sf,basename,PATH_LIMIT);
|
||||
subext = filename_extension(basename);
|
||||
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");
|
||||
base_ext = filename_extension(filename);
|
||||
concatn(sizeof(filename), filename, ".txth");
|
||||
txth_ext = filename_extension(filename);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* try "(path/)(.ext).txth" */
|
||||
get_streamfile_path(sf,filename,PATH_LIMIT);
|
||||
get_streamfile_ext(sf,fileext,PATH_LIMIT);
|
||||
strcat(filename,".");
|
||||
strcat(filename, fileext);
|
||||
strcat(filename, ".txth");
|
||||
sf_text = open_streamfile(sf,filename);
|
||||
if (sf_text) return sf_text;
|
||||
if (base_ext) {
|
||||
base_ext--; //get_streamfile_path(sf, filename, sizeof(filename));
|
||||
|
||||
sf_text = open_streamfile_by_filename(sf, base_ext);
|
||||
if (sf_text) return sf_text;
|
||||
}
|
||||
|
||||
/* try "(path/).txth" */
|
||||
get_streamfile_path(sf,filename,PATH_LIMIT);
|
||||
strcat(filename, ".txth");
|
||||
sf_text = open_streamfile(sf,filename);
|
||||
if (sf_text) return sf_text;
|
||||
if (txth_ext) {
|
||||
txth_ext--; /* points to "txth" due to the concat */
|
||||
|
||||
sf_text = open_streamfile_by_filename(sf, txth_ext);
|
||||
if (sf_text) return sf_text;
|
||||
}
|
||||
|
||||
/* not found */
|
||||
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?
|
||||
if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set)
|
||||
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;
|
||||
if (!txth->sf_body)
|
||||
return;
|
||||
@ -804,18 +805,22 @@ static void set_body_chunk(txth_header* txth) {
|
||||
{
|
||||
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_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_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);
|
||||
if (!temp_sf) return;
|
||||
}
|
||||
|
||||
|
||||
/* closing is handled by temp_sf */
|
||||
//if (txth->sf_body_opened) {
|
||||
// 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,"ASF")) return ASF;
|
||||
else if (is_string(val,"EAXA")) return EAXA;
|
||||
else if (is_string(val,"XA")) return XA;
|
||||
/* special handling */
|
||||
else if (is_string(val,"name_value")) 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
//;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;
|
||||
}
|
||||
else if (is_string(key,"coef_endianness")) {
|
||||
if (is_string(val, "BE"))
|
||||
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;
|
||||
if (!parse_be(txth, val, &txth->coef_big_endian)) goto fail;
|
||||
}
|
||||
else if (is_string(key,"coef_mode")) {
|
||||
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;
|
||||
}
|
||||
else if (is_string(key,"hist_endianness")) {
|
||||
if (is_string(val, "BE"))
|
||||
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;
|
||||
if (!parse_be(txth, val, &txth->hist_big_endian)) goto fail;
|
||||
}
|
||||
|
||||
/* SUBSONGS */
|
||||
@ -1336,34 +1347,41 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
|
||||
}
|
||||
|
||||
/* CHUNKS */
|
||||
else if (is_string(key,"chunk_number")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail;
|
||||
else if (is_string(key,"chunk_count")) {
|
||||
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")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_start)) goto fail;
|
||||
txth->chunk_start_set = 1;
|
||||
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")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size)) goto fail;
|
||||
txth->chunk_size_set = 1;
|
||||
set_body_chunk(txth);
|
||||
}
|
||||
else if (is_string(key,"chunk_count")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_count)) goto fail;
|
||||
txth->chunk_count_set = 1;
|
||||
set_body_chunk(txth);
|
||||
/* optional and should go before the above */
|
||||
else if (is_string(key,"chunk_number")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail;
|
||||
}
|
||||
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 */
|
||||
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);
|
||||
case EAXA:
|
||||
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 */
|
||||
case XMA1:
|
||||
|
@ -1,30 +1,36 @@
|
||||
#ifndef _TXTH_STREAMFILE_H_
|
||||
#define _TXTH_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t chunk_start;
|
||||
size_t chunk_size;
|
||||
size_t chunk_header_size;
|
||||
size_t chunk_data_size;
|
||||
uint32_t chunk_start;
|
||||
uint32_t chunk_size;
|
||||
uint32_t chunk_header_size;
|
||||
uint32_t chunk_data_size;
|
||||
|
||||
int chunk_count;
|
||||
int chunk_number;
|
||||
|
||||
uint32_t chunk_value;
|
||||
uint32_t chunk_size_offset;
|
||||
int chunk_be;
|
||||
} txth_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
txth_io_config_data cfg;
|
||||
size_t stream_size;
|
||||
uint32_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
uint32_t logical_offset; /* fake offset */
|
||||
uint32_t physical_offset; /* actual offset */
|
||||
uint32_t block_size; /* current size */
|
||||
uint32_t skip_size; /* size from block start to reach data */
|
||||
uint32_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
uint32_t logical_size;
|
||||
} 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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
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;
|
||||
|
122
src/meta/xa.c
122
src/meta/xa.c
@ -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_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* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
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;
|
||||
int total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
uint16_t target_config = 0;
|
||||
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .xa: common
|
||||
* .str: often videos and sometimes speech/music
|
||||
* .adp: Phantasy Star Collection (SAT) raw XA
|
||||
* .pxa: Mortal Kombat 4 (PS1)
|
||||
* .grn: Micro Machines (CDi)
|
||||
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
|
||||
if (!check_extensions(sf,"xa,str,adp,pxa,grn,"))
|
||||
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 " */
|
||||
if (read_u32be(0x00,sf) == 0x00FFFFFF && read_u32be(0x04,sf) == 0xFFFFFFFF && read_u32be(0x08,sf) == 0xFFFFFF00) {
|
||||
/* sector sync word = raw data */
|
||||
is_blocked = 1;
|
||||
start_offset = 0x00;
|
||||
}
|
||||
else if (is_id32be(0x00,sf, "RIFF") && is_id32be(0x08,sf, "CDXA") && is_id32be(0x0C,sf, "fmt ")) {
|
||||
/* RIFF header = raw with header (optional, added by CD drivers when copying and not part of the CD data) */
|
||||
is_blocked = 1;
|
||||
is_riff = 1;
|
||||
start_offset = 0x2c; /* after "data", ignore RIFF values as often are wrong */
|
||||
}
|
||||
else {
|
||||
/* sector sync word = raw */
|
||||
if (read_u32be(0x00,sf) == 0x00FFFFFF &&
|
||||
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;
|
||||
}
|
||||
/* non-blocked (ISO 2048 mode1/data) or incorrectly ripped: use TXTH */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* .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) */
|
||||
if (!is_riff && !xa_check_format(sf, start_offset, is_blocked))
|
||||
goto fail;
|
||||
@ -89,42 +81,18 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
||||
switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */
|
||||
case 0: break;
|
||||
default: /* shouldn't be used by games */
|
||||
VGM_LOG("XA: unknown emphasis found\n");
|
||||
break;
|
||||
vgm_logi("XA: unknown emphasis found\n");
|
||||
goto fail;
|
||||
}
|
||||
switch((xa_header >> 7) & 1) { /* 7: reserved */
|
||||
case 0: break;
|
||||
default:
|
||||
VGM_LOG("XA: unknown reserved bit found\n");
|
||||
break;
|
||||
vgm_logi("XA: unknown reserved bit found\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* headerless */
|
||||
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;
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* untested */
|
||||
@ -161,7 +129,9 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
off_t test_offset = offset;
|
||||
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 */
|
||||
while (sector < sector_max) {
|
||||
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) {
|
||||
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) {
|
||||
skip++;
|
||||
if (sector == 0 && skip > skip_max) /* no a single audio sector found */
|
||||
goto fail;
|
||||
test_offset += sector_size + extra_size + extra_size;
|
||||
continue;
|
||||
if (is_blocked && !is_audio) {
|
||||
skip++;
|
||||
if (sector == 0 && skip > skip_max) /* no a single audio sector found */
|
||||
goto fail;
|
||||
test_offset += sector_size + extra_size + extra_size;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
test_offset += extra_size; /* header */
|
||||
|
||||
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 */
|
||||
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)
|
||||
goto fail;
|
||||
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 */
|
||||
if (read_u32be(test_offset+0x00, sf) != read_u32be(test_offset+0x04, sf) ||
|
||||
read_u32be(test_offset+0x08, sf) != read_u32be(test_offset+0x0c, sf))
|
||||
if (get_u32be(frame_hdr+0x00) != get_u32be(frame_hdr+0x04) ||
|
||||
get_u32be(frame_hdr+0x08) != get_u32be(frame_hdr+0x0c))
|
||||
goto fail;
|
||||
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
|
||||
if (read_u32be(test_offset+0x00, sf) == 0 &&
|
||||
read_u32be(test_offset+0x04, sf) == 0 &&
|
||||
read_u32be(test_offset+0x08, sf) == 0 &&
|
||||
read_u32be(test_offset+0x0c, sf) == 0)
|
||||
if (get_u32be(frame_hdr+0x00) == 0 &&
|
||||
get_u32be(frame_hdr+0x04) == 0 &&
|
||||
get_u32be(frame_hdr+0x08) == 0 &&
|
||||
get_u32be(frame_hdr+0x0c) == 0)
|
||||
goto fail;
|
||||
|
||||
test_offset += 0x80;
|
||||
|
@ -1,12 +1,22 @@
|
||||
#include "streamfile.h"
|
||||
#include "util.h"
|
||||
#include "vgmstream.h"
|
||||
#include <string.h>
|
||||
|
||||
/* for dup/fdopen in some systems */
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#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
|
||||
* but may depend on compiler.
|
||||
* - MSVC: +VS2008 should work
|
||||
|
@ -1,7 +1,6 @@
|
||||
/*
|
||||
* streamfile.h - definitions for buffered file reading with STREAMFILE
|
||||
*/
|
||||
|
||||
#ifndef _STREAMFILE_H
|
||||
#define _STREAMFILE_H
|
||||
|
||||
@ -9,9 +8,14 @@
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
//TODO cleanup
|
||||
//NULL, allocs
|
||||
#include <stdlib.h>
|
||||
//FILE
|
||||
#include <stdio.h>
|
||||
//string functions in meta and so on
|
||||
#include <string.h>
|
||||
//off_t
|
||||
#include <sys/types.h>
|
||||
#include "streamtypes.h"
|
||||
#include "util.h"
|
||||
@ -22,14 +26,6 @@
|
||||
#include <io.h>
|
||||
#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).
|
||||
* 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.
|
||||
|
@ -7,4 +7,8 @@ typedef uint32_t (*read_u32_t)(off_t, STREAMFILE*);
|
||||
typedef int32_t (*read_s32_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
|
||||
|
@ -92,9 +92,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_musx,
|
||||
init_vgmstream_leg,
|
||||
init_vgmstream_filp,
|
||||
init_vgmstream_ikm_ps2,
|
||||
init_vgmstream_ikm_pc,
|
||||
init_vgmstream_ikm_psp,
|
||||
init_vgmstream_ikm,
|
||||
init_vgmstream_sfs,
|
||||
init_vgmstream_bg00,
|
||||
init_vgmstream_sat_dvi,
|
||||
@ -214,7 +212,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_xbox_hlwav,
|
||||
init_vgmstream_myspd,
|
||||
init_vgmstream_his,
|
||||
init_vgmstream_ps2_ast,
|
||||
init_vgmstream_ast_mmv,
|
||||
init_vgmstream_ast_mv,
|
||||
init_vgmstream_dmsg,
|
||||
init_vgmstream_ngc_dsp_aaap,
|
||||
init_vgmstream_ngc_dsp_konami,
|
||||
@ -237,7 +236,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_dsp_xiii,
|
||||
init_vgmstream_dsp_cabelas,
|
||||
init_vgmstream_ps2_adm,
|
||||
init_vgmstream_ps2_lpcm,
|
||||
init_vgmstream_lpcm_shade,
|
||||
init_vgmstream_dsp_bdsp,
|
||||
init_vgmstream_ps2_vms,
|
||||
init_vgmstream_xau,
|
||||
|
@ -507,7 +507,8 @@ typedef enum {
|
||||
meta_PONA_3DO, /* Policenauts (3DO) */
|
||||
meta_PONA_PSX, /* Policenauts (PSX) */
|
||||
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_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */
|
||||
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_CABELAS, /* Cabelas games */
|
||||
meta_PS2_ADM, /* Dragon Quest V (PS2) */
|
||||
meta_PS2_LPCM, /* Ah! My Goddess */
|
||||
meta_LPCM_SHADE,
|
||||
meta_DSP_BDSP, /* Ah! My Goddess */
|
||||
meta_PS2_VMS, /* Autobahn Raser - Police Madness */
|
||||
meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */
|
||||
|
Loading…
Reference in New Issue
Block a user