diff --git a/cli/txtp_segmenter.py b/cli/txtp_segmenter.py index f330c85c..e7fdf291 100644 --- a/cli/txtp_segmenter.py +++ b/cli/txtp_segmenter.py @@ -1,145 +1,145 @@ -# !/usr/bin/python - -import os -import sys -import glob -import argparse -import re - - -def parse(): - description = ( - "creates segmented .txtp from a list of files obtained using wildcards" - ) - epilog = ( - "examples:\n" - "%(prog)s bgm_*.ogg\n" - "- get all files that start with bgm_ and end with .ogg\n" - "%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n" - "- get all files that start with bgm_ and end with 2 chars plus any extension\n" - "%(prog)s files/bgm_*_all.ogg -s\n" - "- create single .txtp per every bgm_(something)_all.ogg inside files dir\n" - "%(prog)s **/*.ogg -l\n" - "- find all .ogg in all subdirs but list only\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" - ) - - 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("-s","--single", help="generate single files per list match", action='store_true') - parser.add_argument("-l","--list", help="list only results and don't write", action='store_true') - parser.add_argument("-cls","--command-loop-start", help="sets loop start segment") - parser.add_argument("-cle","--command-loop-end", help="sets loop end segment") - parser.add_argument("-cv","--command-volume", help="sets volume") - parser.add_argument("-c","--command", help="sets any command (free text)") - - return parser.parse_args() - - -def is_file_ok(args, glob_file): - if not os.path.isfile(glob_file): - return False - - if glob_file.endswith(".py"): - return False - - if args.filter: - filename_test = os.path.basename(glob_file) - p = re.compile(args.filter) - if p.match(filename_test) != None: - return False - - if args.include: - filename_test = os.path.basename(glob_file) - p = re.compile(args.include) - if p.match(filename_test) == None: - return False - - return True - -def get_txtp_name(args, segment): - txtp_name = '' - - if args.name: - txtp_name = args.name - - elif args.single: - txtp_name = os.path.splitext(os.path.basename(segment))[0] - - else: - txtp_name = os.path.splitext(os.path.basename(args.files))[0] - - txtp_name = txtp_name.replace('*', '') - txtp_name = txtp_name.replace('?', '') - - if txtp_name.endswith('_'): - txtp_name = txtp_name[:-1] - if txtp_name == '': - txtp_name = 'bgm' - - if not txtp_name.endswith(".txtp"): - txtp_name += ".txtp" - return txtp_name - -def main(): - args = parse() - - # get target files - glob_files = glob.glob(args.files) - - # process matches and add to output list - files = [] - segments = [] - for glob_file in glob_files: - if not is_file_ok(args, glob_file): - continue - - if args.single: - name = get_txtp_name(args, glob_file) - segments = [glob_file] - files.append( (name,segments) ) - - else: - segments.append(glob_file) - - if not args.single: - name = get_txtp_name(args, '') - files.append( (name,segments) ) - - if not files or not segments: - print("no files found") - exit() - - - # list info - for name, segments in files: - print("file: " + name) - for segment in segments: - print(" " + segment) - - if args.list: - exit() - - # write resulting files - for name, segments in files: - with open(name,"w+") as ftxtp: - for segment in segments: - ftxtp.write(segment + "\n") - if args.command_loop_start: - ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n") - if args.command_loop_end: - ftxtp.write("loop_end_segment = " + args.command_loop_end + "\n") - if args.command_volume: - ftxtp.write("commands = #@volume " + args.command_volume + "\n") - if args.command: - ftxtp.write(args.command + "\n") - - -if __name__ == "__main__": - main() +# !/usr/bin/python + +import os +import sys +import glob +import argparse +import re + + +def parse(): + description = ( + "creates segmented .txtp from a list of files obtained using wildcards" + ) + epilog = ( + "examples:\n" + "%(prog)s bgm_*.ogg\n" + "- get all files that start with bgm_ and end with .ogg\n" + "%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n" + "- get all files that start with bgm_ and end with 2 chars plus any extension\n" + "%(prog)s files/bgm_*_all.ogg -s\n" + "- create single .txtp per every bgm_(something)_all.ogg inside files dir\n" + "%(prog)s **/*.ogg -l\n" + "- find all .ogg in all subdirs but list only\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" + ) + + 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("-s","--single", help="generate single files per list match", action='store_true') + parser.add_argument("-l","--list", help="list only results and don't write", action='store_true') + parser.add_argument("-cls","--command-loop-start", help="sets loop start segment") + parser.add_argument("-cle","--command-loop-end", help="sets loop end segment") + parser.add_argument("-cv","--command-volume", help="sets volume") + parser.add_argument("-c","--command", help="sets any command (free text)") + + return parser.parse_args() + + +def is_file_ok(args, glob_file): + if not os.path.isfile(glob_file): + return False + + if glob_file.endswith(".py"): + return False + + if args.filter: + filename_test = os.path.basename(glob_file) + p = re.compile(args.filter) + if p.match(filename_test) != None: + return False + + if args.include: + filename_test = os.path.basename(glob_file) + p = re.compile(args.include) + if p.match(filename_test) == None: + return False + + return True + +def get_txtp_name(args, segment): + txtp_name = '' + + if args.name: + txtp_name = args.name + + elif args.single: + txtp_name = os.path.splitext(os.path.basename(segment))[0] + + else: + txtp_name = os.path.splitext(os.path.basename(args.files))[0] + + txtp_name = txtp_name.replace('*', '') + txtp_name = txtp_name.replace('?', '') + + if txtp_name.endswith('_'): + txtp_name = txtp_name[:-1] + if txtp_name == '': + txtp_name = 'bgm' + + if not txtp_name.endswith(".txtp"): + txtp_name += ".txtp" + return txtp_name + +def main(): + args = parse() + + # get target files + glob_files = glob.glob(args.files) + + # process matches and add to output list + files = [] + segments = [] + for glob_file in glob_files: + if not is_file_ok(args, glob_file): + continue + + if args.single: + name = get_txtp_name(args, glob_file) + segments = [glob_file] + files.append( (name,segments) ) + + else: + segments.append(glob_file) + + if not args.single: + name = get_txtp_name(args, '') + files.append( (name,segments) ) + + if not files or not segments: + print("no files found") + exit() + + + # list info + for name, segments in files: + print("file: " + name) + for segment in segments: + print(" " + segment) + + if args.list: + exit() + + # write resulting files + for name, segments in files: + with open(name,"w+") as ftxtp: + for segment in segments: + ftxtp.write(segment + "\n") + if args.command_loop_start: + ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n") + if args.command_loop_end: + ftxtp.write("loop_end_segment = " + args.command_loop_end + "\n") + if args.command_volume: + ftxtp.write("commands = #@volume " + args.command_volume + "\n") + if args.command: + ftxtp.write(args.command + "\n") + + +if __name__ == "__main__": + main() diff --git a/src/meta/adx_keys.h b/src/meta/adx_keys.h index 72f23b20..5f38cec5 100644 --- a/src/meta/adx_keys.h +++ b/src/meta/adx_keys.h @@ -1,346 +1,346 @@ -#ifndef _ADX_KEYS_H_ -#define _ADX_KEYS_H_ - - -typedef struct { - uint16_t start,mult,add; /* XOR values derived from the actual key */ - char* key8; /* keystring used by type 8 encryption */ - uint64_t key9; /* keycode used by type 9 encryption */ -} adxkey_info; - -/** - * List of known keys, cracked from the sound files. - * Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net. - * Multiple keys may work for a game due to how they are derived. - * start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given. - */ -static const adxkey_info adxkey8_list[] = { - - /* GOD HAND (PS2), Okami (PS2) [Clover Studio] */ - {0x49e1,0x4a57,0x553d, "karaage",0}, - - /* Blood+ (PS2) [Grasshopper Manufacture] */ - {0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF? - - /* Killer7 (PS2) [Grasshopper Manufacture] */ - {0x50fb,0x5803,0x5701, "GHM",0}, - - /* Samurai Champloo (PS2) [Grasshopper Manufacture] */ - {0x4f3f,0x472f,0x562f, "GHMSC",0}, - - /* Raiden III (PS2) [Moss] */ - {0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0}, - - /* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */ - {0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0}, - - /* Senko no Ronde [G.rev] */ - {0x46d3,0x5ced,0x474d, "ranatus",0}, - - /* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */ - {0x440b,0x6539,0x5723, "sakakit4649",0}, - - /* unknown source */ - {0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?) - - /* Shuffle! On the Stage (PS2) [Navel] */ - {0x4969,0x5deb,0x467f, "SHUF",0}, - - /* Aoishiro (PS2) [Success] */ - {0x4d65,0x5eb7,0x5dfd, "wakasugi",0}, - - /* Sonic and the Black Knight (Wii) [Sonic Team] */ - {0x55b7,0x6191,0x5a77, "morio",0}, - - /* Amagami (PS2) [Enterbrain] */ - {0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */ - - /* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */ - {0x4c01,0x549d,0x676f, "7fa0xB9tw3",0}, - - /* Fragments Blue (PS2) [Kadokawa Shoten] */ - {0x5803,0x4555,0x47bf, "PIETA",0}, - - /* Soulcalibur IV (PS3) [Namco] */ - {0x59ed,0x4679,0x46c9, "SC4Test",0}, - - /* Senko no Ronde DUO (X360) [G.rev] */ - {0x6157,0x6809,0x4045, NULL,0}, // from guessadx - - /* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */ - {0x45af,0x5f27,0x52b1, "SKFHSIA",0}, - - /* Little Anchor (PS2) [D3 Publisher] */ - {0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx - - /* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */ - {0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433} - - /* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */ - {0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0}, - - /* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */ - {0x4f7b,0x5071,0x4c61, "ELEMENGAL",0}, - - /* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */ - {0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx - - /* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */ - {0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx - - /* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */ - {0x481d,0x4f25,0x5243, "eva2",0}, - - /* Futakoi Alternative (PS2) [Marvelous] */ - {0x413b,0x543b,0x57d1, "LOVLOV",0}, - - /* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */ - {0x440b,0x4327,0x564b, "MANABIST",0}, - - /* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */ - {0x5f5d,0x552b,0x5507, "DATAM-KK2",0}, - - /* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */ - {0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx - - /* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */ - {0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx - - /* Sotsugyou 2nd Generation (PS2) [Jinx] */ - {0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5} - - /* La Corda d'Oro (PSP) [Koei] */ - {0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF? - - /* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */ - {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx - - /* Shakugan no Shana (PS2) [Vridge] */ - {0x5fc5,0x63d9,0x599f, "FUZETSU",0}, - - /* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */ - {0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx - - /* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */ - {0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */ - - /* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */ - {0x5e75,0x4a89,0x4c61, "funen-gomi",0}, - - /* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */ - {0x64ab,0x5297,0x632f, "sonic",0}, - - /* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */ - {0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */ - - /* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */ - {0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */ - - /* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */ - {0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */ - - /* Marriage Royale: Prism Story (PSP) [Vridge] */ - {0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */ - - /* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */ - {0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */ - - /* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */ - {0x41ef,0x463d,0x5507, "SGGK",0}, - - /* Nichijou: Uchuujin (PSP) [Vridge] */ - {0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */ - - /* R-15 Portable (PSP) [Kadokawa Shoten] */ - {0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0}, - - /* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */ - {0x5c33,0x4133,0x4ce7, "bi88a#fas",0}, - - /* StormLover Natsu Koi!! (PSP) [Vridge] */ - {0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */ - - /* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */ - {0x55d9,0x46d3,0x5b01, "SONMYOJI",0}, - - /* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */ - {0x658f,0x4a89,0x5213, "GBRAVO",0}, - - /* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */ - {0x6109,0x5135,0x673f, "KASHIM",0}, - - /* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */ - {0x4919,0x612d,0x4919, "RENRENKA22",0}, - - /* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */ - {0x5761,0x6283,0x4531, "HAKKEN",0}, - - /* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */ - {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, - - /* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */ - {0x5381,0x5701,0x665B, "SHINN",0}, - - /* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */ - {0x67CD,0x5CA7,0x655F, "gt25809",0}, - -}; - -static const adxkey_info adxkey9_list[] = { - - /* Phantasy Star Online 2 */ - {0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod - - /* Dragon Ball Z: Dokkan Battle (Android/iOS) */ - {0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E - - /* Kisou Ryouhei Gunhound EX (PSP) */ - {0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F - - /* Raramagi (Android) */ - {0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works) - - /* Sonic Runners (Android) */ - {0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF - - /* Fallen Princess (iOS/Android) */ - {0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E - - /* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */ - {0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699 - - /* Super Robot Wars X-Omega (iOS/Android) voices */ - {0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 - - /* AKA to BLUE (Android) */ - {0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928) - //{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149) - - /* Mashiro Witch (Android) */ - {0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204 - -}; - -static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); -static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]); - - -/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */ -static const uint16_t key8_primes[0x400] = { - 0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9, - 0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B, - 0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9, - 0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285, - 0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327, - 0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF, - 0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451, - 0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9, - 0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3, - 0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633, - 0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9, - 0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D, - 0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1, - 0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D, - 0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D, - 0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F, - 0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5, - 0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65, - 0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1, - 0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73, - 0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29, - 0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF, - 0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53, - 0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7, - 0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85, - 0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029, - 0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED, - 0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F, - 0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219, - 0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1, - 0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D, - 0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB, - 0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D, - 0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B, - 0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7, - 0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B, - 0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3, - 0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789, - 0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D, - 0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7, - 0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975, - 0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01, - 0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D, - 0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D, - 0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09, - 0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87, - 0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19, - 0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9, - 0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D, - 0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9, - 0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99, - 0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065, - 0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113, - 0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7, - 0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F, - 0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305, - 0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1, - 0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449, - 0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7, - 0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F, - 0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B, - 0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD, - 0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F, - 0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839, -}; - -static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { - size_t key_size; - uint16_t start = 0, mult = 0, add = 0; - int i; - - if (key8 == NULL || key8[0] == '\0') - goto end; - key_size = strlen(key8); - start = key8_primes[0x100]; - mult = key8_primes[0x200]; - add = key8_primes[0x300]; - - for (i = 0; i < key_size; i++) { - char c = key8[i]; - start = key8_primes[start * key8_primes[c + 0x80] % 0x400]; - mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400]; - add = key8_primes[add * key8_primes[c + 0x80] % 0x400]; - } - -end: - *out_start = start; - *out_mult = mult; - *out_add = add; -} - - -static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { - uint16_t start = 0, mult = 0, add = 0; - - /* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */ - if (key9 == 0) - goto end; - - key9--; - start = (int)(((key9 >> 27) & 0x7fff)); - mult = (int)(((key9 >> 12) & 0x7ffc) | 1); - add = (int)(((key9 << 1 ) & 0x7fff) | 1); - - /* alt from ADX_Decoder, probably the same */ - //start = ((key9 >> 27) & 0x7FFF); - //mult = ((key9 >> 12) & 0x7FFC) | 1; - //add = ((key9 << 1 ) & 0x7FFE) | 1; - //mult |= add << 16; - -end: - *out_start = start; - *out_mult = mult; - *out_add = add; -} - -#endif/*_ADX_KEYS_H_*/ +#ifndef _ADX_KEYS_H_ +#define _ADX_KEYS_H_ + + +typedef struct { + uint16_t start,mult,add; /* XOR values derived from the actual key */ + char* key8; /* keystring used by type 8 encryption */ + uint64_t key9; /* keycode used by type 9 encryption */ +} adxkey_info; + +/** + * List of known keys, cracked from the sound files. + * Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net. + * Multiple keys may work for a game due to how they are derived. + * start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given. + */ +static const adxkey_info adxkey8_list[] = { + + /* GOD HAND (PS2), Okami (PS2) [Clover Studio] */ + {0x49e1,0x4a57,0x553d, "karaage",0}, + + /* Blood+ (PS2) [Grasshopper Manufacture] */ + {0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF? + + /* Killer7 (PS2) [Grasshopper Manufacture] */ + {0x50fb,0x5803,0x5701, "GHM",0}, + + /* Samurai Champloo (PS2) [Grasshopper Manufacture] */ + {0x4f3f,0x472f,0x562f, "GHMSC",0}, + + /* Raiden III (PS2) [Moss] */ + {0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0}, + + /* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */ + {0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0}, + + /* Senko no Ronde [G.rev] */ + {0x46d3,0x5ced,0x474d, "ranatus",0}, + + /* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */ + {0x440b,0x6539,0x5723, "sakakit4649",0}, + + /* unknown source */ + {0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?) + + /* Shuffle! On the Stage (PS2) [Navel] */ + {0x4969,0x5deb,0x467f, "SHUF",0}, + + /* Aoishiro (PS2) [Success] */ + {0x4d65,0x5eb7,0x5dfd, "wakasugi",0}, + + /* Sonic and the Black Knight (Wii) [Sonic Team] */ + {0x55b7,0x6191,0x5a77, "morio",0}, + + /* Amagami (PS2) [Enterbrain] */ + {0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */ + + /* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */ + {0x4c01,0x549d,0x676f, "7fa0xB9tw3",0}, + + /* Fragments Blue (PS2) [Kadokawa Shoten] */ + {0x5803,0x4555,0x47bf, "PIETA",0}, + + /* Soulcalibur IV (PS3) [Namco] */ + {0x59ed,0x4679,0x46c9, "SC4Test",0}, + + /* Senko no Ronde DUO (X360) [G.rev] */ + {0x6157,0x6809,0x4045, NULL,0}, // from guessadx + + /* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */ + {0x45af,0x5f27,0x52b1, "SKFHSIA",0}, + + /* Little Anchor (PS2) [D3 Publisher] */ + {0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx + + /* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */ + {0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433} + + /* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */ + {0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0}, + + /* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */ + {0x4f7b,0x5071,0x4c61, "ELEMENGAL",0}, + + /* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */ + {0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx + + /* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */ + {0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx + + /* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */ + {0x481d,0x4f25,0x5243, "eva2",0}, + + /* Futakoi Alternative (PS2) [Marvelous] */ + {0x413b,0x543b,0x57d1, "LOVLOV",0}, + + /* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */ + {0x440b,0x4327,0x564b, "MANABIST",0}, + + /* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */ + {0x5f5d,0x552b,0x5507, "DATAM-KK2",0}, + + /* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */ + {0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx + + /* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */ + {0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx + + /* Sotsugyou 2nd Generation (PS2) [Jinx] */ + {0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5} + + /* La Corda d'Oro (PSP) [Koei] */ + {0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF? + + /* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */ + {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx + + /* Shakugan no Shana (PS2) [Vridge] */ + {0x5fc5,0x63d9,0x599f, "FUZETSU",0}, + + /* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */ + {0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx + + /* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */ + {0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */ + + /* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */ + {0x5e75,0x4a89,0x4c61, "funen-gomi",0}, + + /* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */ + {0x64ab,0x5297,0x632f, "sonic",0}, + + /* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */ + {0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */ + + /* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */ + {0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */ + + /* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */ + {0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */ + + /* Marriage Royale: Prism Story (PSP) [Vridge] */ + {0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */ + + /* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */ + {0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */ + + /* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */ + {0x41ef,0x463d,0x5507, "SGGK",0}, + + /* Nichijou: Uchuujin (PSP) [Vridge] */ + {0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */ + + /* R-15 Portable (PSP) [Kadokawa Shoten] */ + {0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0}, + + /* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */ + {0x5c33,0x4133,0x4ce7, "bi88a#fas",0}, + + /* StormLover Natsu Koi!! (PSP) [Vridge] */ + {0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */ + + /* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */ + {0x55d9,0x46d3,0x5b01, "SONMYOJI",0}, + + /* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */ + {0x658f,0x4a89,0x5213, "GBRAVO",0}, + + /* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */ + {0x6109,0x5135,0x673f, "KASHIM",0}, + + /* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */ + {0x4919,0x612d,0x4919, "RENRENKA22",0}, + + /* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */ + {0x5761,0x6283,0x4531, "HAKKEN",0}, + + /* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */ + {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, + + /* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */ + {0x5381,0x5701,0x665B, "SHINN",0}, + + /* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */ + {0x67CD,0x5CA7,0x655F, "gt25809",0}, + +}; + +static const adxkey_info adxkey9_list[] = { + + /* Phantasy Star Online 2 */ + {0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod + + /* Dragon Ball Z: Dokkan Battle (Android/iOS) */ + {0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E + + /* Kisou Ryouhei Gunhound EX (PSP) */ + {0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F + + /* Raramagi (Android) */ + {0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works) + + /* Sonic Runners (Android) */ + {0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF + + /* Fallen Princess (iOS/Android) */ + {0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E + + /* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */ + {0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699 + + /* Super Robot Wars X-Omega (iOS/Android) voices */ + {0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 + + /* AKA to BLUE (Android) */ + {0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928) + //{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149) + + /* Mashiro Witch (Android) */ + {0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204 + +}; + +static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); +static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]); + + +/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */ +static const uint16_t key8_primes[0x400] = { + 0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9, + 0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B, + 0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9, + 0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285, + 0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327, + 0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF, + 0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451, + 0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9, + 0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3, + 0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633, + 0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9, + 0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D, + 0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1, + 0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D, + 0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D, + 0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F, + 0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5, + 0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65, + 0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1, + 0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73, + 0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29, + 0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF, + 0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53, + 0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7, + 0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85, + 0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029, + 0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED, + 0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F, + 0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219, + 0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1, + 0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D, + 0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB, + 0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D, + 0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B, + 0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7, + 0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B, + 0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3, + 0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789, + 0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D, + 0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7, + 0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975, + 0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01, + 0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D, + 0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D, + 0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09, + 0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87, + 0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19, + 0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9, + 0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D, + 0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9, + 0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99, + 0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065, + 0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113, + 0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7, + 0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F, + 0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305, + 0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1, + 0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449, + 0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7, + 0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F, + 0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B, + 0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD, + 0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F, + 0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839, +}; + +static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { + size_t key_size; + uint16_t start = 0, mult = 0, add = 0; + int i; + + if (key8 == NULL || key8[0] == '\0') + goto end; + key_size = strlen(key8); + start = key8_primes[0x100]; + mult = key8_primes[0x200]; + add = key8_primes[0x300]; + + for (i = 0; i < key_size; i++) { + char c = key8[i]; + start = key8_primes[start * key8_primes[c + 0x80] % 0x400]; + mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400]; + add = key8_primes[add * key8_primes[c + 0x80] % 0x400]; + } + +end: + *out_start = start; + *out_mult = mult; + *out_add = add; +} + + +static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { + uint16_t start = 0, mult = 0, add = 0; + + /* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */ + if (key9 == 0) + goto end; + + key9--; + start = (int)(((key9 >> 27) & 0x7fff)); + mult = (int)(((key9 >> 12) & 0x7ffc) | 1); + add = (int)(((key9 << 1 ) & 0x7fff) | 1); + + /* alt from ADX_Decoder, probably the same */ + //start = ((key9 >> 27) & 0x7FFF); + //mult = ((key9 >> 12) & 0x7FFC) | 1; + //add = ((key9 << 1 ) & 0x7FFE) | 1; + //mult |= add << 16; + +end: + *out_start = start; + *out_mult = mult; + *out_add = add; +} + +#endif/*_ADX_KEYS_H_*/ diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c index e7592a82..5542ed38 100644 --- a/src/meta/ngc_dsp_std.c +++ b/src/meta/ngc_dsp_std.c @@ -1,1287 +1,1287 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" - - -/* If these variables are packed properly in the struct (one after another) - * then this is actually how they are laid out in the file, albeit big-endian */ -struct dsp_header { - uint32_t sample_count; /* 0x00 */ - uint32_t nibble_count; /* 0x04 */ - uint32_t sample_rate; /* 0x08 */ - uint16_t loop_flag; /* 0x0c */ - uint16_t format; /* 0x0e */ - uint32_t loop_start_offset; /* 0x10 */ - uint32_t loop_end_offset; /* 0x14 */ - uint32_t ca; /* 0x18 */ - int16_t coef[16]; /* 0x1c (really 8x2) */ - uint16_t gain; /* 0x3c */ - uint16_t initial_ps; /* 0x3e */ - int16_t initial_hist1; /* 0x40 */ - int16_t initial_hist2; /* 0x42 */ - uint16_t loop_ps; /* 0x44 */ - int16_t loop_hist1; /* 0x46 */ - int16_t loop_hist2; /* 0x48 */ - int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */ - int16_t block_size; /* 0x4c */ - /* padding/reserved up to 0x60, DSPADPCM.exe from GC adds garbage here (uninitialized MSVC memory?) */ -}; - -/* read the above struct; returns nonzero on failure */ -static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE *streamFile, int big_endian) { - int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE; - int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; - int i; - uint8_t buf[0x4e]; - - if (offset > get_streamfile_size(streamFile)) - return 1; - if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) - return 1; - header->sample_count = get_32bit(buf+0x00); - header->nibble_count = get_32bit(buf+0x04); - header->sample_rate = get_32bit(buf+0x08); - header->loop_flag = get_16bit(buf+0x0c); - header->format = get_16bit(buf+0x0e); - header->loop_start_offset = get_32bit(buf+0x10); - header->loop_end_offset = get_32bit(buf+0x14); - header->ca = get_32bit(buf+0x18); - for (i=0; i < 16; i++) - header->coef[i] = get_16bit(buf+0x1c+i*0x02); - header->gain = get_16bit(buf+0x3c); - header->initial_ps = get_16bit(buf+0x3e); - header->initial_hist1 = get_16bit(buf+0x40); - header->initial_hist2 = get_16bit(buf+0x42); - header->loop_ps = get_16bit(buf+0x44); - header->loop_hist1 = get_16bit(buf+0x46); - header->loop_hist2 = get_16bit(buf+0x48); - header->channel_count = get_16bit(buf+0x4a); - header->block_size = get_16bit(buf+0x4c); - return 0; -} -static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE *file) { - return read_dsp_header_endian(header, offset, file, 1); -} -static int read_dsp_header_le(struct dsp_header *header, off_t offset, STREAMFILE *file) { - return read_dsp_header_endian(header, offset, file, 0); -} - -/* ********************************* */ - -typedef struct { - /* basic config */ - int little_endian; - int channel_count; - int max_channels; - - off_t header_offset; /* standard DSP header */ - size_t header_spacing; /* distance between DSP header of other channels */ - off_t start_offset; /* data start */ - size_t interleave; /* distance between data of other channels */ - size_t interleave_first; /* same, in the first block */ - size_t interleave_first_skip; /* extra info */ - size_t interleave_last; /* same, in the last block */ - - meta_t meta_type; - - /* hacks */ - int force_loop; /* force full loop */ - int force_loop_seconds; /* force loop, but must be longer than this (to catch jingles) */ - int fix_looping; /* fix loop end going past num_samples */ - int fix_loop_start; /* weird files with bad loop start */ - int single_header; /* all channels share header, thus totals are off */ - int ignore_header_agreement; /* sometimes there are minor differences between headers */ -} dsp_meta; - -#define COMMON_DSP_MAX_CHANNELS 6 - -/* Common parser for most DSPs that are basically the same with minor changes. - * Custom variants will just concatenate or interleave standard DSP headers and data, - * so we make sure to validate read vs expected values, based on dsp_meta config. */ -static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *dspm) { - VGMSTREAM * vgmstream = NULL; - int i, j; - int loop_flag; - struct dsp_header ch_header[COMMON_DSP_MAX_CHANNELS]; - - - if (dspm->channel_count > dspm->max_channels) - goto fail; - if (dspm->channel_count > COMMON_DSP_MAX_CHANNELS) - goto fail; - - /* load standard DSP header per channel */ - { - for (i = 0; i < dspm->channel_count; i++) { - if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, streamFile, !dspm->little_endian)) - goto fail; - } - } - - /* fix bad/fixed value in loop start */ - if (dspm->fix_loop_start) { - for (i = 0; i < dspm->channel_count; i++) { - if (ch_header[i].loop_flag) - ch_header[i].loop_start_offset = 0x00; - } - } - - /* check type==0 and gain==0 */ - { - for (i = 0; i < dspm->channel_count; i++) { - if (ch_header[i].format || ch_header[i].gain) - goto fail; - } - } - - /* check for agreement between channels */ - if (!dspm->ignore_header_agreement) { - for (i = 0; i < dspm->channel_count - 1; i++) { - if (ch_header[i].sample_count != ch_header[i+1].sample_count || - ch_header[i].nibble_count != ch_header[i+1].nibble_count || - ch_header[i].sample_rate != ch_header[i+1].sample_rate || - ch_header[i].loop_flag != ch_header[i+1].loop_flag || - ch_header[i].loop_start_offset != ch_header[i+1].loop_start_offset || - ch_header[i].loop_end_offset != ch_header[i+1].loop_end_offset) { - goto fail; - } - } - } - - /* check expected initial predictor/scale */ - { - int channels = dspm->channel_count; - if (dspm->single_header) - channels = 1; - - for (i = 0; i < channels; i++) { - off_t channel_offset = dspm->start_offset + i*dspm->interleave; - if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, streamFile)) - goto fail; - } - } - - /* check expected loop predictor/scale */ - if (ch_header[0].loop_flag) { - int channels = dspm->channel_count; - if (dspm->single_header) - channels = 1; - - for (i = 0; i < channels; i++) { - off_t loop_offset = ch_header[i].loop_start_offset; - if (dspm->interleave) { - loop_offset = loop_offset / 16 * 8; - loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave); - } - - if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,streamFile)) - goto fail; - } - } - - - /* all done, must be DSP */ - - loop_flag = ch_header[0].loop_flag; - if (!loop_flag && dspm->force_loop) { - loop_flag = 1; - if (dspm->force_loop_seconds && - ch_header[0].sample_count < dspm->force_loop_seconds*ch_header[0].sample_rate) { - loop_flag = 0; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(dspm->channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = ch_header[0].sample_rate; - vgmstream->num_samples = ch_header[0].sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1; - - vgmstream->meta_type = dspm->meta_type; - vgmstream->coding_type = coding_NGC_DSP; - if (dspm->interleave > 0 && dspm->interleave < 0x08) - vgmstream->coding_type = coding_NGC_DSP_subint; - vgmstream->layout_type = layout_interleave; - if (dspm->interleave == 0 || vgmstream->coding_type == coding_NGC_DSP_subint) - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = dspm->interleave; - vgmstream->interleave_first_block_size = dspm->interleave_first; - vgmstream->interleave_first_skip = dspm->interleave_first_skip; - vgmstream->interleave_last_block_size = dspm->interleave_last; - - { - /* set coefs and initial history (usually 0) */ - for (i = 0; i < vgmstream->channels; i++) { - for (j = 0; j < 16; j++) { - vgmstream->ch[i].adpcm_coef[j] = ch_header[i].coef[j]; - } - vgmstream->ch[i].adpcm_history1_16 = ch_header[i].initial_hist1; - vgmstream->ch[i].adpcm_history2_16 = ch_header[i].initial_hist2; - } - } - - /* don't know why, but it does happen*/ - if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - - if (dspm->single_header == 2) { /* double the samples */ - vgmstream->num_samples /= dspm->channel_count; - vgmstream->loop_start_sample /= dspm->channel_count; - vgmstream->loop_end_sample /= dspm->channel_count; - } - - - if (!vgmstream_open_stream(vgmstream,streamFile,dspm->start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ********************************* */ - -/* .dsp - standard dsp as generated by DSPADPCM.exe */ -VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, channel_count; - - /* checks */ - /* .dsp: standard - * .adp: Dr. Muto/Battalion Wars (GC) mono files - * (extensionless): Tony Hawk's Downhill Jam (Wii) */ - if (!check_extensions(streamFile, "dsp,adp,")) - goto fail; - - if (read_dsp_header(&header, 0x00, streamFile)) - goto fail; - - channel_count = 1; - start_offset = header_size; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* Check for a matching second header. If we find one and it checks - * out thoroughly, we're probably not dealing with a genuine mono DSP. - * In many cases these will pass all the other checks, including the - * predictor/scale check if the first byte is 0 */ - //todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp - { - int ko; - struct dsp_header header2; - - /* ignore headers one after another */ - ko = read_dsp_header(&header2, header_size, streamFile); - if (!ko && - header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - - - /* ignore headers after interleave [Ultimate Board Collection (Wii)] */ - ko = read_dsp_header(&header2, 0x10000, streamFile); - if (!ko && - header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - } - - if (header.loop_flag) { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = header.loop_start_offset/16*8; - if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { - /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter - * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ - VGM_LOG("DSP (std): bad loop_predictor\n"); - //header.loop_flag = 0; - //goto fail; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->allow_dual_stereo = 1; /* very common in .dsp */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_none; - - { - /* adpcm coeffs/history */ - for (i = 0; i < 16; i++) - vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; - vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .dsp - little endian dsp, possibly main Switch .dsp [LEGO Worlds (Switch)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_std_le(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, channel_count; - - /* checks */ - /* .adpcm: LEGO Worlds */ - if (!check_extensions(streamFile, "adpcm")) - goto fail; - - if (read_dsp_header_le(&header, 0x00, streamFile)) - goto fail; - - channel_count = 1; - start_offset = header_size; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* Check for a matching second header. If we find one and it checks - * out thoroughly, we're probably not dealing with a genuine mono DSP. - * In many cases these will pass all the other checks, including the - * predictor/scale check if the first byte is 0 */ - { - struct dsp_header header2; - read_dsp_header_le(&header2, header_size, streamFile); - - if (header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - } - - if (header.loop_flag) { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = header.loop_start_offset/16*8; - if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { - /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter - * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ - VGM_LOG("DSP (std): bad loop_predictor\n"); - //header.loop_flag = 0; - //goto fail; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_none; - vgmstream->allow_dual_stereo = 1; - - { - /* adpcm coeffs/history */ - for (i = 0; i < 16; i++) - vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; - vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .dsp - standard multi-channel dsp as generated by DSPADPCM.exe (later revisions) */ -VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, c, channel_count; - - /* checks */ - if (!check_extensions(streamFile, "dsp,mdsp")) - goto fail; - - if (read_dsp_header(&header, 0x00, streamFile)) - goto fail; - - channel_count = header.channel_count==0 ? 1 : header.channel_count; - start_offset = header_size * channel_count; - - /* named .dsp and no channels? likely another interleaved dsp */ - if (check_extensions(streamFile,"dsp") && header.channel_count == 0) - goto fail; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset, streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset) + 1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen*/ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; - vgmstream->interleave_block_size = header.block_size * 8; - if (vgmstream->interleave_block_size) - vgmstream->interleave_last_block_size = (header.nibble_count / 2 % vgmstream->interleave_block_size + 7) / 8 * 8; - - for (i = 0; i < channel_count; i++) { - if (read_dsp_header(&header, header_size * i, streamFile)) goto fail; - - /* adpcm coeffs/history */ - for (c = 0; c < 16; c++) - vgmstream->ch[i].adpcm_coef[c] = header.coef[c]; - vgmstream->ch[i].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[i].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ********************************* */ - -/* .stm - Intelligent Systems + others (same programmers) full interleaved dsp [Paper Mario TTYD (GC), Fire Emblem: POR (GC), Cubivore (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .lstm/dsp: renamed to avoid hijacking Scream Tracker 2 Modules */ - if (!check_extensions(streamFile, "stm,lstm,dsp")) - goto fail; - if (read_16bitBE(0x00, streamFile) != 0x0200) - goto fail; - /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ - - dspm.channel_count = read_32bitBE(0x04, streamFile); - dspm.max_channels = 2; - dspm.fix_looping = 1; - - dspm.header_offset = 0x40; - dspm.header_spacing = 0x60; - dspm.start_offset = 0x100; - dspm.interleave = (read_32bitBE(0x08, streamFile) + 0x20) / 0x20 * 0x20; /* strange rounding, but works */ - - dspm.meta_type = meta_DSP_STM; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .(mp)dsp - single header + interleaved dsp [Monopoly Party! (GC)] */ -VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .mpdsp: renamed since standard .dsp would catch it otherwise */ - if (!check_extensions(streamFile, "mpdsp")) - goto fail; - - /* at 0x48 is extra data that could help differenciating these DSPs, but seems like - * memory garbage created by the encoder that other games also have */ - /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.single_header = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x00; /* same header for both channels */ - dspm.start_offset = 0x60; - dspm.interleave = 0xf000; - - dspm.meta_type = meta_DSP_MPDSP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* various dsp with differing extensions and interleave values */ -VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - char filename[PATH_LIMIT]; - - /* checks */ - if (!check_extensions(streamFile, "dsp,mss,gcm")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.fix_looping = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = 0xc0; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strlen(filename) > 7 && !strcasecmp("_lr.dsp",filename+strlen(filename)-7)) { //todo improve - dspm.interleave = 0x14180; - dspm.meta_type = meta_DSP_JETTERS; /* Bomberman Jetters (GC) */ - } else if (check_extensions(streamFile, "mss")) { - dspm.interleave = 0x1000; - dspm.meta_type = meta_DSP_MSS; /* Free Radical GC games */ - /* Timesplitters 2 GC's ts2_atom_smasher_44_fx.mss differs slightly in samples but plays ok */ - dspm.ignore_header_agreement = 1; - } else if (check_extensions(streamFile, "gcm")) { - /* older Traveller's Tales games [Lego Star Wars (GC), The Chronicles of Narnia (GC), Sonic R (GC)] */ - dspm.interleave = 0x8000; - dspm.meta_type = meta_DSP_GCM; - } else { - goto fail; - } - - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* IDSP - Namco header (from NUS3) + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */ -VGMSTREAM * init_vgmstream_idsp_nus3(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "idsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - /* 0x0c: sample rate, 0x10: num_samples, 0x14: loop_start_sample, 0x18: loop_start_sample */ - - dspm.channel_count = read_32bitBE(0x08, streamFile); - dspm.max_channels = 8; - /* games do adjust loop_end if bigger than num_samples (only happens in user-created IDSPs) */ - dspm.fix_looping = 1; - - dspm.header_offset = read_32bitBE(0x20,streamFile); - dspm.header_spacing = read_32bitBE(0x24,streamFile); - dspm.start_offset = read_32bitBE(0x28,streamFile); - dspm.interleave = read_32bitBE(0x1c,streamFile); /* usually 0x10 */ - if (dspm.interleave == 0) /* Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */ - dspm.interleave = read_32bitBE(0x2c,streamFile); /* half interleave, use channel size */ - - dspm.meta_type = meta_IDSP_NUS3; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* sadb - Procyon Studio header + interleaved dsp [Shiren the Wanderer 3 (Wii), Disaster: Day of Crisis (Wii)] */ -VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "sad")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x73616462) /* "sadb" */ - goto fail; - - dspm.channel_count = read_8bit(0x32, streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x80; - dspm.header_spacing = 0x60; - dspm.start_offset = read_32bitBE(0x48,streamFile); - dspm.interleave = 0x10; - - dspm.meta_type = meta_DSP_SADB; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* sadf - Procyon Studio Header Variant [Xenoblade Chronicles 2 (Switch)] (sfx) */ -VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int channel_count, loop_flag; - off_t start_offset; - - /* checks */ - if (!check_extensions(streamFile, "sad")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x73616466) /* "sadf" */ - goto fail; - - channel_count = read_8bit(0x18, streamFile); - loop_flag = read_8bit(0x19, streamFile); - start_offset = read_32bitLE(0x1C, streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->num_samples = read_32bitLE(0x28, streamFile); - vgmstream->sample_rate = read_32bitLE(0x24, streamFile); - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(0x2c, streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x30, streamFile); - } - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : - read_32bitLE(0x20, streamFile) / channel_count; - vgmstream->meta_type = meta_DSP_SADF; - - dsp_read_coefs_le(vgmstream, streamFile, 0x80, 0x80); - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* IDSP - Traveller's Tales header + interleaved dsps [Lego Batman (Wii), Lego Dimensions (Wii U)] */ -VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - int version_main, version_sub; - - /* checks */ - /* .gcm: standard - * .idsp: header id? - * .wua: Lego Dimensions (Wii U) */ - if (!check_extensions(streamFile, "gcm,idsp,wua")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - - version_main = read_32bitBE(0x04, streamFile); - version_sub = read_32bitBE(0x08, streamFile); /* extra check since there are other IDSPs */ - if (version_main == 0x01 && version_sub == 0xc8) { - /* Transformers: The Game (Wii) */ - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.header_offset = 0x10; - } - else if (version_main == 0x02 && version_sub == 0xd2) { - /* Lego Batman (Wii) - * The Chronicles of Narnia: Prince Caspian (Wii) - * Lego Indiana Jones 2 (Wii) - * Lego Star Wars: The Complete Saga (Wii) - * Lego Pirates of the Caribbean (Wii) - * Lego Harry Potter: Years 1-4 (Wii) */ - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.header_offset = 0x20; - /* 0x10+: null */ - } - else if (version_main == 0x03 && version_sub == 0x12c) { - /* Lego The Lord of the Rings (Wii) */ - /* Lego Dimensions (Wii U) */ - dspm.channel_count = read_32bitBE(0x10, streamFile); - dspm.max_channels = 2; - dspm.header_offset = 0x20; - /* 0x14+: "I_AM_PADDING" */ - } - else { - goto fail; - } - - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count; - dspm.interleave = read_32bitBE(0x0c, streamFile); - - dspm.meta_type = meta_IDSP_TT; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* IDSP - from Next Level games [Super Mario Strikers (GC), Mario Strikers: Charged (Wii)] */ -VGMSTREAM * init_vgmstream_idsp_nl(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "idsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x0c; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = read_32bitBE(0x04,streamFile); - /* 0x08: usable channel size */ - { - size_t stream_size = get_streamfile_size(streamFile); - if (read_32bitBE(stream_size - 0x04,streamFile) == 0x30303030) - stream_size -= 0x14; /* remove padding */ - stream_size -= dspm.start_offset; - - if (dspm.interleave) - dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; - } - - dspm.fix_looping = 1; - dspm.force_loop = 1; - dspm.force_loop_seconds = 15; - - dspm.meta_type = meta_IDSP_NL; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .wsd - Custom header + full interleaved dsp [Phantom Brave (Wii)] */ -VGMSTREAM * init_vgmstream_wii_wsd(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "wsd")) - goto fail; - if (read_32bitBE(0x08,streamFile) != read_32bitBE(0x0c,streamFile)) /* channel sizes */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = read_32bitBE(0x00,streamFile); - dspm.header_spacing = read_32bitBE(0x04,streamFile) - dspm.header_offset; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_WII_WSD; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .ddsp - full interleaved dsp [The Sims 2 - Pets (Wii)] */ -VGMSTREAM * init_vgmstream_dsp_ddsp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "ddsp")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = (get_streamfile_size(streamFile) / dspm.channel_count); - dspm.start_offset = 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_DDSP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* iSWS - Sumo Digital header + interleaved dsp [DiRT 2 (Wii), F1 2009 (Wii)] */ -VGMSTREAM * init_vgmstream_wii_was(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "was,dsp,isws")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69535753) /* "iSWS" */ - goto fail; - - dspm.channel_count = read_32bitBE(0x08,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x08 + read_32bitBE(0x04,streamFile); - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = read_32bitBE(0x10,streamFile); - - dspm.meta_type = meta_WII_WAS; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .str - Infogrames raw interleaved dsp [Micro Machines (GC), Superman: Shadow of Apokolips (GC)] */ -VGMSTREAM * init_vgmstream_dsp_str_ig(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "str")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x80; - dspm.start_offset = 0x800; - dspm.interleave = 0x4000; - - dspm.meta_type = meta_DSP_STR_IG; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ -VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */ - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing * dspm.channel_count; - dspm.interleave = 0x08; - - dspm.meta_type = meta_DSP_XIII; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ -VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "ndp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4E445000) /* "NDP\0" */ - goto fail; - if (read_32bitLE(0x08,streamFile) + 0x18 != get_streamfile_size(streamFile)) - goto fail; - /* 0x0c: sample rate */ - - dspm.channel_count = read_32bitLE(0x10,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x18; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = 0x04; - - dspm.meta_type = meta_WII_NDP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* Cabela's series (Magic Wand dev?) - header + interleaved dsp - * [Cabela's Big Game Hunt 2005 Adventures (GC), Cabela's Outdoor Adventures (GC)] */ -VGMSTREAM * init_vgmstream_dsp_cabelas(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - /* has extra stuff in the reserved data, without it this meta may catch other DSPs it shouldn't */ - if (read_32bitBE(0x50,streamFile) == 0 || read_32bitBE(0x54,streamFile) == 0) - goto fail; - - /* sfx are mono, but standard dsp will catch them tho */ - dspm.channel_count = read_32bitBE(0x00,streamFile) == read_32bitBE(0x60,streamFile) ? 2 : 1; - dspm.max_channels = 2; - dspm.force_loop = (dspm.channel_count > 1); - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = 0x10; - - dspm.meta_type = meta_DSP_CABELAS; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* AAAp - Acclaim Austin Audio header + interleaved dsp [Vexx (GC), Turok: Evolution (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_aaap(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41414170) /* "AAAp" */ - goto fail; - - dspm.channel_count = read_16bitBE(0x06,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x08; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = (uint16_t)read_16bitBE(0x04,streamFile); - - dspm.meta_type = meta_NGC_DSP_AAAP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* DSPW - Capcom header + full interleaved DSP [Sengoku Basara 3 (Wii), Monster Hunter 3 Ultimate (WiiU)] */ -VGMSTREAM * init_vgmstream_dsp_dspw(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t data_size; - - /* check extension */ - if (!check_extensions(streamFile, "dspw")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x44535057) /* "DSPW" */ - goto fail; - - /* ignore time marker */ - data_size = read_32bitBE(0x08, streamFile); - if (read_32bitBE(data_size - 0x10, streamFile) == 0x74494D45) /* "tIME" */ - data_size -= 0x10; /* (ignore, 2 ints in YYYYMMDD hhmmss00) */ - - /* some files have a mrkr section with multiple loop regions added at the end (variable size) */ - { - off_t mrkr_offset = data_size - 0x04; - off_t max_offset = data_size - 0x1000; - while (mrkr_offset > max_offset) { - if (read_32bitBE(mrkr_offset, streamFile) != 0x6D726B72) { /* "mrkr" */ - mrkr_offset -= 0x04; - } else { - data_size = mrkr_offset; - break; - } - } - } - data_size -= 0x20; /* header size */ - /* 0x10: loop start, 0x14: loop end, 0x1c: num_samples */ - - dspm.channel_count = read_32bitBE(0x18, streamFile); - dspm.max_channels = 6; /* 6ch in Monster Hunter 3 Ultimate */ - - dspm.header_offset = 0x20; - dspm.header_spacing = data_size / dspm.channel_count; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = data_size / dspm.channel_count; - - dspm.meta_type = meta_DSP_DSPW; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* iadp - custom header + interleaved dsp [Dr. Muto (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_iadp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .adp: actual extension, .iadp: header id */ - if (!check_extensions(streamFile, "adp,iadp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69616470) /* "iadp" */ - goto fail; - - dspm.channel_count = read_32bitBE(0x04,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x20; - dspm.header_spacing = 0x60; - dspm.start_offset = read_32bitBE(0x1C,streamFile); - dspm.interleave = read_32bitBE(0x08,streamFile); - - dspm.meta_type = meta_NGC_DSP_IADP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .mcadpcm - Custom header + full interleaved dsp [Skyrim (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "mcadpcm")) - goto fail; - /* could validate dsp sizes but only for +1ch, check_dsp_samples will do it anyway */ - //if (read_32bitLE(0x08,streamFile) != read_32bitLE(0x10,streamFile)) - // goto fail; - - dspm.channel_count = read_32bitLE(0x00,streamFile); - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = read_32bitLE(0x04,streamFile); - dspm.header_spacing = dspm.channel_count == 1 ? 0 : - read_32bitLE(0x0c,streamFile) - dspm.header_offset; /* channel 2 start, only with Nch */ - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_MCADPCM; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .switch_audio - UE4 standard LE header + full interleaved dsp [Gal Gun 2 (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .switch_audio: possibly UE4 class name rather than extension, .dsp: assumed */ - if (!check_extensions(streamFile, "switch_audio,dsp")) - goto fail; - - /* manual double header test */ - if (read_32bitLE(0x00, streamFile) == read_32bitLE(get_streamfile_size(streamFile) / 2, streamFile)) - dspm.channel_count = 2; - else - dspm.channel_count = 1; - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = get_streamfile_size(streamFile) / dspm.channel_count; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_SWITCH_AUDIO; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .vag - Nippon Ichi SPS wrapper [Penny-Punching Princess (Switch), Ys VIII (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .vag: Penny-Punching Princess (Switch) - * .nlsd: Ys VIII (Switch) */ - if (!check_extensions(streamFile, "vag,nlsd")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type (see other N1 SPS) */ - goto fail; - if ((uint16_t)read_16bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */ - goto fail; - - dspm.channel_count = 1; - dspm.max_channels = 1; - dspm.little_endian = 1; - - dspm.header_offset = 0x1c; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0; - - dspm.fix_loop_start = 1; - - dspm.meta_type = meta_DSP_VAG; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .itl - from Chanrinko Hero (GC) */ -VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "itl")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0x23C0; - - dspm.fix_looping = 1; - - dspm.meta_type = meta_DSP_ITL; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* ADPY - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "adpcmx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41445059) /* "ADPY" */ - goto fail; - - /* 0x04(2): 1? */ - /* 0x08: some size? */ - /* 0x0c: null */ - - dspm.channel_count = read_16bitLE(0x06,streamFile); - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x10; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0x08; - - dspm.meta_type = meta_DSP_ADPY; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* ADPX - AQUASTYLE wrapper [Fushigi no Gensokyo: Lotus Labyrinth (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "adpcmx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41445058) /* "ADPX" */ - goto fail; - - /* from 0x04 *6 are probably channel sizes, so max would be 6ch; this assumes 2ch */ - if (read_32bitLE(0x04,streamFile) != read_32bitLE(0x08,streamFile) && - read_32bitLE(0x0c,streamFile) != 0) - goto fail; - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x1c; - dspm.header_spacing = read_32bitLE(0x04,streamFile); - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_ADPX; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ -VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t file_size, channel_offset; - - /* checks */ - /* .ds2: real extension, dsp: fake/renamed */ - if (!check_extensions(streamFile, "ds2,dsp")) - goto fail; - if (!(read_32bitBE(0x50,streamFile) == 0 && - read_32bitBE(0x54,streamFile) == 0 && - read_32bitBE(0x58,streamFile) == 0 && - read_32bitBE(0x5c,streamFile) != 0)) - goto fail; - file_size = get_streamfile_size(streamFile); - channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ - if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.single_header = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x00; - dspm.start_offset = 0x60; - dspm.interleave = channel_offset - dspm.start_offset; - - dspm.meta_type = meta_DSP_DS2; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .itl - Incinerator Studios interleaved dsp [Cars Race-o-rama (Wii), MX vs ATV Untamed (Wii)] */ -VGMSTREAM * init_vgmstream_dsp_itl(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t stream_size; - - /* checks */ - /* .itl: standard - * .dsp: default to catch a similar file, not sure which devs */ - if (!check_extensions(streamFile, "itl,dsp")) - goto fail; - - stream_size = get_streamfile_size(streamFile); - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.start_offset = 0x60; - dspm.interleave = 0x10000; - dspm.interleave_first_skip = dspm.start_offset; - dspm.interleave_first = dspm.interleave - dspm.interleave_first_skip; - dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; - dspm.header_offset = 0x00; - dspm.header_spacing = dspm.interleave; - - //todo some files end in half a frame and may click at the very end - //todo when .dsp should refer to Ultimate Board Collection (Wii), not sure about dev - dspm.meta_type = meta_DSP_ITL_i; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" + + +/* If these variables are packed properly in the struct (one after another) + * then this is actually how they are laid out in the file, albeit big-endian */ +struct dsp_header { + uint32_t sample_count; /* 0x00 */ + uint32_t nibble_count; /* 0x04 */ + uint32_t sample_rate; /* 0x08 */ + uint16_t loop_flag; /* 0x0c */ + uint16_t format; /* 0x0e */ + uint32_t loop_start_offset; /* 0x10 */ + uint32_t loop_end_offset; /* 0x14 */ + uint32_t ca; /* 0x18 */ + int16_t coef[16]; /* 0x1c (really 8x2) */ + uint16_t gain; /* 0x3c */ + uint16_t initial_ps; /* 0x3e */ + int16_t initial_hist1; /* 0x40 */ + int16_t initial_hist2; /* 0x42 */ + uint16_t loop_ps; /* 0x44 */ + int16_t loop_hist1; /* 0x46 */ + int16_t loop_hist2; /* 0x48 */ + int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */ + int16_t block_size; /* 0x4c */ + /* padding/reserved up to 0x60, DSPADPCM.exe from GC adds garbage here (uninitialized MSVC memory?) */ +}; + +/* read the above struct; returns nonzero on failure */ +static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE *streamFile, int big_endian) { + int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE; + int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; + int i; + uint8_t buf[0x4e]; + + if (offset > get_streamfile_size(streamFile)) + return 1; + if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) + return 1; + header->sample_count = get_32bit(buf+0x00); + header->nibble_count = get_32bit(buf+0x04); + header->sample_rate = get_32bit(buf+0x08); + header->loop_flag = get_16bit(buf+0x0c); + header->format = get_16bit(buf+0x0e); + header->loop_start_offset = get_32bit(buf+0x10); + header->loop_end_offset = get_32bit(buf+0x14); + header->ca = get_32bit(buf+0x18); + for (i=0; i < 16; i++) + header->coef[i] = get_16bit(buf+0x1c+i*0x02); + header->gain = get_16bit(buf+0x3c); + header->initial_ps = get_16bit(buf+0x3e); + header->initial_hist1 = get_16bit(buf+0x40); + header->initial_hist2 = get_16bit(buf+0x42); + header->loop_ps = get_16bit(buf+0x44); + header->loop_hist1 = get_16bit(buf+0x46); + header->loop_hist2 = get_16bit(buf+0x48); + header->channel_count = get_16bit(buf+0x4a); + header->block_size = get_16bit(buf+0x4c); + return 0; +} +static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE *file) { + return read_dsp_header_endian(header, offset, file, 1); +} +static int read_dsp_header_le(struct dsp_header *header, off_t offset, STREAMFILE *file) { + return read_dsp_header_endian(header, offset, file, 0); +} + +/* ********************************* */ + +typedef struct { + /* basic config */ + int little_endian; + int channel_count; + int max_channels; + + off_t header_offset; /* standard DSP header */ + size_t header_spacing; /* distance between DSP header of other channels */ + off_t start_offset; /* data start */ + size_t interleave; /* distance between data of other channels */ + size_t interleave_first; /* same, in the first block */ + size_t interleave_first_skip; /* extra info */ + size_t interleave_last; /* same, in the last block */ + + meta_t meta_type; + + /* hacks */ + int force_loop; /* force full loop */ + int force_loop_seconds; /* force loop, but must be longer than this (to catch jingles) */ + int fix_looping; /* fix loop end going past num_samples */ + int fix_loop_start; /* weird files with bad loop start */ + int single_header; /* all channels share header, thus totals are off */ + int ignore_header_agreement; /* sometimes there are minor differences between headers */ +} dsp_meta; + +#define COMMON_DSP_MAX_CHANNELS 6 + +/* Common parser for most DSPs that are basically the same with minor changes. + * Custom variants will just concatenate or interleave standard DSP headers and data, + * so we make sure to validate read vs expected values, based on dsp_meta config. */ +static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *dspm) { + VGMSTREAM * vgmstream = NULL; + int i, j; + int loop_flag; + struct dsp_header ch_header[COMMON_DSP_MAX_CHANNELS]; + + + if (dspm->channel_count > dspm->max_channels) + goto fail; + if (dspm->channel_count > COMMON_DSP_MAX_CHANNELS) + goto fail; + + /* load standard DSP header per channel */ + { + for (i = 0; i < dspm->channel_count; i++) { + if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, streamFile, !dspm->little_endian)) + goto fail; + } + } + + /* fix bad/fixed value in loop start */ + if (dspm->fix_loop_start) { + for (i = 0; i < dspm->channel_count; i++) { + if (ch_header[i].loop_flag) + ch_header[i].loop_start_offset = 0x00; + } + } + + /* check type==0 and gain==0 */ + { + for (i = 0; i < dspm->channel_count; i++) { + if (ch_header[i].format || ch_header[i].gain) + goto fail; + } + } + + /* check for agreement between channels */ + if (!dspm->ignore_header_agreement) { + for (i = 0; i < dspm->channel_count - 1; i++) { + if (ch_header[i].sample_count != ch_header[i+1].sample_count || + ch_header[i].nibble_count != ch_header[i+1].nibble_count || + ch_header[i].sample_rate != ch_header[i+1].sample_rate || + ch_header[i].loop_flag != ch_header[i+1].loop_flag || + ch_header[i].loop_start_offset != ch_header[i+1].loop_start_offset || + ch_header[i].loop_end_offset != ch_header[i+1].loop_end_offset) { + goto fail; + } + } + } + + /* check expected initial predictor/scale */ + { + int channels = dspm->channel_count; + if (dspm->single_header) + channels = 1; + + for (i = 0; i < channels; i++) { + off_t channel_offset = dspm->start_offset + i*dspm->interleave; + if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, streamFile)) + goto fail; + } + } + + /* check expected loop predictor/scale */ + if (ch_header[0].loop_flag) { + int channels = dspm->channel_count; + if (dspm->single_header) + channels = 1; + + for (i = 0; i < channels; i++) { + off_t loop_offset = ch_header[i].loop_start_offset; + if (dspm->interleave) { + loop_offset = loop_offset / 16 * 8; + loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave); + } + + if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,streamFile)) + goto fail; + } + } + + + /* all done, must be DSP */ + + loop_flag = ch_header[0].loop_flag; + if (!loop_flag && dspm->force_loop) { + loop_flag = 1; + if (dspm->force_loop_seconds && + ch_header[0].sample_count < dspm->force_loop_seconds*ch_header[0].sample_rate) { + loop_flag = 0; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(dspm->channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = ch_header[0].sample_rate; + vgmstream->num_samples = ch_header[0].sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1; + + vgmstream->meta_type = dspm->meta_type; + vgmstream->coding_type = coding_NGC_DSP; + if (dspm->interleave > 0 && dspm->interleave < 0x08) + vgmstream->coding_type = coding_NGC_DSP_subint; + vgmstream->layout_type = layout_interleave; + if (dspm->interleave == 0 || vgmstream->coding_type == coding_NGC_DSP_subint) + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = dspm->interleave; + vgmstream->interleave_first_block_size = dspm->interleave_first; + vgmstream->interleave_first_skip = dspm->interleave_first_skip; + vgmstream->interleave_last_block_size = dspm->interleave_last; + + { + /* set coefs and initial history (usually 0) */ + for (i = 0; i < vgmstream->channels; i++) { + for (j = 0; j < 16; j++) { + vgmstream->ch[i].adpcm_coef[j] = ch_header[i].coef[j]; + } + vgmstream->ch[i].adpcm_history1_16 = ch_header[i].initial_hist1; + vgmstream->ch[i].adpcm_history2_16 = ch_header[i].initial_hist2; + } + } + + /* don't know why, but it does happen*/ + if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + + if (dspm->single_header == 2) { /* double the samples */ + vgmstream->num_samples /= dspm->channel_count; + vgmstream->loop_start_sample /= dspm->channel_count; + vgmstream->loop_end_sample /= dspm->channel_count; + } + + + if (!vgmstream_open_stream(vgmstream,streamFile,dspm->start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ********************************* */ + +/* .dsp - standard dsp as generated by DSPADPCM.exe */ +VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, channel_count; + + /* checks */ + /* .dsp: standard + * .adp: Dr. Muto/Battalion Wars (GC) mono files + * (extensionless): Tony Hawk's Downhill Jam (Wii) */ + if (!check_extensions(streamFile, "dsp,adp,")) + goto fail; + + if (read_dsp_header(&header, 0x00, streamFile)) + goto fail; + + channel_count = 1; + start_offset = header_size; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* Check for a matching second header. If we find one and it checks + * out thoroughly, we're probably not dealing with a genuine mono DSP. + * In many cases these will pass all the other checks, including the + * predictor/scale check if the first byte is 0 */ + //todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp + { + int ko; + struct dsp_header header2; + + /* ignore headers one after another */ + ko = read_dsp_header(&header2, header_size, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + + + /* ignore headers after interleave [Ultimate Board Collection (Wii)] */ + ko = read_dsp_header(&header2, 0x10000, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + } + + if (header.loop_flag) { + off_t loop_off; + /* check loop predictor/scale */ + loop_off = header.loop_start_offset/16*8; + if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { + /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter + * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ + VGM_LOG("DSP (std): bad loop_predictor\n"); + //header.loop_flag = 0; + //goto fail; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->allow_dual_stereo = 1; /* very common in .dsp */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + + { + /* adpcm coeffs/history */ + for (i = 0; i < 16; i++) + vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; + vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .dsp - little endian dsp, possibly main Switch .dsp [LEGO Worlds (Switch)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_std_le(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, channel_count; + + /* checks */ + /* .adpcm: LEGO Worlds */ + if (!check_extensions(streamFile, "adpcm")) + goto fail; + + if (read_dsp_header_le(&header, 0x00, streamFile)) + goto fail; + + channel_count = 1; + start_offset = header_size; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* Check for a matching second header. If we find one and it checks + * out thoroughly, we're probably not dealing with a genuine mono DSP. + * In many cases these will pass all the other checks, including the + * predictor/scale check if the first byte is 0 */ + { + struct dsp_header header2; + read_dsp_header_le(&header2, header_size, streamFile); + + if (header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + } + + if (header.loop_flag) { + off_t loop_off; + /* check loop predictor/scale */ + loop_off = header.loop_start_offset/16*8; + if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { + /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter + * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ + VGM_LOG("DSP (std): bad loop_predictor\n"); + //header.loop_flag = 0; + //goto fail; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + vgmstream->allow_dual_stereo = 1; + + { + /* adpcm coeffs/history */ + for (i = 0; i < 16; i++) + vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; + vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .dsp - standard multi-channel dsp as generated by DSPADPCM.exe (later revisions) */ +VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, c, channel_count; + + /* checks */ + if (!check_extensions(streamFile, "dsp,mdsp")) + goto fail; + + if (read_dsp_header(&header, 0x00, streamFile)) + goto fail; + + channel_count = header.channel_count==0 ? 1 : header.channel_count; + start_offset = header_size * channel_count; + + /* named .dsp and no channels? likely another interleaved dsp */ + if (check_extensions(streamFile,"dsp") && header.channel_count == 0) + goto fail; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset, streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset) + 1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen*/ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; + vgmstream->interleave_block_size = header.block_size * 8; + if (vgmstream->interleave_block_size) + vgmstream->interleave_last_block_size = (header.nibble_count / 2 % vgmstream->interleave_block_size + 7) / 8 * 8; + + for (i = 0; i < channel_count; i++) { + if (read_dsp_header(&header, header_size * i, streamFile)) goto fail; + + /* adpcm coeffs/history */ + for (c = 0; c < 16; c++) + vgmstream->ch[i].adpcm_coef[c] = header.coef[c]; + vgmstream->ch[i].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[i].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ********************************* */ + +/* .stm - Intelligent Systems + others (same programmers) full interleaved dsp [Paper Mario TTYD (GC), Fire Emblem: POR (GC), Cubivore (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .lstm/dsp: renamed to avoid hijacking Scream Tracker 2 Modules */ + if (!check_extensions(streamFile, "stm,lstm,dsp")) + goto fail; + if (read_16bitBE(0x00, streamFile) != 0x0200) + goto fail; + /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ + + dspm.channel_count = read_32bitBE(0x04, streamFile); + dspm.max_channels = 2; + dspm.fix_looping = 1; + + dspm.header_offset = 0x40; + dspm.header_spacing = 0x60; + dspm.start_offset = 0x100; + dspm.interleave = (read_32bitBE(0x08, streamFile) + 0x20) / 0x20 * 0x20; /* strange rounding, but works */ + + dspm.meta_type = meta_DSP_STM; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .(mp)dsp - single header + interleaved dsp [Monopoly Party! (GC)] */ +VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .mpdsp: renamed since standard .dsp would catch it otherwise */ + if (!check_extensions(streamFile, "mpdsp")) + goto fail; + + /* at 0x48 is extra data that could help differenciating these DSPs, but seems like + * memory garbage created by the encoder that other games also have */ + /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; /* same header for both channels */ + dspm.start_offset = 0x60; + dspm.interleave = 0xf000; + + dspm.meta_type = meta_DSP_MPDSP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* various dsp with differing extensions and interleave values */ +VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + char filename[PATH_LIMIT]; + + /* checks */ + if (!check_extensions(streamFile, "dsp,mss,gcm")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.fix_looping = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = 0xc0; + + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strlen(filename) > 7 && !strcasecmp("_lr.dsp",filename+strlen(filename)-7)) { //todo improve + dspm.interleave = 0x14180; + dspm.meta_type = meta_DSP_JETTERS; /* Bomberman Jetters (GC) */ + } else if (check_extensions(streamFile, "mss")) { + dspm.interleave = 0x1000; + dspm.meta_type = meta_DSP_MSS; /* Free Radical GC games */ + /* Timesplitters 2 GC's ts2_atom_smasher_44_fx.mss differs slightly in samples but plays ok */ + dspm.ignore_header_agreement = 1; + } else if (check_extensions(streamFile, "gcm")) { + /* older Traveller's Tales games [Lego Star Wars (GC), The Chronicles of Narnia (GC), Sonic R (GC)] */ + dspm.interleave = 0x8000; + dspm.meta_type = meta_DSP_GCM; + } else { + goto fail; + } + + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* IDSP - Namco header (from NUS3) + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */ +VGMSTREAM * init_vgmstream_idsp_nus3(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "idsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + /* 0x0c: sample rate, 0x10: num_samples, 0x14: loop_start_sample, 0x18: loop_start_sample */ + + dspm.channel_count = read_32bitBE(0x08, streamFile); + dspm.max_channels = 8; + /* games do adjust loop_end if bigger than num_samples (only happens in user-created IDSPs) */ + dspm.fix_looping = 1; + + dspm.header_offset = read_32bitBE(0x20,streamFile); + dspm.header_spacing = read_32bitBE(0x24,streamFile); + dspm.start_offset = read_32bitBE(0x28,streamFile); + dspm.interleave = read_32bitBE(0x1c,streamFile); /* usually 0x10 */ + if (dspm.interleave == 0) /* Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */ + dspm.interleave = read_32bitBE(0x2c,streamFile); /* half interleave, use channel size */ + + dspm.meta_type = meta_IDSP_NUS3; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* sadb - Procyon Studio header + interleaved dsp [Shiren the Wanderer 3 (Wii), Disaster: Day of Crisis (Wii)] */ +VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "sad")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x73616462) /* "sadb" */ + goto fail; + + dspm.channel_count = read_8bit(0x32, streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x80; + dspm.header_spacing = 0x60; + dspm.start_offset = read_32bitBE(0x48,streamFile); + dspm.interleave = 0x10; + + dspm.meta_type = meta_DSP_SADB; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* sadf - Procyon Studio Header Variant [Xenoblade Chronicles 2 (Switch)] (sfx) */ +VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + int channel_count, loop_flag; + off_t start_offset; + + /* checks */ + if (!check_extensions(streamFile, "sad")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x73616466) /* "sadf" */ + goto fail; + + channel_count = read_8bit(0x18, streamFile); + loop_flag = read_8bit(0x19, streamFile); + start_offset = read_32bitLE(0x1C, streamFile); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->num_samples = read_32bitLE(0x28, streamFile); + vgmstream->sample_rate = read_32bitLE(0x24, streamFile); + if (loop_flag) { + vgmstream->loop_start_sample = read_32bitLE(0x2c, streamFile); + vgmstream->loop_end_sample = read_32bitLE(0x30, streamFile); + } + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : + read_32bitLE(0x20, streamFile) / channel_count; + vgmstream->meta_type = meta_DSP_SADF; + + dsp_read_coefs_le(vgmstream, streamFile, 0x80, 0x80); + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* IDSP - Traveller's Tales header + interleaved dsps [Lego Batman (Wii), Lego Dimensions (Wii U)] */ +VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + int version_main, version_sub; + + /* checks */ + /* .gcm: standard + * .idsp: header id? + * .wua: Lego Dimensions (Wii U) */ + if (!check_extensions(streamFile, "gcm,idsp,wua")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + + version_main = read_32bitBE(0x04, streamFile); + version_sub = read_32bitBE(0x08, streamFile); /* extra check since there are other IDSPs */ + if (version_main == 0x01 && version_sub == 0xc8) { + /* Transformers: The Game (Wii) */ + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.header_offset = 0x10; + } + else if (version_main == 0x02 && version_sub == 0xd2) { + /* Lego Batman (Wii) + * The Chronicles of Narnia: Prince Caspian (Wii) + * Lego Indiana Jones 2 (Wii) + * Lego Star Wars: The Complete Saga (Wii) + * Lego Pirates of the Caribbean (Wii) + * Lego Harry Potter: Years 1-4 (Wii) */ + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.header_offset = 0x20; + /* 0x10+: null */ + } + else if (version_main == 0x03 && version_sub == 0x12c) { + /* Lego The Lord of the Rings (Wii) */ + /* Lego Dimensions (Wii U) */ + dspm.channel_count = read_32bitBE(0x10, streamFile); + dspm.max_channels = 2; + dspm.header_offset = 0x20; + /* 0x14+: "I_AM_PADDING" */ + } + else { + goto fail; + } + + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count; + dspm.interleave = read_32bitBE(0x0c, streamFile); + + dspm.meta_type = meta_IDSP_TT; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* IDSP - from Next Level games [Super Mario Strikers (GC), Mario Strikers: Charged (Wii)] */ +VGMSTREAM * init_vgmstream_idsp_nl(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "idsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x0c; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = read_32bitBE(0x04,streamFile); + /* 0x08: usable channel size */ + { + size_t stream_size = get_streamfile_size(streamFile); + if (read_32bitBE(stream_size - 0x04,streamFile) == 0x30303030) + stream_size -= 0x14; /* remove padding */ + stream_size -= dspm.start_offset; + + if (dspm.interleave) + dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; + } + + dspm.fix_looping = 1; + dspm.force_loop = 1; + dspm.force_loop_seconds = 15; + + dspm.meta_type = meta_IDSP_NL; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .wsd - Custom header + full interleaved dsp [Phantom Brave (Wii)] */ +VGMSTREAM * init_vgmstream_wii_wsd(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "wsd")) + goto fail; + if (read_32bitBE(0x08,streamFile) != read_32bitBE(0x0c,streamFile)) /* channel sizes */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = read_32bitBE(0x00,streamFile); + dspm.header_spacing = read_32bitBE(0x04,streamFile) - dspm.header_offset; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_WII_WSD; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .ddsp - full interleaved dsp [The Sims 2 - Pets (Wii)] */ +VGMSTREAM * init_vgmstream_dsp_ddsp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "ddsp")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = (get_streamfile_size(streamFile) / dspm.channel_count); + dspm.start_offset = 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_DDSP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* iSWS - Sumo Digital header + interleaved dsp [DiRT 2 (Wii), F1 2009 (Wii)] */ +VGMSTREAM * init_vgmstream_wii_was(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "was,dsp,isws")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69535753) /* "iSWS" */ + goto fail; + + dspm.channel_count = read_32bitBE(0x08,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x08 + read_32bitBE(0x04,streamFile); + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = read_32bitBE(0x10,streamFile); + + dspm.meta_type = meta_WII_WAS; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .str - Infogrames raw interleaved dsp [Micro Machines (GC), Superman: Shadow of Apokolips (GC)] */ +VGMSTREAM * init_vgmstream_dsp_str_ig(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "str")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x80; + dspm.start_offset = 0x800; + dspm.interleave = 0x4000; + + dspm.meta_type = meta_DSP_STR_IG; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ +VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */ + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing * dspm.channel_count; + dspm.interleave = 0x08; + + dspm.meta_type = meta_DSP_XIII; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ +VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "ndp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4E445000) /* "NDP\0" */ + goto fail; + if (read_32bitLE(0x08,streamFile) + 0x18 != get_streamfile_size(streamFile)) + goto fail; + /* 0x0c: sample rate */ + + dspm.channel_count = read_32bitLE(0x10,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x18; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = 0x04; + + dspm.meta_type = meta_WII_NDP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* Cabela's series (Magic Wand dev?) - header + interleaved dsp + * [Cabela's Big Game Hunt 2005 Adventures (GC), Cabela's Outdoor Adventures (GC)] */ +VGMSTREAM * init_vgmstream_dsp_cabelas(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + /* has extra stuff in the reserved data, without it this meta may catch other DSPs it shouldn't */ + if (read_32bitBE(0x50,streamFile) == 0 || read_32bitBE(0x54,streamFile) == 0) + goto fail; + + /* sfx are mono, but standard dsp will catch them tho */ + dspm.channel_count = read_32bitBE(0x00,streamFile) == read_32bitBE(0x60,streamFile) ? 2 : 1; + dspm.max_channels = 2; + dspm.force_loop = (dspm.channel_count > 1); + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = 0x10; + + dspm.meta_type = meta_DSP_CABELAS; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* AAAp - Acclaim Austin Audio header + interleaved dsp [Vexx (GC), Turok: Evolution (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_aaap(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41414170) /* "AAAp" */ + goto fail; + + dspm.channel_count = read_16bitBE(0x06,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x08; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = (uint16_t)read_16bitBE(0x04,streamFile); + + dspm.meta_type = meta_NGC_DSP_AAAP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* DSPW - Capcom header + full interleaved DSP [Sengoku Basara 3 (Wii), Monster Hunter 3 Ultimate (WiiU)] */ +VGMSTREAM * init_vgmstream_dsp_dspw(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t data_size; + + /* check extension */ + if (!check_extensions(streamFile, "dspw")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x44535057) /* "DSPW" */ + goto fail; + + /* ignore time marker */ + data_size = read_32bitBE(0x08, streamFile); + if (read_32bitBE(data_size - 0x10, streamFile) == 0x74494D45) /* "tIME" */ + data_size -= 0x10; /* (ignore, 2 ints in YYYYMMDD hhmmss00) */ + + /* some files have a mrkr section with multiple loop regions added at the end (variable size) */ + { + off_t mrkr_offset = data_size - 0x04; + off_t max_offset = data_size - 0x1000; + while (mrkr_offset > max_offset) { + if (read_32bitBE(mrkr_offset, streamFile) != 0x6D726B72) { /* "mrkr" */ + mrkr_offset -= 0x04; + } else { + data_size = mrkr_offset; + break; + } + } + } + data_size -= 0x20; /* header size */ + /* 0x10: loop start, 0x14: loop end, 0x1c: num_samples */ + + dspm.channel_count = read_32bitBE(0x18, streamFile); + dspm.max_channels = 6; /* 6ch in Monster Hunter 3 Ultimate */ + + dspm.header_offset = 0x20; + dspm.header_spacing = data_size / dspm.channel_count; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = data_size / dspm.channel_count; + + dspm.meta_type = meta_DSP_DSPW; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* iadp - custom header + interleaved dsp [Dr. Muto (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_iadp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .adp: actual extension, .iadp: header id */ + if (!check_extensions(streamFile, "adp,iadp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69616470) /* "iadp" */ + goto fail; + + dspm.channel_count = read_32bitBE(0x04,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x20; + dspm.header_spacing = 0x60; + dspm.start_offset = read_32bitBE(0x1C,streamFile); + dspm.interleave = read_32bitBE(0x08,streamFile); + + dspm.meta_type = meta_NGC_DSP_IADP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .mcadpcm - Custom header + full interleaved dsp [Skyrim (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "mcadpcm")) + goto fail; + /* could validate dsp sizes but only for +1ch, check_dsp_samples will do it anyway */ + //if (read_32bitLE(0x08,streamFile) != read_32bitLE(0x10,streamFile)) + // goto fail; + + dspm.channel_count = read_32bitLE(0x00,streamFile); + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = read_32bitLE(0x04,streamFile); + dspm.header_spacing = dspm.channel_count == 1 ? 0 : + read_32bitLE(0x0c,streamFile) - dspm.header_offset; /* channel 2 start, only with Nch */ + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_MCADPCM; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .switch_audio - UE4 standard LE header + full interleaved dsp [Gal Gun 2 (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .switch_audio: possibly UE4 class name rather than extension, .dsp: assumed */ + if (!check_extensions(streamFile, "switch_audio,dsp")) + goto fail; + + /* manual double header test */ + if (read_32bitLE(0x00, streamFile) == read_32bitLE(get_streamfile_size(streamFile) / 2, streamFile)) + dspm.channel_count = 2; + else + dspm.channel_count = 1; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = get_streamfile_size(streamFile) / dspm.channel_count; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_SWITCH_AUDIO; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .vag - Nippon Ichi SPS wrapper [Penny-Punching Princess (Switch), Ys VIII (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .vag: Penny-Punching Princess (Switch) + * .nlsd: Ys VIII (Switch) */ + if (!check_extensions(streamFile, "vag,nlsd")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type (see other N1 SPS) */ + goto fail; + if ((uint16_t)read_16bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */ + goto fail; + + dspm.channel_count = 1; + dspm.max_channels = 1; + dspm.little_endian = 1; + + dspm.header_offset = 0x1c; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0; + + dspm.fix_loop_start = 1; + + dspm.meta_type = meta_DSP_VAG; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .itl - from Chanrinko Hero (GC) */ +VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "itl")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0x23C0; + + dspm.fix_looping = 1; + + dspm.meta_type = meta_DSP_ITL; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* ADPY - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "adpcmx")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41445059) /* "ADPY" */ + goto fail; + + /* 0x04(2): 1? */ + /* 0x08: some size? */ + /* 0x0c: null */ + + dspm.channel_count = read_16bitLE(0x06,streamFile); + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x10; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0x08; + + dspm.meta_type = meta_DSP_ADPY; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* ADPX - AQUASTYLE wrapper [Fushigi no Gensokyo: Lotus Labyrinth (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "adpcmx")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41445058) /* "ADPX" */ + goto fail; + + /* from 0x04 *6 are probably channel sizes, so max would be 6ch; this assumes 2ch */ + if (read_32bitLE(0x04,streamFile) != read_32bitLE(0x08,streamFile) && + read_32bitLE(0x0c,streamFile) != 0) + goto fail; + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x1c; + dspm.header_spacing = read_32bitLE(0x04,streamFile); + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_ADPX; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t file_size, channel_offset; + + /* checks */ + /* .ds2: real extension, dsp: fake/renamed */ + if (!check_extensions(streamFile, "ds2,dsp")) + goto fail; + if (!(read_32bitBE(0x50,streamFile) == 0 && + read_32bitBE(0x54,streamFile) == 0 && + read_32bitBE(0x58,streamFile) == 0 && + read_32bitBE(0x5c,streamFile) != 0)) + goto fail; + file_size = get_streamfile_size(streamFile); + channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ + if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; + dspm.start_offset = 0x60; + dspm.interleave = channel_offset - dspm.start_offset; + + dspm.meta_type = meta_DSP_DS2; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .itl - Incinerator Studios interleaved dsp [Cars Race-o-rama (Wii), MX vs ATV Untamed (Wii)] */ +VGMSTREAM * init_vgmstream_dsp_itl(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t stream_size; + + /* checks */ + /* .itl: standard + * .dsp: default to catch a similar file, not sure which devs */ + if (!check_extensions(streamFile, "itl,dsp")) + goto fail; + + stream_size = get_streamfile_size(streamFile); + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.start_offset = 0x60; + dspm.interleave = 0x10000; + dspm.interleave_first_skip = dspm.start_offset; + dspm.interleave_first = dspm.interleave - dspm.interleave_first_skip; + dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; + dspm.header_offset = 0x00; + dspm.header_spacing = dspm.interleave; + + //todo some files end in half a frame and may click at the very end + //todo when .dsp should refer to Ultimate Board Collection (Wii), not sure about dev + dspm.meta_type = meta_DSP_ITL_i; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} diff --git a/src/meta/nub.c b/src/meta/nub.c index cc5b39f9..0d488abc 100644 --- a/src/meta/nub.c +++ b/src/meta/nub.c @@ -1,527 +1,527 @@ -#include "meta.h" -#include "../coding/coding.h" - - -static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext); - -/* .nub - Namco's nu Sound v2 audio container */ -VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t name_offset = 0; - size_t name_size = 0; - int total_subsongs, target_subsong = streamFile->stream_index; - uint32_t version, codec; - const char* fake_ext; - VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *) = NULL; - char name[STREAM_NAME_SIZE] = {0}; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - if (!check_extensions(streamFile, "nub")) - goto fail; - - version = read_32bitBE(0x00,streamFile); - if (version != 0x00020000 && /* v2.0 (rare, ex. Ridge Race 6 (X360)) */ - version != 0x00020100 && /* v2.1 (common) */ - version != 0x01020100) /* same but LE? (seen in PSP games, not PS4) */ - goto fail; - if (read_32bitBE(0x04,streamFile) != 0x00000000) /* null */ - goto fail; - - /* sometimes LE [Soul Calibur: Broken Destiny (PSP), Tales of Vesperia (PS4) */ - if (guess_endianness32bit(0x10, streamFile)) { - read_32bit = read_32bitBE; - } else{ - read_32bit = read_32bitLE; - } - - /* parse TOC */ - { - off_t offset, data_start, header_start; - off_t header_offset, subheader_start, stream_offset; - size_t header_size, subheader_size, stream_size; - - /* - base header */ - /* 0x08: file id/number (can be 0 = first) */ - total_subsongs = read_32bit(0x0c, streamFile); /* .nub with 0 files do exist */ - data_start = read_32bit(0x10, streamFile); /* exists even with 0 files */ - /* 0x14: data end (may have padding) */ - header_start = read_32bit(0x18, streamFile); - /* 0x1c: header end */ - - /* probably means "header end" in v2.0 */ - if (version == 0x00020000) { - data_start = align_size_to_block(data_start, 0x800); - } - - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - - offset = read_32bit(header_start + (target_subsong-1)*0x04, streamFile); - - /* .nus have all headers first then all data, but extractors often just paste them together, - * so we'll combine header+data on the fly to make them playable with existing parsers. - * Formats inside .nub don't exist as external files, so they could be extracted in various - * ways that we'll try to match (though BNSF can be found as header+data in some bigfiles too). */ - - header_offset = offset; - - /* - extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */ - if (version != 0x00020000) - offset += 0x04; /* skip but not in v2.0 */ - - /* - file header */ - /* 0x00: config? */ - /* 0x04: header id/number */ - codec = (uint32_t)read_32bit(offset + 0x08, streamFile); - /* 0x0c: null */ - stream_size = read_32bit(offset + 0x10, streamFile); /* 0x10 aligned */ - stream_offset = read_32bit(offset + 0x14, streamFile) + data_start; - subheader_size = read_32bit(offset + 0x18, streamFile); - /* rest looks like config/volumes/etc */ - - if (version == 0x00020000) - subheader_start = 0xAC; - else - subheader_start = 0xBC; - header_size = align_size_to_block(subheader_start + subheader_size, 0x10); - - switch(codec) { - case 0x00: /* (none) (xma1) */ - fake_ext = "xma"; - init_vgmstream_function = init_vgmstream_nub_xma; - break; - - case 0x01: /* "wav\0" */ - fake_ext = "wav"; - init_vgmstream_function = init_vgmstream_nub_wav; - break; - - case 0x02: /* "vag\0" */ - fake_ext = "vag"; - init_vgmstream_function = init_vgmstream_nub_vag; - break; - - case 0x03: /* "at3\0" */ - fake_ext = "at3"; - init_vgmstream_function = init_vgmstream_nub_at3; - break; - - case 0x04: /* "xma\0" (xma2 old) */ - case 0x08: /* "xma\0" (xma2 new) */ - fake_ext = "xma"; - init_vgmstream_function = init_vgmstream_nub_xma; - break; - - case 0x06: /* "idsp" */ - fake_ext = "idsp"; - init_vgmstream_function = init_vgmstream_nub_idsp; - break; - - case 0x07: /* "is14" */ - fake_ext = "is14"; - init_vgmstream_function = init_vgmstream_nub_is14; - break; - - case 0x05: - default: - VGM_LOG("NUB: unknown codec %x\n", codec); - goto fail; - } - - //;VGM_LOG("NUB: subfile header=%lx + %x, offset=%lx + %x\n", header_offset, header_size, stream_offset, stream_size); - - temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, fake_ext); - if (!temp_sf) goto fail; - } - - /* get names */ - { - /* file names are in a companion file, rarely [Noby Noby Boy (PS3)] */ - STREAMFILE *nameFile = NULL; - char filename[PATH_LIMIT]; - char basename[255]; - - get_streamfile_basename(streamFile, basename, sizeof(basename)); - snprintf(filename,sizeof(filename), "nuSound2ToneStr%s.bin", basename); - - nameFile = open_streamfile_by_filename(streamFile, filename); - if (nameFile && read_32bit(0x08, nameFile) == total_subsongs) { - off_t header_start = 0x40; /* first name is bank name */ - char name1[0x20+1] = {0}; - char name2[0x20+1] = {0}; - - name_size = 0x20; - name_offset = header_start + (target_subsong-1)*(name_size*2); - - read_string(name1,name_size, name_offset + 0x00, nameFile); /* internal name */ - read_string(name2,name_size, name_offset + 0x20, nameFile); /* file name */ - //todo some filenames use shift-jis, not sure what to do - - snprintf(name,sizeof(name), "%s/%s", name1,name2); - } - close_streamfile(nameFile); - } - - /* init the VGMSTREAM */ - vgmstream = init_vgmstream_function(temp_sf); - if (!vgmstream) goto fail; - - vgmstream->stream_size = get_streamfile_size(temp_sf); - vgmstream->num_streams = total_subsongs; - if (name[0] != '\0') - strcpy(vgmstream->stream_name, name); - - close_streamfile(temp_sf); - return vgmstream; - -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} - -/* *********************************************************** */ - -static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext) { - STREAMFILE *new_sf = NULL; - STREAMFILE *multi_sf[2] = {0}; - - multi_sf[0] = open_wrap_streamfile(sf); - multi_sf[0] = open_clamp_streamfile_f(multi_sf[0], header_offset, header_size); - multi_sf[1] = open_wrap_streamfile(sf); - multi_sf[1] = open_clamp_streamfile_f(multi_sf[1], stream_offset, stream_size); - new_sf = open_multifile_streamfile_f(multi_sf, 2); - new_sf = open_fakename_streamfile_f(new_sf, NULL, fake_ext); - return new_sf; -} - -/* *********************************************************** */ - -//todo could be simplified - -/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */ -VGMSTREAM * init_vgmstream_nub_wav(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - if (!check_extensions(streamFile, "wav,lwav")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x77617600) /* "wav\0" "*/ - goto fail; - - if (read_16bitBE(0xBC+0x00,streamFile) != 0x0001) /* mini "fmt" chunk */ - goto fail; - - loop_flag = read_32bitBE(0x24,streamFile); - channel_count = read_16bitBE(0xBC+0x02,streamFile); - start_offset = 0xD0; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xBC+0x04,streamFile); - vgmstream->num_samples = pcm_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count, 16); - vgmstream->loop_start_sample = pcm_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count, 16); - vgmstream->loop_end_sample = pcm_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count, 16); - - vgmstream->coding_type = coding_PCM16BE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub vag - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (PS3)] */ -VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - if ( !check_extensions(streamFile, "vag")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x76616700) /* "vag\0" */ - goto fail; - - loop_flag = read_32bitBE(0x24,streamFile); - channel_count = 1; /* dual file stereo */ - start_offset = 0xC0; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xBC,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count); - vgmstream->loop_start_sample = ps_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count); - vgmstream->loop_end_sample = ps_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count); - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_none; - vgmstream->allow_dual_stereo = 1; - - if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub at3 - from Namco NUB archives [Ridge Racer 7 (PS3), Katamari Forever (PS3)] */ -VGMSTREAM * init_vgmstream_nub_at3(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t subfile_offset = 0; - size_t subfile_size = 0; - - - /* checks */ - if (!check_extensions(streamFile,"at3")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x61743300) /* "at3\0" */ - goto fail; - - /* mini fmt+fact header, we can use RIFF anyway */ - subfile_offset = 0x100; - subfile_size = read_32bitLE(subfile_offset + 0x04, streamFile) + 0x08; /* RIFF size */ - - temp_sf = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); - if (!temp_sf) goto fail; - - vgmstream = init_vgmstream_riff(temp_sf); - if (!vgmstream) goto fail; - - close_streamfile(temp_sf); - return vgmstream; -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} - - -/* .nub xma - from Namco NUB archives [Ridge Racer 6 (X360), Tekken 6 (X360), Galaga Legions DX (X360)] */ -VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, chunk_offset; - size_t data_size, chunk_size, header_size; - int loop_flag, channel_count, sample_rate, nus_codec; - int num_samples, loop_start_sample, loop_end_sample; - - - /* checks */ - if (!check_extensions(streamFile,"xma")) - goto fail; - - if (read_32bitBE(0x00,streamFile) == 0x786D6100) { /* "xma\0" */ - /* nub v2.1 */ - nus_codec = read_32bitBE(0x0C,streamFile); - data_size = read_32bitBE(0x14,streamFile); - header_size = read_32bitBE(0x1c,streamFile); - chunk_offset = 0xBC; - chunk_size = read_32bitBE(0x24,streamFile); - } - else if (read_32bitBE(0x08,streamFile) == 0 && read_32bitBE(0x0c,streamFile) == 0) { - /* nub v2.0 from Ridge Racer 6 */ - nus_codec = read_32bitBE(0x08,streamFile); - data_size = read_32bitBE(0x10,streamFile); - header_size = read_32bitBE(0x18,streamFile); - chunk_offset = 0xAC; - chunk_size = header_size; - } - else { - goto fail; - } - - start_offset = align_size_to_block(chunk_offset + header_size, 0x10); - - if (nus_codec == 0x00) { /* XMA1 "fmt " */ - int loop_start_b, loop_end_b, loop_subframe; - - xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 1); - - { - ms_sample_data msd = {0}; - - msd.xma_version = 1; - msd.channels = channel_count; - msd.data_offset = start_offset; - msd.data_size = data_size; - msd.loop_flag = loop_flag; - msd.loop_start_b= loop_start_b; - msd.loop_end_b = loop_end_b; - msd.loop_start_subframe = loop_subframe & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ - msd.loop_end_subframe = loop_subframe >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ - msd.chunk_offset= chunk_offset; - - xma_get_samples(&msd, streamFile); - - num_samples = msd.num_samples; - loop_start_sample = msd.loop_start_sample; - loop_end_sample = msd.loop_end_sample; - } - } - else if (nus_codec == 0x04) { /* "XMA2" */ - xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample); - } - else if (nus_codec == 0x08) { /* XMA2 "fmt " */ - channel_count = read_16bitBE(chunk_offset+0x02,streamFile); - sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); - xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); - } - else { - goto fail; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - -#ifdef VGM_USE_FFMPEG - { - uint8_t buf[0x100]; - size_t bytes; - - if (nus_codec == 0x04) { - bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); - } else { - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); - } - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); - if ( !vgmstream->codec_data ) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); /* samples needs adjustment */ - } -#else - goto fail; -#endif - - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub idsp - from Namco NUB archives [Soul Calibur Legends (Wii), Sky Crawlers: Innocent Aces (Wii)] */ -VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - /* checks */ - if ( !check_extensions(streamFile,"idsp") ) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x69647370) /* "idsp" */ - goto fail; - if (read_32bitBE(0xBC,streamFile) != 0x49445350) /* "IDSP" (actual header) */ - goto fail; - - loop_flag = read_32bitBE(0x20,streamFile); - channel_count = read_32bitBE(0xC4,streamFile); - if (channel_count > 8) goto fail; - start_offset = 0x100 + (channel_count * 0x60); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xC8,streamFile); - vgmstream->num_samples = dsp_bytes_to_samples(read_32bitBE(0x14,streamFile),channel_count); - vgmstream->loop_start_sample = read_32bitBE(0xD0,streamFile); - vgmstream->loop_end_sample = read_32bitBE(0xD4,streamFile); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitBE(0xD8,streamFile); - if (vgmstream->interleave_block_size == 0) - vgmstream->interleave_block_size = read_32bitBE(0x14,streamFile) / channel_count; - - dsp_read_coefs_be(vgmstream,streamFile,0x118,0x60); - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub is14 - from Namco NUB archives [Tales of Vesperia (PS3)] */ -VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t header_offset, stream_offset; - size_t header_size, stream_size, sdat_size; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - if (!check_extensions(streamFile,"is14")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69733134) /* "is14" */ - goto fail; - - if (guess_endianness32bit(0x1c, streamFile)) { - read_32bit = read_32bitBE; - } else{ - read_32bit = read_32bitLE; - } - - - /* paste header+data together and pass to meta */ - header_offset = 0xBC; - header_size = read_32bit(0x1c, streamFile); - - /* size at 0x14 is padded, find "sdat" size BE (may move around) */ - if (!find_chunk_riff_be(streamFile, 0x73646174, 0xbc+0x0c, header_size - 0x0c, NULL, &sdat_size)) - goto fail; - stream_offset = align_size_to_block(header_offset + header_size, 0x10); - stream_size = sdat_size; - - - temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "bnsf"); - if (!temp_sf) goto fail; - - vgmstream = init_vgmstream_bnsf(temp_sf); - if (!vgmstream) goto fail; - - close_streamfile(temp_sf); - return vgmstream; -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + + +static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext); + +/* .nub - Namco's nu Sound v2 audio container */ +VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t name_offset = 0; + size_t name_size = 0; + int total_subsongs, target_subsong = streamFile->stream_index; + uint32_t version, codec; + const char* fake_ext; + VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *) = NULL; + char name[STREAM_NAME_SIZE] = {0}; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "nub")) + goto fail; + + version = read_32bitBE(0x00,streamFile); + if (version != 0x00020000 && /* v2.0 (rare, ex. Ridge Race 6 (X360)) */ + version != 0x00020100 && /* v2.1 (common) */ + version != 0x01020100) /* same but LE? (seen in PSP games, not PS4) */ + goto fail; + if (read_32bitBE(0x04,streamFile) != 0x00000000) /* null */ + goto fail; + + /* sometimes LE [Soul Calibur: Broken Destiny (PSP), Tales of Vesperia (PS4) */ + if (guess_endianness32bit(0x10, streamFile)) { + read_32bit = read_32bitBE; + } else{ + read_32bit = read_32bitLE; + } + + /* parse TOC */ + { + off_t offset, data_start, header_start; + off_t header_offset, subheader_start, stream_offset; + size_t header_size, subheader_size, stream_size; + + /* - base header */ + /* 0x08: file id/number (can be 0 = first) */ + total_subsongs = read_32bit(0x0c, streamFile); /* .nub with 0 files do exist */ + data_start = read_32bit(0x10, streamFile); /* exists even with 0 files */ + /* 0x14: data end (may have padding) */ + header_start = read_32bit(0x18, streamFile); + /* 0x1c: header end */ + + /* probably means "header end" in v2.0 */ + if (version == 0x00020000) { + data_start = align_size_to_block(data_start, 0x800); + } + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + offset = read_32bit(header_start + (target_subsong-1)*0x04, streamFile); + + /* .nus have all headers first then all data, but extractors often just paste them together, + * so we'll combine header+data on the fly to make them playable with existing parsers. + * Formats inside .nub don't exist as external files, so they could be extracted in various + * ways that we'll try to match (though BNSF can be found as header+data in some bigfiles too). */ + + header_offset = offset; + + /* - extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */ + if (version != 0x00020000) + offset += 0x04; /* skip but not in v2.0 */ + + /* - file header */ + /* 0x00: config? */ + /* 0x04: header id/number */ + codec = (uint32_t)read_32bit(offset + 0x08, streamFile); + /* 0x0c: null */ + stream_size = read_32bit(offset + 0x10, streamFile); /* 0x10 aligned */ + stream_offset = read_32bit(offset + 0x14, streamFile) + data_start; + subheader_size = read_32bit(offset + 0x18, streamFile); + /* rest looks like config/volumes/etc */ + + if (version == 0x00020000) + subheader_start = 0xAC; + else + subheader_start = 0xBC; + header_size = align_size_to_block(subheader_start + subheader_size, 0x10); + + switch(codec) { + case 0x00: /* (none) (xma1) */ + fake_ext = "xma"; + init_vgmstream_function = init_vgmstream_nub_xma; + break; + + case 0x01: /* "wav\0" */ + fake_ext = "wav"; + init_vgmstream_function = init_vgmstream_nub_wav; + break; + + case 0x02: /* "vag\0" */ + fake_ext = "vag"; + init_vgmstream_function = init_vgmstream_nub_vag; + break; + + case 0x03: /* "at3\0" */ + fake_ext = "at3"; + init_vgmstream_function = init_vgmstream_nub_at3; + break; + + case 0x04: /* "xma\0" (xma2 old) */ + case 0x08: /* "xma\0" (xma2 new) */ + fake_ext = "xma"; + init_vgmstream_function = init_vgmstream_nub_xma; + break; + + case 0x06: /* "idsp" */ + fake_ext = "idsp"; + init_vgmstream_function = init_vgmstream_nub_idsp; + break; + + case 0x07: /* "is14" */ + fake_ext = "is14"; + init_vgmstream_function = init_vgmstream_nub_is14; + break; + + case 0x05: + default: + VGM_LOG("NUB: unknown codec %x\n", codec); + goto fail; + } + + //;VGM_LOG("NUB: subfile header=%lx + %x, offset=%lx + %x\n", header_offset, header_size, stream_offset, stream_size); + + temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, fake_ext); + if (!temp_sf) goto fail; + } + + /* get names */ + { + /* file names are in a companion file, rarely [Noby Noby Boy (PS3)] */ + STREAMFILE *nameFile = NULL; + char filename[PATH_LIMIT]; + char basename[255]; + + get_streamfile_basename(streamFile, basename, sizeof(basename)); + snprintf(filename,sizeof(filename), "nuSound2ToneStr%s.bin", basename); + + nameFile = open_streamfile_by_filename(streamFile, filename); + if (nameFile && read_32bit(0x08, nameFile) == total_subsongs) { + off_t header_start = 0x40; /* first name is bank name */ + char name1[0x20+1] = {0}; + char name2[0x20+1] = {0}; + + name_size = 0x20; + name_offset = header_start + (target_subsong-1)*(name_size*2); + + read_string(name1,name_size, name_offset + 0x00, nameFile); /* internal name */ + read_string(name2,name_size, name_offset + 0x20, nameFile); /* file name */ + //todo some filenames use shift-jis, not sure what to do + + snprintf(name,sizeof(name), "%s/%s", name1,name2); + } + close_streamfile(nameFile); + } + + /* init the VGMSTREAM */ + vgmstream = init_vgmstream_function(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->stream_size = get_streamfile_size(temp_sf); + vgmstream->num_streams = total_subsongs; + if (name[0] != '\0') + strcpy(vgmstream->stream_name, name); + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + +/* *********************************************************** */ + +static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext) { + STREAMFILE *new_sf = NULL; + STREAMFILE *multi_sf[2] = {0}; + + multi_sf[0] = open_wrap_streamfile(sf); + multi_sf[0] = open_clamp_streamfile_f(multi_sf[0], header_offset, header_size); + multi_sf[1] = open_wrap_streamfile(sf); + multi_sf[1] = open_clamp_streamfile_f(multi_sf[1], stream_offset, stream_size); + new_sf = open_multifile_streamfile_f(multi_sf, 2); + new_sf = open_fakename_streamfile_f(new_sf, NULL, fake_ext); + return new_sf; +} + +/* *********************************************************** */ + +//todo could be simplified + +/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */ +VGMSTREAM * init_vgmstream_nub_wav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + + + /* checks */ + if (!check_extensions(streamFile, "wav,lwav")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x77617600) /* "wav\0" "*/ + goto fail; + + if (read_16bitBE(0xBC+0x00,streamFile) != 0x0001) /* mini "fmt" chunk */ + goto fail; + + loop_flag = read_32bitBE(0x24,streamFile); + channel_count = read_16bitBE(0xBC+0x02,streamFile); + start_offset = 0xD0; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = read_32bitBE(0xBC+0x04,streamFile); + vgmstream->num_samples = pcm_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count, 16); + vgmstream->loop_start_sample = pcm_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count, 16); + vgmstream->loop_end_sample = pcm_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count, 16); + + vgmstream->coding_type = coding_PCM16BE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub vag - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (PS3)] */ +VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + + + /* checks */ + if ( !check_extensions(streamFile, "vag")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x76616700) /* "vag\0" */ + goto fail; + + loop_flag = read_32bitBE(0x24,streamFile); + channel_count = 1; /* dual file stereo */ + start_offset = 0xC0; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = read_32bitBE(0xBC,streamFile); + vgmstream->num_samples = ps_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count); + vgmstream->loop_start_sample = ps_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count); + vgmstream->loop_end_sample = ps_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count); + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_none; + vgmstream->allow_dual_stereo = 1; + + if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub at3 - from Namco NUB archives [Ridge Racer 7 (PS3), Katamari Forever (PS3)] */ +VGMSTREAM * init_vgmstream_nub_at3(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t subfile_offset = 0; + size_t subfile_size = 0; + + + /* checks */ + if (!check_extensions(streamFile,"at3")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x61743300) /* "at3\0" */ + goto fail; + + /* mini fmt+fact header, we can use RIFF anyway */ + subfile_offset = 0x100; + subfile_size = read_32bitLE(subfile_offset + 0x04, streamFile) + 0x08; /* RIFF size */ + + temp_sf = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_riff(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + + +/* .nub xma - from Namco NUB archives [Ridge Racer 6 (X360), Tekken 6 (X360), Galaga Legions DX (X360)] */ +VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, chunk_offset; + size_t data_size, chunk_size, header_size; + int loop_flag, channel_count, sample_rate, nus_codec; + int num_samples, loop_start_sample, loop_end_sample; + + + /* checks */ + if (!check_extensions(streamFile,"xma")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x786D6100) { /* "xma\0" */ + /* nub v2.1 */ + nus_codec = read_32bitBE(0x0C,streamFile); + data_size = read_32bitBE(0x14,streamFile); + header_size = read_32bitBE(0x1c,streamFile); + chunk_offset = 0xBC; + chunk_size = read_32bitBE(0x24,streamFile); + } + else if (read_32bitBE(0x08,streamFile) == 0 && read_32bitBE(0x0c,streamFile) == 0) { + /* nub v2.0 from Ridge Racer 6 */ + nus_codec = read_32bitBE(0x08,streamFile); + data_size = read_32bitBE(0x10,streamFile); + header_size = read_32bitBE(0x18,streamFile); + chunk_offset = 0xAC; + chunk_size = header_size; + } + else { + goto fail; + } + + start_offset = align_size_to_block(chunk_offset + header_size, 0x10); + + if (nus_codec == 0x00) { /* XMA1 "fmt " */ + int loop_start_b, loop_end_b, loop_subframe; + + xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 1); + + { + ms_sample_data msd = {0}; + + msd.xma_version = 1; + msd.channels = channel_count; + msd.data_offset = start_offset; + msd.data_size = data_size; + msd.loop_flag = loop_flag; + msd.loop_start_b= loop_start_b; + msd.loop_end_b = loop_end_b; + msd.loop_start_subframe = loop_subframe & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ + msd.loop_end_subframe = loop_subframe >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ + msd.chunk_offset= chunk_offset; + + xma_get_samples(&msd, streamFile); + + num_samples = msd.num_samples; + loop_start_sample = msd.loop_start_sample; + loop_end_sample = msd.loop_end_sample; + } + } + else if (nus_codec == 0x04) { /* "XMA2" */ + xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample); + } + else if (nus_codec == 0x08) { /* XMA2 "fmt " */ + channel_count = read_16bitBE(chunk_offset+0x02,streamFile); + sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); + xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); + } + else { + goto fail; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + +#ifdef VGM_USE_FFMPEG + { + uint8_t buf[0x100]; + size_t bytes; + + if (nus_codec == 0x04) { + bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); + } else { + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); + } + vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); + if ( !vgmstream->codec_data ) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); /* samples needs adjustment */ + } +#else + goto fail; +#endif + + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub idsp - from Namco NUB archives [Soul Calibur Legends (Wii), Sky Crawlers: Innocent Aces (Wii)] */ +VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + + /* checks */ + if ( !check_extensions(streamFile,"idsp") ) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x69647370) /* "idsp" */ + goto fail; + if (read_32bitBE(0xBC,streamFile) != 0x49445350) /* "IDSP" (actual header) */ + goto fail; + + loop_flag = read_32bitBE(0x20,streamFile); + channel_count = read_32bitBE(0xC4,streamFile); + if (channel_count > 8) goto fail; + start_offset = 0x100 + (channel_count * 0x60); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = read_32bitBE(0xC8,streamFile); + vgmstream->num_samples = dsp_bytes_to_samples(read_32bitBE(0x14,streamFile),channel_count); + vgmstream->loop_start_sample = read_32bitBE(0xD0,streamFile); + vgmstream->loop_end_sample = read_32bitBE(0xD4,streamFile); + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = read_32bitBE(0xD8,streamFile); + if (vgmstream->interleave_block_size == 0) + vgmstream->interleave_block_size = read_32bitBE(0x14,streamFile) / channel_count; + + dsp_read_coefs_be(vgmstream,streamFile,0x118,0x60); + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub is14 - from Namco NUB archives [Tales of Vesperia (PS3)] */ +VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t header_offset, stream_offset; + size_t header_size, stream_size, sdat_size; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile,"is14")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69733134) /* "is14" */ + goto fail; + + if (guess_endianness32bit(0x1c, streamFile)) { + read_32bit = read_32bitBE; + } else{ + read_32bit = read_32bitLE; + } + + + /* paste header+data together and pass to meta */ + header_offset = 0xBC; + header_size = read_32bit(0x1c, streamFile); + + /* size at 0x14 is padded, find "sdat" size BE (may move around) */ + if (!find_chunk_riff_be(streamFile, 0x73646174, 0xbc+0x0c, header_size - 0x0c, NULL, &sdat_size)) + goto fail; + stream_offset = align_size_to_block(header_offset + header_size, 0x10); + stream_size = sdat_size; + + + temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "bnsf"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_bnsf(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/nus3bank.c b/src/meta/nus3bank.c index 48b749d7..8aa41a79 100644 --- a/src/meta/nus3bank.c +++ b/src/meta/nus3bank.c @@ -1,183 +1,183 @@ -#include "meta.h" -#include "../coding/coding.h" - -typedef enum { /*XMA_RAW, ATRAC3,*/ IDSP, ATRAC9, OPUS, BNSF, /*PCM, XMA_RIFF*/ } nus3bank_codec; - -/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */ -VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0; - size_t name_size = 0, subfile_size = 0; - nus3bank_codec codec; - const char* fake_ext; - int total_subsongs, target_subsong = streamFile->stream_index; - - /* checks */ - if (!check_extensions(streamFile, "nus3bank")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */ - goto fail; - if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */ - goto fail; - - /* parse TOC with all existing chunks and sizes (offsets must be derived) */ - { - int i; - off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */ - size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */ - - for (i = 0; i < chunk_count; i++) { - uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile); - size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile); - - switch(chunk_id) { - case 0x544F4E45: /* "TONE": stream info */ - tone_offset = 0x08 + offset; - break; - case 0x5041434B: /* "PACK": audio streams */ - pack_offset = 0x08 + offset; - break; - - case 0x50524F50: /* "PROP": project info */ - case 0x42494E46: /* "BINF": bank info (filename) */ - case 0x47525020: /* "GRP ": ? */ - case 0x44544F4E: /* "DTON": ? */ - case 0x4D41524B: /* "MARK": ? */ - case 0x4A554E4B: /* "JUNK": padding */ - default: - break; - } - - offset += 0x08 + chunk_size; - } - - if (tone_offset == 0 || pack_offset == 0) { - VGM_LOG("NUS3BANK: chunks found\n"); - goto fail; - } - } - - - /* parse tones */ - { - int i; - uint32_t codec_id = 0; - size_t entries = read_32bitLE(tone_offset+0x00, streamFile); - - /* get actual number of subsongs */ - total_subsongs = 0; - if (target_subsong == 0) target_subsong = 1; - - for (i = 0; i < entries; i++) { - off_t header_offset, header_suboffset, stream_name_offset, stream_offset; - size_t stream_name_size, stream_size; - off_t tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile); - size_t tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile); - - if (tone_header_size <= 0x0c) { - continue; /* ignore non-sounds */ - } - - header_offset = tone_offset + tone_header_offset; - - stream_name_size = read_8bit(header_offset+0x0c, streamFile); /* includes null */ - stream_name_offset = header_offset+0x0d; - header_suboffset = 0x0c + 0x01 + stream_name_size; - if (header_suboffset % 0x04) /* padded */ - header_suboffset += (0x04 - (header_suboffset % 0x04)); - if (read_32bitLE(header_offset+header_suboffset+0x04, streamFile) != 0x08) { - continue; /* ignore non-sounds, too */ - } - - stream_offset = read_32bitLE(header_offset+header_suboffset+0x08, streamFile) + pack_offset; - stream_size = read_32bitLE(header_offset+header_suboffset+0x0c, streamFile); - if (stream_size == 0) { - continue; /* happens in some sfx packs */ - } - - total_subsongs++; - if (total_subsongs == target_subsong) { - //;VGM_LOG("NUS3BANK: subsong header offset %lx\n", header_offset); - subfile_offset = stream_offset; - subfile_size = stream_size; - name_size = stream_name_size; - name_offset = stream_name_offset; - //todo improve, codec may be related to header values at 0x00/0x06/0x08 - codec_id = read_32bitBE(subfile_offset, streamFile); - } - /* continue counting subsongs */ - } - - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - if (subfile_offset == 0 || codec_id == 0) { - VGM_LOG("NUS3BANK: subsong not found\n"); - goto fail; - } - - switch(codec_id) { - case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */ - codec = IDSP; - fake_ext = "idsp"; - break; - case 0x52494646: /* "RIFF" [idolm@ster: Platinum Stars (PS4)] */ - //todo this can be standard PCM RIFF too, ex. Mario Kart Arcade GP DX (PC) (works but should have better detection) - codec = ATRAC9; - fake_ext = "at9"; - break; - case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */ - codec = OPUS; - fake_ext = "opus"; - break; - case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */ - codec = BNSF; - fake_ext = "bnsf"; - break; - default: - VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id); - goto fail; - } - } - - //;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size); - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); - if (!temp_streamFile) goto fail; - - /* init the VGMSTREAM */ - switch(codec) { - case IDSP: - vgmstream = init_vgmstream_idsp_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - case OPUS: - vgmstream = init_vgmstream_opus_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - case ATRAC9: - vgmstream = init_vgmstream_riff(temp_streamFile); - if (!vgmstream) goto fail; - break; - case BNSF: - vgmstream = init_vgmstream_bnsf(temp_streamFile); - if (!vgmstream) goto fail; - break; - default: - goto fail; - } - - vgmstream->num_streams = total_subsongs; - if (name_offset) - read_string(vgmstream->stream_name,name_size, name_offset,streamFile); - - - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + +typedef enum { /*XMA_RAW, ATRAC3,*/ IDSP, ATRAC9, OPUS, BNSF, /*PCM, XMA_RIFF*/ } nus3bank_codec; + +/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */ +VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0; + size_t name_size = 0, subfile_size = 0; + nus3bank_codec codec; + const char* fake_ext; + int total_subsongs, target_subsong = streamFile->stream_index; + + /* checks */ + if (!check_extensions(streamFile, "nus3bank")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ + goto fail; + if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */ + goto fail; + if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */ + goto fail; + + /* parse TOC with all existing chunks and sizes (offsets must be derived) */ + { + int i; + off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */ + size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */ + + for (i = 0; i < chunk_count; i++) { + uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile); + size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile); + + switch(chunk_id) { + case 0x544F4E45: /* "TONE": stream info */ + tone_offset = 0x08 + offset; + break; + case 0x5041434B: /* "PACK": audio streams */ + pack_offset = 0x08 + offset; + break; + + case 0x50524F50: /* "PROP": project info */ + case 0x42494E46: /* "BINF": bank info (filename) */ + case 0x47525020: /* "GRP ": ? */ + case 0x44544F4E: /* "DTON": ? */ + case 0x4D41524B: /* "MARK": ? */ + case 0x4A554E4B: /* "JUNK": padding */ + default: + break; + } + + offset += 0x08 + chunk_size; + } + + if (tone_offset == 0 || pack_offset == 0) { + VGM_LOG("NUS3BANK: chunks found\n"); + goto fail; + } + } + + + /* parse tones */ + { + int i; + uint32_t codec_id = 0; + size_t entries = read_32bitLE(tone_offset+0x00, streamFile); + + /* get actual number of subsongs */ + total_subsongs = 0; + if (target_subsong == 0) target_subsong = 1; + + for (i = 0; i < entries; i++) { + off_t header_offset, header_suboffset, stream_name_offset, stream_offset; + size_t stream_name_size, stream_size; + off_t tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile); + size_t tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile); + + if (tone_header_size <= 0x0c) { + continue; /* ignore non-sounds */ + } + + header_offset = tone_offset + tone_header_offset; + + stream_name_size = read_8bit(header_offset+0x0c, streamFile); /* includes null */ + stream_name_offset = header_offset+0x0d; + header_suboffset = 0x0c + 0x01 + stream_name_size; + if (header_suboffset % 0x04) /* padded */ + header_suboffset += (0x04 - (header_suboffset % 0x04)); + if (read_32bitLE(header_offset+header_suboffset+0x04, streamFile) != 0x08) { + continue; /* ignore non-sounds, too */ + } + + stream_offset = read_32bitLE(header_offset+header_suboffset+0x08, streamFile) + pack_offset; + stream_size = read_32bitLE(header_offset+header_suboffset+0x0c, streamFile); + if (stream_size == 0) { + continue; /* happens in some sfx packs */ + } + + total_subsongs++; + if (total_subsongs == target_subsong) { + //;VGM_LOG("NUS3BANK: subsong header offset %lx\n", header_offset); + subfile_offset = stream_offset; + subfile_size = stream_size; + name_size = stream_name_size; + name_offset = stream_name_offset; + //todo improve, codec may be related to header values at 0x00/0x06/0x08 + codec_id = read_32bitBE(subfile_offset, streamFile); + } + /* continue counting subsongs */ + } + + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + if (subfile_offset == 0 || codec_id == 0) { + VGM_LOG("NUS3BANK: subsong not found\n"); + goto fail; + } + + switch(codec_id) { + case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */ + codec = IDSP; + fake_ext = "idsp"; + break; + case 0x52494646: /* "RIFF" [idolm@ster: Platinum Stars (PS4)] */ + //todo this can be standard PCM RIFF too, ex. Mario Kart Arcade GP DX (PC) (works but should have better detection) + codec = ATRAC9; + fake_ext = "at9"; + break; + case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */ + codec = OPUS; + fake_ext = "opus"; + break; + case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */ + codec = BNSF; + fake_ext = "bnsf"; + break; + default: + VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id); + goto fail; + } + } + + //;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size); + + temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); + if (!temp_streamFile) goto fail; + + /* init the VGMSTREAM */ + switch(codec) { + case IDSP: + vgmstream = init_vgmstream_idsp_nus3(temp_streamFile); + if (!vgmstream) goto fail; + break; + case OPUS: + vgmstream = init_vgmstream_opus_nus3(temp_streamFile); + if (!vgmstream) goto fail; + break; + case ATRAC9: + vgmstream = init_vgmstream_riff(temp_streamFile); + if (!vgmstream) goto fail; + break; + case BNSF: + vgmstream = init_vgmstream_bnsf(temp_streamFile); + if (!vgmstream) goto fail; + break; + default: + goto fail; + } + + vgmstream->num_streams = total_subsongs; + if (name_offset) + read_string(vgmstream->stream_name,name_size, name_offset,streamFile); + + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/sqex_sead.c b/src/meta/sqex_sead.c index 524c4b29..c0e2a75f 100644 --- a/src/meta/sqex_sead.c +++ b/src/meta/sqex_sead.c @@ -1,657 +1,657 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "sqex_sead_streamfile.h" - - -typedef struct { - int big_endian; - - int version; - int is_sab; - int is_mab; - - int total_subsongs; - int target_subsong; - - uint16_t wave_id; - int loop_flag; - int channel_count; - int codec; - int sample_rate; - int loop_start; - int loop_end; - off_t meta_offset; - off_t extradata_offset; - size_t extradata_size; - size_t stream_size; - size_t special_size; - - off_t descriptor_offset; - size_t descriptor_size; - off_t filename_offset; - size_t filename_size; - off_t cuename_offset; - size_t cuename_size; - off_t modename_offset; - size_t modename_size; - off_t instname_offset; - size_t instname_size; - off_t sndname_offset; - size_t sndname_size; - - off_t sections_offset; - off_t snd_offset; - off_t trk_offset; - off_t musc_offset; - off_t inst_offset; - off_t mtrl_offset; - - char readable_name[STREAM_NAME_SIZE]; - -} sead_header; - -static int parse_sead(sead_header *sead, STREAMFILE *sf); - - -/* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */ -VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - sead_header sead = {0}; - off_t start_offset; - int target_subsong = streamFile->stream_index; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - /* .sab: sound/bgm - * .mab: music - * .sbin: Dissidia Opera Omnia .sab */ - if (!check_extensions(streamFile,"sab,mab,sbin")) - goto fail; - - if (read_32bitBE(0x00,streamFile) == 0x73616266) { /* "sabf" */ - sead.is_sab = 1; - } else if (read_32bitBE(0x00,streamFile) == 0x6D616266) { /* "mabf" */ - sead.is_mab = 1; - } else { - /* there are other SEAD files with other chunks but similar formats too */ - goto fail; - } - - sead.big_endian = guess_endianness16bit(0x06,streamFile); /* use some value as no apparent flag */ - if (sead.big_endian) { - read_32bit = read_32bitBE; - read_16bit = read_16bitBE; - } else { - read_32bit = read_32bitLE; - read_16bit = read_16bitLE; - } - - sead.target_subsong = target_subsong; - - if (!parse_sead(&sead, streamFile)) - goto fail; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(sead.channel_count, sead.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = sead.is_sab ? meta_SQEX_SAB : meta_SQEX_MAB; - vgmstream->sample_rate = sead.sample_rate; - vgmstream->num_streams = sead.total_subsongs; - vgmstream->stream_size = sead.stream_size; - strcpy(vgmstream->stream_name, sead.readable_name); - - switch(sead.codec) { - - case 0x01: { /* PCM [Chrono Trigger sfx (PC)] */ - start_offset = sead.extradata_offset + sead.extradata_size; - - vgmstream->coding_type = coding_PCM16LE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - vgmstream->num_samples = pcm_bytes_to_samples(sead.stream_size, vgmstream->channels, 16); - vgmstream->loop_start_sample = sead.loop_start; - vgmstream->loop_end_sample = sead.loop_end; - break; - } - - case 0x02: { /* MSADPCM [Dragon Quest Builders (Vita) sfx] */ - start_offset = sead.extradata_offset + sead.extradata_size; - - /* 0x00 (2): null?, 0x02(2): entry size? */ - vgmstream->coding_type = coding_MSADPCM; - vgmstream->layout_type = layout_none; - vgmstream->frame_size = read_16bit(sead.extradata_offset+0x04,streamFile); - - /* much like AKBs, there are slightly different loop values here, probably more accurate - * (if no loop, loop_end doubles as num_samples) */ - vgmstream->num_samples = msadpcm_bytes_to_samples(sead.stream_size, vgmstream->frame_size, vgmstream->channels); - vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x08, streamFile); //loop_start - vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x0c, streamFile); //loop_end - break; - } - -#ifdef VGM_USE_VORBIS - case 0x03: { /* OGG [Final Fantasy XV Benchmark sfx (PC)] */ - VGMSTREAM *ogg_vgmstream = NULL; - ogg_vorbis_meta_info_t ovmi = {0}; - off_t subfile_offset = sead.extradata_offset + sead.extradata_size; - - ovmi.meta_type = vgmstream->meta_type; - ovmi.total_subsongs = sead.total_subsongs; - ovmi.stream_size = sead.stream_size; - /* post header has some kind of repeated values, config/table? */ - - ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, subfile_offset, &ovmi); - if (ogg_vgmstream) { - ogg_vgmstream->num_streams = vgmstream->num_streams; - ogg_vgmstream->stream_size = vgmstream->stream_size; - strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); - - close_vgmstream(vgmstream); - return ogg_vgmstream; - } - else { - goto fail; - } - - break; - } -#endif - -#ifdef VGM_USE_ATRAC9 - case 0x04: { /* ATRAC9 [Dragon Quest Builders (Vita), Final Fantaxy XV (PS4)] */ - atrac9_config cfg = {0}; - - start_offset = sead.extradata_offset + sead.extradata_size; - /* post header has various typical ATRAC9 values */ - cfg.channels = vgmstream->channels; - cfg.config_data = read_32bit(sead.extradata_offset+0x0c,streamFile); - cfg.encoder_delay = read_32bit(sead.extradata_offset+0x18,streamFile); - - vgmstream->codec_data = init_atrac9(&cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_ATRAC9; - vgmstream->layout_type = layout_none; - - vgmstream->sample_rate = read_32bit(sead.extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */ - vgmstream->num_samples = read_32bit(sead.extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */ - vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x20, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_start - vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x24, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_end - break; - } -#endif - -#ifdef VGM_USE_MPEG - case 0x06: { /* MSF subfile (MPEG mode) [Dragon Quest Builders (PS3)] */ - mpeg_codec_data *mpeg_data = NULL; - mpeg_custom_config cfg = {0}; - - start_offset = sead.extradata_offset + sead.extradata_size; - /* post header is a proper MSF, but sample rate/loops are ignored in favor of SAB's */ - - mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); - if (!mpeg_data) goto fail; - vgmstream->codec_data = mpeg_data; - vgmstream->layout_type = layout_none; - - vgmstream->num_samples = mpeg_bytes_to_samples(sead.stream_size, mpeg_data); - vgmstream->loop_start_sample = sead.loop_start; - vgmstream->loop_end_sample = sead.loop_end; - break; - } -#endif - - case 0x07: { /* HCA subfile [Dissidia Opera Omnia (Mobile), Final Fantaxy XV (PS4)] */ - //todo there is no easy way to use the HCA decoder; try subfile hack for now - VGMSTREAM *temp_vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset = sead.extradata_offset + 0x10; - size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10; - - /* post header: values from the HCA header, in file endianness + HCA header */ - size_t key_start = sead.special_size & 0xff; - size_t header_size = read_16bit(sead.extradata_offset+0x02, streamFile); - int encryption = read_16bit(sead.extradata_offset+0x0c, streamFile); //maybe 8bit? - /* encryption type 0x01 found in Final Fantasy XII TZA (PS4/PC) */ - - temp_streamFile = setup_sqex_sead_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start); - if (!temp_streamFile) goto fail; - - temp_vgmstream = init_vgmstream_hca(temp_streamFile); - if (temp_vgmstream) { - /* loops can be slightly different (~1000 samples) but probably HCA's are more accurate */ - temp_vgmstream->num_streams = vgmstream->num_streams; - temp_vgmstream->stream_size = vgmstream->stream_size; - temp_vgmstream->meta_type = vgmstream->meta_type; - strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); - - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return temp_vgmstream; - } - else { - close_streamfile(temp_streamFile); - goto fail; - } - } - - case 0x00: /* dummy entry */ - default: - VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec); - goto fail; - } - - strcpy(vgmstream->stream_name, sead.readable_name); - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) { - - if (sead->is_sab) { - char descriptor[255], name[255]; - - if (sead->descriptor_size > 255 || sead->sndname_size > 255) goto fail; - - read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); - read_string(name,sead->sndname_size+1,sead->sndname_offset, sf); - - snprintf(buf,buf_size, "%s/%s", descriptor, name); - } - else { - char descriptor[255], name[255], mode[255]; - - if (sead->descriptor_size > 255 || sead->filename_size > 255 || sead->cuename_size > 255 || sead->modename_size > 255) goto fail; - - read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); - //read_string(filename,sead->filename_size+1,sead->filename_offset, sf); /* same as filename, not too interesting */ - if (sead->cuename_offset) - read_string(name,sead->cuename_size+1,sead->cuename_offset, sf); - else if (sead->instname_offset) - read_string(name,sead->instname_size+1,sead->instname_offset, sf); - else - strcpy(name, "?"); - read_string(mode,sead->modename_size+1,sead->modename_offset, sf); - - /* default mode in most files, not very interesting */ - if (strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) - snprintf(buf,buf_size, "%s/%s", descriptor, name); - else - snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); - } - - return; -fail: - VGM_LOG("SEAD: bad name found\n"); -} - -static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - int i, entries, cue, mode, cue_count, mode_count; - off_t entry_offset, cue_offset, mode_offset, name_offset, table_offset; - size_t name_size; - //int wave, wave_count; off_t wave_offset, subtable_offset; uint16_t wave_id; - int name = 0; - - - /* find which name corresponds to our song (mabf can have N subsongs - * and X cues + Y modes and also Z instruments, one of which should reference it) */ - //todo exact name matching unknown, assumes subsong N = name N - - /* parse "musc" (music cue?) */ - entries = read_16bit(sead->musc_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->musc_offset + read_32bit(sead->musc_offset + 0x10 + i*0x04, sf); - - /* 0x00: config? */ - sead->filename_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); - cue_count = read_8bit(entry_offset + 0x04, sf); - mode_count = read_8bit(entry_offset + 0x05, sf); - /* 0x06: some low number? */ - /* 0x07: always 0x80? (apparently not an offset/size) */ - /* 0x08: id? */ - /* 0x0a: 0? */ - /* 0x44: sample rate */ - /* others: unknown/null */ - sead->filename_size = read_8bit(entry_offset + 0x48, sf); - - /* table points to all cue offsets first then all modes offsets */ - table_offset = align_size_to_block(sead->filename_offset + sead->filename_size + 0x01, 0x10); - - /* cue name (ex. "bgm_007_take2" / "bgm_007s" / etc subsongs) */ - for (cue = 0; cue < cue_count; cue++) { - cue_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue*0x04, sf); - - /* 0x00: id? */ - name_offset = cue_offset + read_16bit(cue_offset + 0x02, sf); - name_size = read_8bit(cue_offset + 0x04, sf); - //wave_count = read_8bit(cue_offset + 0x05, sf); - /* 0x06: ? */ - /* 0x0c: num samples */ - /* 0x10: loop start */ - /* 0x14: loop end */ - /* 0x18: flag? */ - /* others: ? */ - - name++; - if (name == sead->target_subsong || cue_count == 1) { - sead->cuename_offset = name_offset; - sead->cuename_size = name_size; - break; - } - -#if 0 //this works for some games like KH3 but not others like FFXII - /* subtable: first N wave refs + ? unk refs (rarely more than 1 each) */ - subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); - - for (wave = 0; wave < wave_count; wave++) { - wave_offset = cue_offset + read_32bit(subtable_offset + wave*0x04, sf); - - /* 0x00: config? */ - /* 0x02: entry size */ - wave_id = read_16bit(wave_offset + 0x04, sf); - /* 0x06: null? */ - /* 0x08: null? */ - /* 0x0c: some id/config? */ - - if (wave_id == sead->wave_id) { - sead->cuename_offset = name_offset; - sead->cuename_size = name_size; - break; - } - } - - if (sead->cuename_offset) - break; -#endif - } - - /* mode name (ex. almost always "Mode" and only 1 entry, rarely "Water" / "Restaurant" / etc) - * no idea how modes are referenced (perhaps manually with in-game events) - * so just a quick hack, only found multiple in FFXV's bgm_gardina */ - if (mode_count == sead->total_subsongs) - mode = sead->target_subsong - 1; - else - mode = 0; - - { //for (mode = 0; mode < mode_count; mode++) { - mode_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue_count*0x04 + mode*0x04, sf); - - /* 0x00: id? */ - name_offset = mode_offset + read_16bit(mode_offset + 0x02, sf); - /* 0x04: mode id */ - name_size = read_8bit(mode_offset + 0x06, sf); - /* 0x08: offset? */ - /* others: floats and stuff */ - - sead->modename_offset = name_offset; - sead->modename_size = name_size; - } - } - - - /* parse "inst" (instruments) */ - entries = read_16bit(sead->inst_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->inst_offset + read_32bit(sead->inst_offset + 0x10 + i*0x04, sf); - - /* 0x00: id? */ - /* 0x02: base size? */ - /* 0x05: count? */ - //wave_count = read_8bit(entry_offset + 0x06, sf); - /* 0x0c: num samples */ - /* 0x10: loop start */ - /* 0x14: loop end */ - /* 0x18: flag? */ - /* others: ? */ - - /* no apparent fields and inst is very rare (ex. KH3 tut) */ - name_offset = entry_offset + 0x30; - name_size = 0x0F; - - name++; - if (name == sead->target_subsong) { - sead->instname_offset = name_offset; - sead->instname_size = name_size; - break; - } - - -#if 0 //not actually tested - if (wave_count != 1) break; /* ? */ - - /* subtable: N wave refs? */ - subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); - - for (wave = 0; wave < wave_count; wave++) { - wave_offset = subtable_offset + read_32bit(subtable_offset + wave*0x04, sf); - - /* 0x00: config? */ - /* 0x02: entry size? */ - wave_id = read_16bit(wave_offset + 0x04, sf); - /* 0x06: ? */ - /* 0x08: id/crc? */ - /* 0x0c: ? */ - /* 0x10: sample rate */ - /* others: null? */ - - if (wave_id == sead->wave_id) { - sead->instname_offset = name_offset; - sead->instname_size = name_size; - break; - } - } - - if (sead->instname_offset) - break; -#endif - } -} - -static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - int i, entries, snd_id, wave_id, snd_found = 0; - size_t size; - off_t entry_offset; - - - //todo looks mostly correct for many subsongs but in rare cases wave_ids aren't referenced - // or maybe id needs another jump (seq?) (ex. DQB se_break_soil, FFXV aircraftzeroone) - - /* parse "trk" (track info) */ - entries = read_16bit(sead->trk_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->trk_offset + read_32bit(sead->trk_offset + 0x10 + i*0x04, sf); - - /* 0x00: type/count? */ - size = read_16bit(entry_offset + 0x02, sf); /* bigger if 'type=03' */ - /* 0x04: trk id? */ - /* 0x04: some id? */ - - if (size > 0x10) { - snd_id = read_8bit(entry_offset + 0x10, sf); - wave_id = read_16bit(entry_offset + 0x11, sf); - } - else { - snd_id = read_16bit(entry_offset + 0x08, sf); - wave_id = read_16bit(entry_offset + 0x0a, sf); - } - - - if (wave_id == sead->wave_id) { - snd_found = 1; - break; - } - } - - if (!snd_found) { - if (sead->total_subsongs == 1) { - snd_id = 0; /* meh */ - VGM_LOG("SEAD: snd_id not found, using first\n"); - } else { - return; - } - } - - - /* parse "snd " (sound info) */ - { - off_t entry_offset = sead->snd_offset + read_32bit(sead->snd_offset + 0x10 + snd_id*0x04, sf); - - /* 0x00: config? */ - sead->sndname_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); - /* 0x04: count of ? */ - /* 0x05: count of ? (0 if no sound exist in file) */ - /* 0x06: some low number? */ - /* 0x07: always 0x80? (apparently not an offset/size) */ - /* 0x08: snd id */ - /* 0x0a: 0? */ - /* 0x0c: 1.0? */ - /* 0x1a: header size? */ - /* 0x1c: 30.0? * */ - /* 0x24: crc/id? */ - /* 0x46: header size? */ - /* 0x4c: header size? */ - - if (sead->version == 1) { - sead->sndname_offset -= 0x10; - sead->sndname_size = read_8bit(entry_offset + 0x08, sf); - } - else { - sead->sndname_size = read_8bit(entry_offset + 0x23, sf); - } - - /* 0x24: unique id? (referenced in "seq" section?) */ - /* others: probably sound config like pan/volume (has floats and stuff) */ - } -} - -static int parse_sead(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - - /** base header **/ - sead->version = read_8bit(0x04, sf); /* usually 0x02, rarely 0x01 (ex FF XV early songs) */ - /* 0x05(1): 0/1? */ - /* 0x06(2): ? (usually 0x10, rarely 0x20) */ - /* 0x08(1): 3/4? */ - sead->descriptor_size = read_8bit(0x09, sf); - /* 0x0a(2): ? */ - if (read_32bit(0x0c, sf) != get_streamfile_size(sf)) - goto fail; - - if (sead->descriptor_size == 0) /* not set when version == 1 */ - sead->descriptor_size = 0x0f; - sead->descriptor_offset = 0x10; /* file descriptor ("BGM", "Music2", "SE", etc, long names are ok) */ - sead->sections_offset = sead->descriptor_offset + (sead->descriptor_size + 0x01); /* string null matters for padding */ - sead->sections_offset = align_size_to_block(sead->sections_offset, 0x10); - - - /** offsets to sections **/ - if (sead->is_sab) { - if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x736E6420) goto fail; /* "snd " (sonds) */ - if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x73657120) goto fail; /* "seq " (unknown) */ - if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x74726B20) goto fail; /* "trk " (unknown) */ - if (read_32bitBE(sead->sections_offset + 0x30, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ - sead->snd_offset = read_32bit(sead->sections_offset + 0x08, sf); - //sead->seq_offset = read_32bit(sead->sections_offset + 0x18, sf); - sead->trk_offset = read_32bit(sead->sections_offset + 0x28, sf); - sead->mtrl_offset = read_32bit(sead->sections_offset + 0x38, sf); - } - else if (sead->is_mab) { - if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x6D757363) goto fail; /* "musc" (cues) */ - if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x696E7374) goto fail; /* "inst" (instruments) */ - if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ - sead->musc_offset = read_32bit(sead->sections_offset + 0x08, sf); - sead->inst_offset = read_32bit(sead->sections_offset + 0x18, sf); - sead->mtrl_offset = read_32bit(sead->sections_offset + 0x28, sf); - } - else { - goto fail; - } - - - /* section format at offset: - * 0x00(2): 0/1? - * 0x02(2): header size? (always 0x10) - * 0x04(2): entries - * 0x06(+): padded to 0x10 - * 0x10 + 0x04*entry: offset to entry from table start (also padded to 0x10 at the end) */ - - - /* find meta_offset in "mtrl" and total subsongs */ - { - int i, entries; - - entries = read_16bit(sead->mtrl_offset+0x04, sf); - - if (sead->target_subsong == 0) sead->target_subsong = 1; - sead->total_subsongs = 0; - sead->meta_offset = 0; - - /* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */ - for (i = 0; i < entries; i++) { - off_t entry_offset = sead->mtrl_offset + read_32bit(sead->mtrl_offset + 0x10 + i*0x04, sf); - - if (read_8bit(entry_offset + 0x05, sf) == 0) { - continue; /* codec 0 when dummy (see stream header) */ - } - - - sead->total_subsongs++; - if (!sead->meta_offset && sead->total_subsongs == sead->target_subsong) { - sead->meta_offset = entry_offset; - } - } - if (sead->meta_offset == 0) goto fail; - /* SAB can contain 0 entries too */ - } - - - /** stream header **/ - /* 0x00(2): 0x00/01? */ - /* 0x02(2): base entry size? (0x20) */ - sead->channel_count = read_8bit(sead->meta_offset + 0x04, sf); - sead->codec = read_8bit(sead->meta_offset + 0x05, sf); - sead->wave_id = read_16bit(sead->meta_offset + 0x06, sf); /* 0..N */ - sead->sample_rate = read_32bit(sead->meta_offset + 0x08, sf); - sead->loop_start = read_32bit(sead->meta_offset + 0x0c, sf); /* in samples but usually ignored */ - - sead->loop_end = read_32bit(sead->meta_offset + 0x10, sf); - sead->extradata_size = read_32bit(sead->meta_offset + 0x14, sf); /* including subfile header, can be 0 */ - sead->stream_size = read_32bit(sead->meta_offset + 0x18, sf); /* not including subfile header */ - sead->special_size = read_32bit(sead->meta_offset + 0x1c, sf); - - sead->loop_flag = (sead->loop_end > 0); - sead->extradata_offset = sead->meta_offset + 0x20; - - - /** info section (get stream name) **/ - if (sead->is_sab) { - parse_sead_sab_name(sead, sf); - } - else if (sead->is_mab) { - parse_sead_mab_name(sead, sf); - } - - build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf); - - return 1; -fail: - return 0; -} +#include "meta.h" +#include "../coding/coding.h" +#include "sqex_sead_streamfile.h" + + +typedef struct { + int big_endian; + + int version; + int is_sab; + int is_mab; + + int total_subsongs; + int target_subsong; + + uint16_t wave_id; + int loop_flag; + int channel_count; + int codec; + int sample_rate; + int loop_start; + int loop_end; + off_t meta_offset; + off_t extradata_offset; + size_t extradata_size; + size_t stream_size; + size_t special_size; + + off_t descriptor_offset; + size_t descriptor_size; + off_t filename_offset; + size_t filename_size; + off_t cuename_offset; + size_t cuename_size; + off_t modename_offset; + size_t modename_size; + off_t instname_offset; + size_t instname_size; + off_t sndname_offset; + size_t sndname_size; + + off_t sections_offset; + off_t snd_offset; + off_t trk_offset; + off_t musc_offset; + off_t inst_offset; + off_t mtrl_offset; + + char readable_name[STREAM_NAME_SIZE]; + +} sead_header; + +static int parse_sead(sead_header *sead, STREAMFILE *sf); + + +/* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */ +VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) { + VGMSTREAM * vgmstream = NULL; + sead_header sead = {0}; + off_t start_offset; + int target_subsong = streamFile->stream_index; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + /* .sab: sound/bgm + * .mab: music + * .sbin: Dissidia Opera Omnia .sab */ + if (!check_extensions(streamFile,"sab,mab,sbin")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x73616266) { /* "sabf" */ + sead.is_sab = 1; + } else if (read_32bitBE(0x00,streamFile) == 0x6D616266) { /* "mabf" */ + sead.is_mab = 1; + } else { + /* there are other SEAD files with other chunks but similar formats too */ + goto fail; + } + + sead.big_endian = guess_endianness16bit(0x06,streamFile); /* use some value as no apparent flag */ + if (sead.big_endian) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + sead.target_subsong = target_subsong; + + if (!parse_sead(&sead, streamFile)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(sead.channel_count, sead.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = sead.is_sab ? meta_SQEX_SAB : meta_SQEX_MAB; + vgmstream->sample_rate = sead.sample_rate; + vgmstream->num_streams = sead.total_subsongs; + vgmstream->stream_size = sead.stream_size; + strcpy(vgmstream->stream_name, sead.readable_name); + + switch(sead.codec) { + + case 0x01: { /* PCM [Chrono Trigger sfx (PC)] */ + start_offset = sead.extradata_offset + sead.extradata_size; + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + vgmstream->num_samples = pcm_bytes_to_samples(sead.stream_size, vgmstream->channels, 16); + vgmstream->loop_start_sample = sead.loop_start; + vgmstream->loop_end_sample = sead.loop_end; + break; + } + + case 0x02: { /* MSADPCM [Dragon Quest Builders (Vita) sfx] */ + start_offset = sead.extradata_offset + sead.extradata_size; + + /* 0x00 (2): null?, 0x02(2): entry size? */ + vgmstream->coding_type = coding_MSADPCM; + vgmstream->layout_type = layout_none; + vgmstream->frame_size = read_16bit(sead.extradata_offset+0x04,streamFile); + + /* much like AKBs, there are slightly different loop values here, probably more accurate + * (if no loop, loop_end doubles as num_samples) */ + vgmstream->num_samples = msadpcm_bytes_to_samples(sead.stream_size, vgmstream->frame_size, vgmstream->channels); + vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x08, streamFile); //loop_start + vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x0c, streamFile); //loop_end + break; + } + +#ifdef VGM_USE_VORBIS + case 0x03: { /* OGG [Final Fantasy XV Benchmark sfx (PC)] */ + VGMSTREAM *ogg_vgmstream = NULL; + ogg_vorbis_meta_info_t ovmi = {0}; + off_t subfile_offset = sead.extradata_offset + sead.extradata_size; + + ovmi.meta_type = vgmstream->meta_type; + ovmi.total_subsongs = sead.total_subsongs; + ovmi.stream_size = sead.stream_size; + /* post header has some kind of repeated values, config/table? */ + + ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, subfile_offset, &ovmi); + if (ogg_vgmstream) { + ogg_vgmstream->num_streams = vgmstream->num_streams; + ogg_vgmstream->stream_size = vgmstream->stream_size; + strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + return ogg_vgmstream; + } + else { + goto fail; + } + + break; + } +#endif + +#ifdef VGM_USE_ATRAC9 + case 0x04: { /* ATRAC9 [Dragon Quest Builders (Vita), Final Fantaxy XV (PS4)] */ + atrac9_config cfg = {0}; + + start_offset = sead.extradata_offset + sead.extradata_size; + /* post header has various typical ATRAC9 values */ + cfg.channels = vgmstream->channels; + cfg.config_data = read_32bit(sead.extradata_offset+0x0c,streamFile); + cfg.encoder_delay = read_32bit(sead.extradata_offset+0x18,streamFile); + + vgmstream->codec_data = init_atrac9(&cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_ATRAC9; + vgmstream->layout_type = layout_none; + + vgmstream->sample_rate = read_32bit(sead.extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */ + vgmstream->num_samples = read_32bit(sead.extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */ + vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x20, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_start + vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x24, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_end + break; + } +#endif + +#ifdef VGM_USE_MPEG + case 0x06: { /* MSF subfile (MPEG mode) [Dragon Quest Builders (PS3)] */ + mpeg_codec_data *mpeg_data = NULL; + mpeg_custom_config cfg = {0}; + + start_offset = sead.extradata_offset + sead.extradata_size; + /* post header is a proper MSF, but sample rate/loops are ignored in favor of SAB's */ + + mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); + if (!mpeg_data) goto fail; + vgmstream->codec_data = mpeg_data; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = mpeg_bytes_to_samples(sead.stream_size, mpeg_data); + vgmstream->loop_start_sample = sead.loop_start; + vgmstream->loop_end_sample = sead.loop_end; + break; + } +#endif + + case 0x07: { /* HCA subfile [Dissidia Opera Omnia (Mobile), Final Fantaxy XV (PS4)] */ + //todo there is no easy way to use the HCA decoder; try subfile hack for now + VGMSTREAM *temp_vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset = sead.extradata_offset + 0x10; + size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10; + + /* post header: values from the HCA header, in file endianness + HCA header */ + size_t key_start = sead.special_size & 0xff; + size_t header_size = read_16bit(sead.extradata_offset+0x02, streamFile); + int encryption = read_16bit(sead.extradata_offset+0x0c, streamFile); //maybe 8bit? + /* encryption type 0x01 found in Final Fantasy XII TZA (PS4/PC) */ + + temp_streamFile = setup_sqex_sead_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start); + if (!temp_streamFile) goto fail; + + temp_vgmstream = init_vgmstream_hca(temp_streamFile); + if (temp_vgmstream) { + /* loops can be slightly different (~1000 samples) but probably HCA's are more accurate */ + temp_vgmstream->num_streams = vgmstream->num_streams; + temp_vgmstream->stream_size = vgmstream->stream_size; + temp_vgmstream->meta_type = vgmstream->meta_type; + strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); + + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return temp_vgmstream; + } + else { + close_streamfile(temp_streamFile); + goto fail; + } + } + + case 0x00: /* dummy entry */ + default: + VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec); + goto fail; + } + + strcpy(vgmstream->stream_name, sead.readable_name); + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + + +static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) { + + if (sead->is_sab) { + char descriptor[255], name[255]; + + if (sead->descriptor_size > 255 || sead->sndname_size > 255) goto fail; + + read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); + read_string(name,sead->sndname_size+1,sead->sndname_offset, sf); + + snprintf(buf,buf_size, "%s/%s", descriptor, name); + } + else { + char descriptor[255], name[255], mode[255]; + + if (sead->descriptor_size > 255 || sead->filename_size > 255 || sead->cuename_size > 255 || sead->modename_size > 255) goto fail; + + read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); + //read_string(filename,sead->filename_size+1,sead->filename_offset, sf); /* same as filename, not too interesting */ + if (sead->cuename_offset) + read_string(name,sead->cuename_size+1,sead->cuename_offset, sf); + else if (sead->instname_offset) + read_string(name,sead->instname_size+1,sead->instname_offset, sf); + else + strcpy(name, "?"); + read_string(mode,sead->modename_size+1,sead->modename_offset, sf); + + /* default mode in most files, not very interesting */ + if (strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) + snprintf(buf,buf_size, "%s/%s", descriptor, name); + else + snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); + } + + return; +fail: + VGM_LOG("SEAD: bad name found\n"); +} + +static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + int i, entries, cue, mode, cue_count, mode_count; + off_t entry_offset, cue_offset, mode_offset, name_offset, table_offset; + size_t name_size; + //int wave, wave_count; off_t wave_offset, subtable_offset; uint16_t wave_id; + int name = 0; + + + /* find which name corresponds to our song (mabf can have N subsongs + * and X cues + Y modes and also Z instruments, one of which should reference it) */ + //todo exact name matching unknown, assumes subsong N = name N + + /* parse "musc" (music cue?) */ + entries = read_16bit(sead->musc_offset + 0x04, sf); + for (i = 0; i < entries; i++) { + entry_offset = sead->musc_offset + read_32bit(sead->musc_offset + 0x10 + i*0x04, sf); + + /* 0x00: config? */ + sead->filename_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); + cue_count = read_8bit(entry_offset + 0x04, sf); + mode_count = read_8bit(entry_offset + 0x05, sf); + /* 0x06: some low number? */ + /* 0x07: always 0x80? (apparently not an offset/size) */ + /* 0x08: id? */ + /* 0x0a: 0? */ + /* 0x44: sample rate */ + /* others: unknown/null */ + sead->filename_size = read_8bit(entry_offset + 0x48, sf); + + /* table points to all cue offsets first then all modes offsets */ + table_offset = align_size_to_block(sead->filename_offset + sead->filename_size + 0x01, 0x10); + + /* cue name (ex. "bgm_007_take2" / "bgm_007s" / etc subsongs) */ + for (cue = 0; cue < cue_count; cue++) { + cue_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue*0x04, sf); + + /* 0x00: id? */ + name_offset = cue_offset + read_16bit(cue_offset + 0x02, sf); + name_size = read_8bit(cue_offset + 0x04, sf); + //wave_count = read_8bit(cue_offset + 0x05, sf); + /* 0x06: ? */ + /* 0x0c: num samples */ + /* 0x10: loop start */ + /* 0x14: loop end */ + /* 0x18: flag? */ + /* others: ? */ + + name++; + if (name == sead->target_subsong || cue_count == 1) { + sead->cuename_offset = name_offset; + sead->cuename_size = name_size; + break; + } + +#if 0 //this works for some games like KH3 but not others like FFXII + /* subtable: first N wave refs + ? unk refs (rarely more than 1 each) */ + subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); + + for (wave = 0; wave < wave_count; wave++) { + wave_offset = cue_offset + read_32bit(subtable_offset + wave*0x04, sf); + + /* 0x00: config? */ + /* 0x02: entry size */ + wave_id = read_16bit(wave_offset + 0x04, sf); + /* 0x06: null? */ + /* 0x08: null? */ + /* 0x0c: some id/config? */ + + if (wave_id == sead->wave_id) { + sead->cuename_offset = name_offset; + sead->cuename_size = name_size; + break; + } + } + + if (sead->cuename_offset) + break; +#endif + } + + /* mode name (ex. almost always "Mode" and only 1 entry, rarely "Water" / "Restaurant" / etc) + * no idea how modes are referenced (perhaps manually with in-game events) + * so just a quick hack, only found multiple in FFXV's bgm_gardina */ + if (mode_count == sead->total_subsongs) + mode = sead->target_subsong - 1; + else + mode = 0; + + { //for (mode = 0; mode < mode_count; mode++) { + mode_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue_count*0x04 + mode*0x04, sf); + + /* 0x00: id? */ + name_offset = mode_offset + read_16bit(mode_offset + 0x02, sf); + /* 0x04: mode id */ + name_size = read_8bit(mode_offset + 0x06, sf); + /* 0x08: offset? */ + /* others: floats and stuff */ + + sead->modename_offset = name_offset; + sead->modename_size = name_size; + } + } + + + /* parse "inst" (instruments) */ + entries = read_16bit(sead->inst_offset + 0x04, sf); + for (i = 0; i < entries; i++) { + entry_offset = sead->inst_offset + read_32bit(sead->inst_offset + 0x10 + i*0x04, sf); + + /* 0x00: id? */ + /* 0x02: base size? */ + /* 0x05: count? */ + //wave_count = read_8bit(entry_offset + 0x06, sf); + /* 0x0c: num samples */ + /* 0x10: loop start */ + /* 0x14: loop end */ + /* 0x18: flag? */ + /* others: ? */ + + /* no apparent fields and inst is very rare (ex. KH3 tut) */ + name_offset = entry_offset + 0x30; + name_size = 0x0F; + + name++; + if (name == sead->target_subsong) { + sead->instname_offset = name_offset; + sead->instname_size = name_size; + break; + } + + +#if 0 //not actually tested + if (wave_count != 1) break; /* ? */ + + /* subtable: N wave refs? */ + subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); + + for (wave = 0; wave < wave_count; wave++) { + wave_offset = subtable_offset + read_32bit(subtable_offset + wave*0x04, sf); + + /* 0x00: config? */ + /* 0x02: entry size? */ + wave_id = read_16bit(wave_offset + 0x04, sf); + /* 0x06: ? */ + /* 0x08: id/crc? */ + /* 0x0c: ? */ + /* 0x10: sample rate */ + /* others: null? */ + + if (wave_id == sead->wave_id) { + sead->instname_offset = name_offset; + sead->instname_size = name_size; + break; + } + } + + if (sead->instname_offset) + break; +#endif + } +} + +static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + int i, entries, snd_id, wave_id, snd_found = 0; + size_t size; + off_t entry_offset; + + + //todo looks mostly correct for many subsongs but in rare cases wave_ids aren't referenced + // or maybe id needs another jump (seq?) (ex. DQB se_break_soil, FFXV aircraftzeroone) + + /* parse "trk" (track info) */ + entries = read_16bit(sead->trk_offset + 0x04, sf); + for (i = 0; i < entries; i++) { + entry_offset = sead->trk_offset + read_32bit(sead->trk_offset + 0x10 + i*0x04, sf); + + /* 0x00: type/count? */ + size = read_16bit(entry_offset + 0x02, sf); /* bigger if 'type=03' */ + /* 0x04: trk id? */ + /* 0x04: some id? */ + + if (size > 0x10) { + snd_id = read_8bit(entry_offset + 0x10, sf); + wave_id = read_16bit(entry_offset + 0x11, sf); + } + else { + snd_id = read_16bit(entry_offset + 0x08, sf); + wave_id = read_16bit(entry_offset + 0x0a, sf); + } + + + if (wave_id == sead->wave_id) { + snd_found = 1; + break; + } + } + + if (!snd_found) { + if (sead->total_subsongs == 1) { + snd_id = 0; /* meh */ + VGM_LOG("SEAD: snd_id not found, using first\n"); + } else { + return; + } + } + + + /* parse "snd " (sound info) */ + { + off_t entry_offset = sead->snd_offset + read_32bit(sead->snd_offset + 0x10 + snd_id*0x04, sf); + + /* 0x00: config? */ + sead->sndname_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); + /* 0x04: count of ? */ + /* 0x05: count of ? (0 if no sound exist in file) */ + /* 0x06: some low number? */ + /* 0x07: always 0x80? (apparently not an offset/size) */ + /* 0x08: snd id */ + /* 0x0a: 0? */ + /* 0x0c: 1.0? */ + /* 0x1a: header size? */ + /* 0x1c: 30.0? * */ + /* 0x24: crc/id? */ + /* 0x46: header size? */ + /* 0x4c: header size? */ + + if (sead->version == 1) { + sead->sndname_offset -= 0x10; + sead->sndname_size = read_8bit(entry_offset + 0x08, sf); + } + else { + sead->sndname_size = read_8bit(entry_offset + 0x23, sf); + } + + /* 0x24: unique id? (referenced in "seq" section?) */ + /* others: probably sound config like pan/volume (has floats and stuff) */ + } +} + +static int parse_sead(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + + /** base header **/ + sead->version = read_8bit(0x04, sf); /* usually 0x02, rarely 0x01 (ex FF XV early songs) */ + /* 0x05(1): 0/1? */ + /* 0x06(2): ? (usually 0x10, rarely 0x20) */ + /* 0x08(1): 3/4? */ + sead->descriptor_size = read_8bit(0x09, sf); + /* 0x0a(2): ? */ + if (read_32bit(0x0c, sf) != get_streamfile_size(sf)) + goto fail; + + if (sead->descriptor_size == 0) /* not set when version == 1 */ + sead->descriptor_size = 0x0f; + sead->descriptor_offset = 0x10; /* file descriptor ("BGM", "Music2", "SE", etc, long names are ok) */ + sead->sections_offset = sead->descriptor_offset + (sead->descriptor_size + 0x01); /* string null matters for padding */ + sead->sections_offset = align_size_to_block(sead->sections_offset, 0x10); + + + /** offsets to sections **/ + if (sead->is_sab) { + if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x736E6420) goto fail; /* "snd " (sonds) */ + if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x73657120) goto fail; /* "seq " (unknown) */ + if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x74726B20) goto fail; /* "trk " (unknown) */ + if (read_32bitBE(sead->sections_offset + 0x30, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ + sead->snd_offset = read_32bit(sead->sections_offset + 0x08, sf); + //sead->seq_offset = read_32bit(sead->sections_offset + 0x18, sf); + sead->trk_offset = read_32bit(sead->sections_offset + 0x28, sf); + sead->mtrl_offset = read_32bit(sead->sections_offset + 0x38, sf); + } + else if (sead->is_mab) { + if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x6D757363) goto fail; /* "musc" (cues) */ + if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x696E7374) goto fail; /* "inst" (instruments) */ + if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ + sead->musc_offset = read_32bit(sead->sections_offset + 0x08, sf); + sead->inst_offset = read_32bit(sead->sections_offset + 0x18, sf); + sead->mtrl_offset = read_32bit(sead->sections_offset + 0x28, sf); + } + else { + goto fail; + } + + + /* section format at offset: + * 0x00(2): 0/1? + * 0x02(2): header size? (always 0x10) + * 0x04(2): entries + * 0x06(+): padded to 0x10 + * 0x10 + 0x04*entry: offset to entry from table start (also padded to 0x10 at the end) */ + + + /* find meta_offset in "mtrl" and total subsongs */ + { + int i, entries; + + entries = read_16bit(sead->mtrl_offset+0x04, sf); + + if (sead->target_subsong == 0) sead->target_subsong = 1; + sead->total_subsongs = 0; + sead->meta_offset = 0; + + /* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */ + for (i = 0; i < entries; i++) { + off_t entry_offset = sead->mtrl_offset + read_32bit(sead->mtrl_offset + 0x10 + i*0x04, sf); + + if (read_8bit(entry_offset + 0x05, sf) == 0) { + continue; /* codec 0 when dummy (see stream header) */ + } + + + sead->total_subsongs++; + if (!sead->meta_offset && sead->total_subsongs == sead->target_subsong) { + sead->meta_offset = entry_offset; + } + } + if (sead->meta_offset == 0) goto fail; + /* SAB can contain 0 entries too */ + } + + + /** stream header **/ + /* 0x00(2): 0x00/01? */ + /* 0x02(2): base entry size? (0x20) */ + sead->channel_count = read_8bit(sead->meta_offset + 0x04, sf); + sead->codec = read_8bit(sead->meta_offset + 0x05, sf); + sead->wave_id = read_16bit(sead->meta_offset + 0x06, sf); /* 0..N */ + sead->sample_rate = read_32bit(sead->meta_offset + 0x08, sf); + sead->loop_start = read_32bit(sead->meta_offset + 0x0c, sf); /* in samples but usually ignored */ + + sead->loop_end = read_32bit(sead->meta_offset + 0x10, sf); + sead->extradata_size = read_32bit(sead->meta_offset + 0x14, sf); /* including subfile header, can be 0 */ + sead->stream_size = read_32bit(sead->meta_offset + 0x18, sf); /* not including subfile header */ + sead->special_size = read_32bit(sead->meta_offset + 0x1c, sf); + + sead->loop_flag = (sead->loop_end > 0); + sead->extradata_offset = sead->meta_offset + 0x20; + + + /** info section (get stream name) **/ + if (sead->is_sab) { + parse_sead_sab_name(sead, sf); + } + else if (sead->is_mab) { + parse_sead_mab_name(sead, sf); + } + + build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf); + + return 1; +fail: + return 0; +} diff --git a/src/meta/thp.c b/src/meta/thp.c index 1244ff58..593f94ac 100644 --- a/src/meta/thp.c +++ b/src/meta/thp.c @@ -1,103 +1,103 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../util.h" - -/* THP (Just play audio from .thp movie file) - by fastelbja */ - -VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile) { - - VGMSTREAM * vgmstream = NULL; - - char filename[PATH_LIMIT]; - off_t start_offset; - - uint32_t maxAudioSize=0; - - uint32_t numComponents; - off_t componentTypeOffset; - off_t componentDataOffset; - - char thpVersion; - - int loop_flag; - int channel_count=-1; - int i; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("thp",filename_extension(filename)) && - strcasecmp("dsp",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x54485000) - goto fail; - - maxAudioSize = read_32bitBE(0x0C,streamFile); - thpVersion = read_8bit(0x06,streamFile); - - if(maxAudioSize==0) // no sound - goto fail; - - loop_flag = 0; // allways unloop - - /* fill in the vital statistics */ - if(thpVersion==0x10) { - start_offset = read_32bitBE(0x24,streamFile); - /* No idea what's up with this */ - if (start_offset == 0) - start_offset = read_32bitBE(0x28,streamFile); - } else - start_offset = read_32bitBE(0x28,streamFile); - - // Get info from the first block - componentTypeOffset = read_32bitBE(0x20,streamFile); - numComponents = read_32bitBE(componentTypeOffset ,streamFile); - componentDataOffset=componentTypeOffset+0x14; - componentTypeOffset+=4; - - for(i=0;ichannels=channel_count; - vgmstream->sample_rate=read_32bitBE(componentDataOffset+4,streamFile); - vgmstream->num_samples=read_32bitBE(componentDataOffset+8,streamFile); - break; - } else { - if(thpVersion==0x10) - componentDataOffset+=0x0c; - else - componentDataOffset+=0x08; - } - } - - /* 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;ich[i].streamfile = file; - } - } - - vgmstream->full_block_size = read_32bitBE(0x18,streamFile); /* block size of current block, changes every time */ - block_update_thp(start_offset,vgmstream); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_blocked_thp; - vgmstream->meta_type = meta_THP; - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../util.h" + +/* THP (Just play audio from .thp movie file) + by fastelbja */ + +VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile) { + + VGMSTREAM * vgmstream = NULL; + + char filename[PATH_LIMIT]; + off_t start_offset; + + uint32_t maxAudioSize=0; + + uint32_t numComponents; + off_t componentTypeOffset; + off_t componentDataOffset; + + char thpVersion; + + int loop_flag; + int channel_count=-1; + int i; + + /* check extension, case insensitive */ + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strcasecmp("thp",filename_extension(filename)) && + strcasecmp("dsp",filename_extension(filename))) goto fail; + + /* check header */ + if (read_32bitBE(0x00,streamFile) != 0x54485000) + goto fail; + + maxAudioSize = read_32bitBE(0x0C,streamFile); + thpVersion = read_8bit(0x06,streamFile); + + if(maxAudioSize==0) // no sound + goto fail; + + loop_flag = 0; // allways unloop + + /* fill in the vital statistics */ + if(thpVersion==0x10) { + start_offset = read_32bitBE(0x24,streamFile); + /* No idea what's up with this */ + if (start_offset == 0) + start_offset = read_32bitBE(0x28,streamFile); + } else + start_offset = read_32bitBE(0x28,streamFile); + + // Get info from the first block + componentTypeOffset = read_32bitBE(0x20,streamFile); + numComponents = read_32bitBE(componentTypeOffset ,streamFile); + componentDataOffset=componentTypeOffset+0x14; + componentTypeOffset+=4; + + for(i=0;ichannels=channel_count; + vgmstream->sample_rate=read_32bitBE(componentDataOffset+4,streamFile); + vgmstream->num_samples=read_32bitBE(componentDataOffset+8,streamFile); + break; + } else { + if(thpVersion==0x10) + componentDataOffset+=0x0c; + else + componentDataOffset+=0x08; + } + } + + /* 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;ich[i].streamfile = file; + } + } + + vgmstream->full_block_size = read_32bitBE(0x18,streamFile); /* block size of current block, changes every time */ + block_update_thp(start_offset,vgmstream); + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_blocked_thp; + vgmstream->meta_type = meta_THP; + + return vgmstream; + + /* clean up anything we may have opened */ +fail: + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +}