VGMStream: Updated libvgmstream code base

Updated VGMStream to r1980-277-g72cb4b89

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-05-09 21:13:13 -07:00
parent ab7c0a0afd
commit 30db270af3
11 changed files with 862 additions and 42 deletions

View file

@ -673,6 +673,8 @@
83852B0B2680247900378854 /* rxws.c in Sources */ = {isa = PBXBuildFile; fileRef = 83852B092680247900378854 /* rxws.c */; }; 83852B0B2680247900378854 /* rxws.c in Sources */ = {isa = PBXBuildFile; fileRef = 83852B092680247900378854 /* rxws.c */; };
83852B0C2680247900378854 /* ads_midway.c in Sources */ = {isa = PBXBuildFile; fileRef = 83852B0A2680247900378854 /* ads_midway.c */; }; 83852B0C2680247900378854 /* ads_midway.c in Sources */ = {isa = PBXBuildFile; fileRef = 83852B0A2680247900378854 /* ads_midway.c */; };
8385D4E6245174C700FF8E67 /* diva.c in Sources */ = {isa = PBXBuildFile; fileRef = 8385D4E2245174C600FF8E67 /* diva.c */; }; 8385D4E6245174C700FF8E67 /* diva.c in Sources */ = {isa = PBXBuildFile; fileRef = 8385D4E2245174C600FF8E67 /* diva.c */; };
838A6FF12DCF0850009CBEE7 /* audiopkg_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 838A6FF02DCF0850009CBEE7 /* audiopkg_streamfile.h */; };
838A6FF22DCF0850009CBEE7 /* audiopkg.c in Sources */ = {isa = PBXBuildFile; fileRef = 838A6FEF2DCF0850009CBEE7 /* audiopkg.c */; };
838BDB681D3AF70D0022CA6F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB671D3AF70D0022CA6F /* libz.tbd */; }; 838BDB681D3AF70D0022CA6F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB671D3AF70D0022CA6F /* libz.tbd */; };
838BDB6A1D3AF7140022CA6F /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB691D3AF7140022CA6F /* libiconv.tbd */; }; 838BDB6A1D3AF7140022CA6F /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB691D3AF7140022CA6F /* libiconv.tbd */; };
838BDB6E1D3B043C0022CA6F /* ffmpeg.c in Sources */ = {isa = PBXBuildFile; fileRef = 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */; }; 838BDB6E1D3B043C0022CA6F /* ffmpeg.c in Sources */ = {isa = PBXBuildFile; fileRef = 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */; };
@ -1629,6 +1631,8 @@
83852B092680247900378854 /* rxws.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rxws.c; sourceTree = "<group>"; }; 83852B092680247900378854 /* rxws.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rxws.c; sourceTree = "<group>"; };
83852B0A2680247900378854 /* ads_midway.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ads_midway.c; sourceTree = "<group>"; }; 83852B0A2680247900378854 /* ads_midway.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ads_midway.c; sourceTree = "<group>"; };
8385D4E2245174C600FF8E67 /* diva.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = diva.c; sourceTree = "<group>"; }; 8385D4E2245174C600FF8E67 /* diva.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = diva.c; sourceTree = "<group>"; };
838A6FEF2DCF0850009CBEE7 /* audiopkg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = audiopkg.c; sourceTree = "<group>"; };
838A6FF02DCF0850009CBEE7 /* audiopkg_streamfile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = audiopkg_streamfile.h; sourceTree = "<group>"; };
838BDB671D3AF70D0022CA6F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 838BDB671D3AF70D0022CA6F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
838BDB691D3AF7140022CA6F /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; 838BDB691D3AF7140022CA6F /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
838BDB6D1D3B043C0022CA6F /* ffmpeg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffmpeg.c; sourceTree = "<group>"; }; 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffmpeg.c; sourceTree = "<group>"; };
@ -2340,6 +2344,8 @@
83AB8C741E8072A100086084 /* astb.c */, 83AB8C741E8072A100086084 /* astb.c */,
8349A8F01FE6257C00E26435 /* astl.c */, 8349A8F01FE6257C00E26435 /* astl.c */,
8306B0D520984590000302D4 /* atsl.c */, 8306B0D520984590000302D4 /* atsl.c */,
838A6FEF2DCF0850009CBEE7 /* audiopkg.c */,
838A6FF02DCF0850009CBEE7 /* audiopkg_streamfile.h */,
83EED5D2203A8BC7008BEB45 /* aus.c */, 83EED5D2203A8BC7008BEB45 /* aus.c */,
83A16D2722D2ADE700B90C4C /* awb.c */, 83A16D2722D2ADE700B90C4C /* awb.c */,
83AA5D201F6E2F9B0020821C /* awc.c */, 83AA5D201F6E2F9B0020821C /* awc.c */,
@ -3069,6 +3075,7 @@
836F6F4D18BDC2190095E648 /* layout.h in Headers */, 836F6F4D18BDC2190095E648 /* layout.h in Headers */,
83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */, 83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */,
83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */, 83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */,
838A6FF12DCF0850009CBEE7 /* audiopkg_streamfile.h in Headers */,
834F7E832C709F5B003AC386 /* apa3_streamfile.h in Headers */, 834F7E832C709F5B003AC386 /* apa3_streamfile.h in Headers */,
83256CDA28666C620036D9C0 /* l2tables.h in Headers */, 83256CDA28666C620036D9C0 /* l2tables.h in Headers */,
83256CC828666C620036D9C0 /* getbits.h in Headers */, 83256CC828666C620036D9C0 /* getbits.h in Headers */,
@ -3608,6 +3615,7 @@
834F7DDD2C7093EA003AC386 /* mpeg_decoder.c in Sources */, 834F7DDD2C7093EA003AC386 /* mpeg_decoder.c in Sources */,
83A21F8B201D8982000F04B9 /* sps_n1.c in Sources */, 83A21F8B201D8982000F04B9 /* sps_n1.c in Sources */,
836F6F9E18BDC2190095E648 /* mus_acm.c in Sources */, 836F6F9E18BDC2190095E648 /* mus_acm.c in Sources */,
838A6FF22DCF0850009CBEE7 /* audiopkg.c in Sources */,
83A16D2B22D2ADE800B90C4C /* awb.c in Sources */, 83A16D2B22D2ADE800B90C4C /* awb.c in Sources */,
834F7EC72C70A786003AC386 /* api_tags.c in Sources */, 834F7EC72C70A786003AC386 /* api_tags.c in Sources */,
831BA6191EAC61A500CF89B0 /* ogl.c in Sources */, 831BA6191EAC61A500CF89B0 /* ogl.c in Sources */,

View file

@ -137,6 +137,11 @@ LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_s
buf_copied += copy_samples; buf_copied += copy_samples;
} }
// detect EOF, to avoid another call to _fill that returns with 0 samples
if (!done && priv->decode_done && priv->buf.consumed >= priv->buf.samples) {
done = true;
}
// TODO improve // TODO improve
priv->dec.buf = buf; priv->dec.buf = buf;
priv->dec.buf_samples = buf_copied; priv->dec.buf_samples = buf_copied;

View file

@ -89,6 +89,7 @@ static const char* extension_list[] = {
"aud", "aud",
"audio", //txth/reserved [Grimm Echoes (Android)] "audio", //txth/reserved [Grimm Echoes (Android)]
"audio_data", "audio_data",
"audiopkg",
"aus", "aus",
"awa", //txth/reserved [Missing Parts Side A (PS2)] "awa", //txth/reserved [Missing Parts Side A (PS2)]
"awb", "awb",
@ -1479,6 +1480,7 @@ static const meta_info meta_info_list[] = {
{meta_SHAA, "Nintendo SHAA header"}, {meta_SHAA, "Nintendo SHAA header"},
{meta_OOR, "age .OOR header"}, {meta_OOR, "age .OOR header"},
{meta_MIO, "Entis .MIO header"}, {meta_MIO, "Entis .MIO header"},
{meta_AUDIOPKG, "Inevitable .AUDIOPKG header"},
}; };
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {

View file

@ -0,0 +1,743 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "../util/endianness.h"
#include "../util/layout_utils.h"
#include "audiopkg_streamfile.h"
// streams are defined as "hot" (memory) + "warm" (memory + stream?) + "cold" (stream) sample 'temperatures'.
// BGM is typically set to either while SFX banks may include hot+cold types.
#define MAX_TEMPERATURES 3
#define MAX_CHANNELS 2
#define MAX_NAME 256
typedef enum { CODEC_NONE, CODEC_PSX, CODEC_XBOX, CODEC_XBOX_PC, CODEC_DSP, CODEC_PCM, CODEC_MP3 } audiopkg_codec_t;
typedef enum { PT_NONE, PT_PS2, PT_XBOX, PT_GC, PT_PC } audiopkg_platform_t;
typedef struct {
audiopkg_platform_t platform;
int version;
bool big_endian;
int total_subsongs;
int target_subsong;
char name[MAX_NAME];
int name_count;
// package info
int descriptors; // total cues
int identifiers; // total strings
uint32_t descriptors_size;
uint32_t strings_size;
uint32_t lipsyncs_size;
uint32_t musicdata_size;
uint32_t breakpoints_size;
int sample_headers[MAX_TEMPERATURES]; // actual stream headers
int sample_indices[MAX_TEMPERATURES]; // pointers to stream headers
int sample_sizes[MAX_TEMPERATURES]; // size per headers (in practice 0x28 or 0x56 for DSP)
// derived package info
uint32_t strings_offset; // c-strings, referenced by identifiers
uint32_t lipsyncs_offset; // big table
uint32_t breakpoints_offset; // strings + data, probably identifiers to be triggered; some strings start with an u8 ID
uint32_t musicdata_offset; // seeks? has N mini tables of count + N time position(?) floats
uint32_t identifiers_index_offset; // identifier N to descriptor index
uint32_t descriptors_index_offset; // points to descriptor (cue)
uint32_t descriptors_offset; // variable-sized cues
uint32_t sample_indices_offset; // index to header
uint32_t sample_headers_offset; // stream info
int sample_indices_count; // total indices
int sample_indices_extras; // extra indices for stereo handling
int sample_headers_count; // total headers
// calculated current stream info
int target_index;
int target_temperature;
uint32_t head_offset;
uint32_t head_size;
// current stream header
audiopkg_codec_t codec;
int type;
int channels;
int sample_rate;
int32_t num_samples;
int32_t loop_start;
int32_t loop_end;
uint32_t stream_offset;
uint32_t stream_size;
uint32_t coefs_offset;
uint32_t coefs_spacing;
uint32_t stream_offset2;
uint32_t stream_size2;
bool is_interleaved;
bool loop_flag;
} audiopkg_header_t;
static bool parse_header(audiopkg_header_t* h, STREAMFILE* sf);
static VGMSTREAM* init_vgmstream_audiopkg_main(STREAMFILE* sf, audiopkg_header_t* h);
/* .AUDIOPKG - from Inevitable / Midway Austin games [Area 51 (multi), The Hobbit (multi)] */
VGMSTREAM* init_vgmstream_audiopkg(STREAMFILE* sf) {
/* checks */
if (!(is_id32be(0x00,sf, "v1.5") || is_id32be(0x00,sf, "v1.6") || is_id32be(0x00,sf, "v1.7") || is_id32be(0x00,sf, "v1.8")))
return NULL;
if (!check_extensions(sf,"audiopkg"))
return NULL;
audiopkg_header_t h = {0};
if (!parse_header(&h, sf))
return NULL;
return init_vgmstream_audiopkg_main(sf, &h);
}
static VGMSTREAM* init_vgmstream_audiopkg_main(STREAMFILE* sf, audiopkg_header_t* h) {
VGMSTREAM* vgmstream = NULL;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(h->channels, h->loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_AUDIOPKG;
vgmstream->sample_rate = h->sample_rate;
vgmstream->num_samples = h->num_samples;
vgmstream->loop_start_sample = h->loop_start;
vgmstream->loop_end_sample = h->loop_end;
vgmstream->stream_size = h->stream_size;
vgmstream->num_streams = h->total_subsongs;
if (h->name[0] != '\0') {
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", h->name);
vgmstream->stream_name[STREAM_NAME_SIZE-1] = '\0';
}
switch(h->codec) {
case CODEC_PCM: // Area 51 (PC)-sfx
if (h->big_endian){
VGM_LOG("AUDIOPKG: unsupported big endian found\n");
goto fail;
}
vgmstream->coding_type = h->big_endian ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
break;
case CODEC_PSX: // The Hobbit (PS2), Area 51 (PS2)
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8000;
break;
case CODEC_XBOX_PC: // The Hobbit (PC)
vgmstream->coding_type = coding_XBOX_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x9000;
break;
case CODEC_XBOX: // Area 51 (Xbox), The Hobbit (Xbox)
vgmstream->coding_type = coding_XBOX_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8000;
break;
case CODEC_DSP: // The Hobbit (GC)-sfx
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8000;
dsp_read_coefs(vgmstream, sf, h->coefs_offset + 0x00, h->coefs_spacing, h->big_endian);
dsp_read_hist (vgmstream, sf, h->coefs_offset + 0x24, h->coefs_spacing, h->big_endian);
break;
#ifdef VGM_USE_MPEG
case CODEC_MP3: { // Area 51 (PC)-music, The Hobbit (GC)-music/voices
// not seen, interleaved uses regular stereo MP3
if (!h->is_interleaved && h->channels > 1) {
VGM_LOG("AUDIOPKG: found non-interleaved MPEG stereo\n");
goto fail;
}
// some The Hobbit (GC) file have issues, probably skipped/synced
// (engine seems to use Miles Sound System for MPEG)
// garbage at frame start, seems consistent
if ((read_u16be(h->stream_offset, sf) & 0xFFFE0) != 0xFFE0
&& (read_u16be(h->stream_offset + 0x61, sf) & 0xFFE0) == 0xFFE0) {
h->stream_offset += 0x61;
h->stream_size -= 0x61;
}
// single a blank frame
if (read_u32be(h->stream_offset, sf) == 0) {
h->stream_offset += 0x1B0;
h->stream_size -= 0x1B0;
}
mpeg_custom_config cfg = {0};
cfg.skip_samples = 0; //?
cfg.data_size = h->stream_size;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_mpeg_custom(sf, h->stream_offset, &vgmstream->coding_type, h->channels, MPEG_STANDARD, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->num_samples = h->num_samples;
break;
}
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, sf, h->stream_offset))
goto fail;
// needed for memory 1ch XBOX_mono
if (!h->is_interleaved) {
vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = 0x00;
}
if (!h->is_interleaved && h->channels > 1) {
vgmstream->ch[0].channel_start_offset = h->stream_offset;
vgmstream->ch[1].channel_start_offset = h->stream_offset2;
vgmstream->ch[0].offset = h->stream_offset;
vgmstream->ch[1].offset = h->stream_offset2;
}
// TODO: handle streamfiles in a cleaner way
// streamed XBOX_mono 0x24 frames can't fit exactly into 0x8000 interleave, and (instead of using 0x9000 like PC) blocks
// work like this: [B1-0x8000-ch1][B1-0x8000-ch2][B2-0x7FF0-ch1 + 0x10 padding][B2-0x7FF0-ch2 + 0x10 padding] xN
// Last frame in B1 is partially cut (since it doesn't fix that block), meaning we need a custom streamfile to read.
if (h->codec == CODEC_XBOX && h->target_temperature == 2) {
vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = 0x00;
for (int ch = 0; ch < h->channels; ch++) {
vgmstream->ch[ch].offset = vgmstream->ch[ch].channel_start_offset = 0;
close_streamfile(vgmstream->ch[ch].streamfile);
vgmstream->ch[ch].streamfile = setup_audiopkg_streamfile(sf, h->stream_offset, h->stream_size, ch, h->channels);
if (!vgmstream->ch[ch].streamfile) goto fail;
}
}
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
// read base header
static bool parse_package_identifier(audiopkg_header_t* h, STREAMFILE* sf) {
uint32_t offset = 0x00;
// 00 version string
// 10 platform string
// 20 build user string
// 30 build date string
// The Hobbit: v1.5 PS2/Xbox/GC, v1.6* PC (actually 1.5)
// Area 51 (beta): v1.6 Xbox, v1.7 PS2
// Area 51 (final): v1.7 PS2/Xbox/GC, v1.8* PC (actually 1.7)
h->version = read_u8(offset + 0x03, sf) - 0x30;
if (h->version < 5 || h->version > 8)
return false;
uint32_t platform = read_u32be(offset + 0x10, sf);
if (platform == get_id32be("Wind")) { // Windows
h->platform = PT_PC;
}
else if (platform == get_id32be("Xbox")) {
h->platform = PT_XBOX;
}
else if (platform == get_id32be("Play")) { // PlayStation II
h->platform = PT_PS2;
}
else if (platform == get_id32be("Game")) { // Gamecube
h->platform = PT_GC;
}
else {
return false;
}
// simplify
if (h->platform == PT_PC && h->version == 6) {
h->version = 5;
}
h->big_endian = (h->platform == PT_GC);
return true;
}
// read main header
static bool parse_package_header(audiopkg_header_t* h, STREAMFILE* sf) {
read_s32_t read_s32 = h->big_endian ? read_s32be : read_s32le;
read_u32_t read_u32 = h->big_endian ? read_u32be : read_u32le;
uint32_t offset = 0x40;
/* global bank config (volumes, pan, pitch, etc), changes a bit between versions */
if (h->version == 5)
offset += 0x60;
else if (h->version == 6)
offset += 0x70;
else if (h->version == 7 || h->version == 8)
offset += 0x80;
else
return false;
//;VGM_LOG("AUDIOPKG: table at %x\n", offset);
/* table values */
h->descriptors = read_s32(offset + 0x00, sf);
h->identifiers = read_s32(offset + 0x04, sf);
h->descriptors_size = read_u32(offset + 0x08, sf);
h->strings_size = read_u32(offset + 0x0c, sf);
h->lipsyncs_size = read_u32(offset + 0x10, sf);
h->musicdata_size = read_u32(offset + 0x14, sf);
h->breakpoints_size = read_u32(offset + 0x18, sf);
h->sample_headers[0] = read_s32(offset + 0x1c, sf);
h->sample_headers[1] = read_s32(offset + 0x20, sf);
h->sample_headers[2] = read_s32(offset + 0x24, sf);
h->sample_indices[0] = read_s32(offset + 0x28, sf);
h->sample_indices[1] = read_s32(offset + 0x2c, sf);
h->sample_indices[2] = read_s32(offset + 0x30, sf);
// 34: compression types x3, useless since it's also defined per stream
h->sample_sizes[0] = read_s32(offset + 0x40, sf);
h->sample_sizes[1] = read_s32(offset + 0x44, sf);
h->sample_sizes[2] = read_s32(offset + 0x48, sf);
offset += 0x4c;
if (h->version >= 6) {
// 4c: V1.6: size? v1.7: always 1?, v1.8: always 0?
offset += 0x04;
}
if (h->sample_indices[1] != 0 || h->sample_headers[1] != 0) {
vgm_logi("AUDIOPKG: 'warm' samples found\n");
return false; //not seen (not implemented?)
}
if ((h->lipsyncs_size && h->musicdata_size) || (h->lipsyncs_size && h->breakpoints_size)) {
vgm_logi("AUDIOPKG: lipsyncs and musicdata/breakpoints found\n");
return false; //unknown order
}
/* tables */
h->strings_offset = offset;
offset += h->strings_size;
h->lipsyncs_offset = offset;
offset += h->lipsyncs_size;
h->breakpoints_offset = offset;
offset += h->breakpoints_size;
h->musicdata_offset = offset;
offset += h->musicdata_size;
h->identifiers_index_offset = offset;
offset += (h->identifiers * 0x08);
h->descriptors_index_offset = offset;
offset += (h->descriptors * 0x04);
h->descriptors_offset = offset;
offset += h->descriptors_size;
// u16 index N in sample-header table, N per hot + warm + cold,
// plus 1 index pointing to end for each defined (for stereo handling)
h->sample_indices_count = h->sample_indices[0] + h->sample_indices[1] + h->sample_indices[2];
h->sample_indices_extras = 0;
for (int i = 0; i < 3; i++) {
if (!h->sample_indices[i])
continue;
h->sample_indices_extras++;
}
h->sample_indices_offset = offset;
uint32_t sample_indices_size = (h->sample_indices_count + h->sample_indices_extras) * 0x02;
offset += sample_indices_size;
h->sample_headers_count = h->sample_headers[0] + h->sample_headers[1] + h->sample_headers[2];
h->sample_headers_offset = offset;
uint32_t sample_headers_size = 0;
for (int i = 0; i < 3; i++) {
sample_headers_size += h->sample_headers[i] * h->sample_sizes[i];
}
offset += sample_headers_size;
// after xN sample headers is variable padding then data start
#if 0
VGM_LOG("AUDIOPKG: data:\n");
VGM_LOG(" descriptors=%i\n", h->descriptors);
VGM_LOG(" identifiers=%i\n", h->identifiers);
VGM_LOG(" strings_offset=%x\n", h->strings_offset);
VGM_LOG(" lipsyncs_offset=%x\n", h->lipsyncs_offset);
VGM_LOG(" breakpoints_offset=%x\n", h->breakpoints_offset);
VGM_LOG(" musicdata_offset=%x\n", h->musicdata_offset);
VGM_LOG(" identifiers_index_offset=%x\n", h->identifiers_index_offset);
VGM_LOG(" descriptors_index_offset=%x\n", h->descriptors_index_offset);
VGM_LOG(" descriptors_offset=%x + %x\n", h->descriptors_offset, h->descriptors_size);
VGM_LOG(" sample_indices_offset=%x + %x (%i + %i + %i)\n", h->sample_indices_offset, sample_indices_size, h->sample_indices[0], h->sample_indices[1], h->sample_indices[2]);
VGM_LOG(" sample_headers_offset=%x + %x (%i + %i + %i)\n", h->sample_headers_offset, sample_headers_size, h->sample_headers[0], h->sample_headers[1], h->sample_headers[2]);
#endif
return true;
}
// calculate subsong info
static bool parse_sample_indices(audiopkg_header_t* h, STREAMFILE* sf) {
read_u16_t read_u16 = h->big_endian ? read_u16be : read_u16le;
if (h->sample_indices_count > h->sample_headers_count) {
vgm_logi("AUDIOPKG: unexpected count\n");
return false;
}
// indices point to headers, but for stereo files there is 1 index but 2 headers,
// so sample_indices_counts are total subsongs rather than sample_headers_count.
h->total_subsongs = h->sample_indices_count;
if (h->total_subsongs <= 0) {
vgm_logi("AUDIOPKG: bank has no subsongs (ignore)\n");
return false;
}
if (h->target_subsong < 1 || h->target_subsong > h->total_subsongs)
return false;
// map target subsong to hot/warm/cold index + offsets
int entries_left = h->target_subsong - 1;
uint32_t target_offset = h->sample_indices_offset;
for (int i = 0; i < 3; i++) {
if (!h->sample_indices[i])
continue;
if (entries_left >= h->sample_indices[i]) {
// not in this section
target_offset += (h->sample_indices[i] + 1) * 0x02;
entries_left -= h->sample_indices[i];
continue;
}
target_offset += entries_left * 0x02;
h->target_temperature = i;
h->target_index = entries_left;
break;
}
// Stereo songs are detected by checking current index vs next index (really).
// There is always +1 extra index to account for this calculation.
int header_index0 = read_u16(target_offset + 0x00, sf);
int header_index1 = read_u16(target_offset + 0x02, sf);
h->channels = (header_index1 - header_index0);
if (h->channels < 1 || h->channels > MAX_CHANNELS) {
VGM_LOG("AUDIOPKG: wrong channels %i (target %x, temp=%i, index=%i)\n", h->channels, target_offset, h->target_temperature, h->target_index);
return false;
}
h->head_size = h->sample_sizes[h->target_temperature];
h->head_offset = h->sample_headers_offset;
for (int i = 0; i < 3; i++) {
if (!h->sample_headers[i])
continue;
if (i < h->target_temperature) {
// not in this section
h->head_offset += h->sample_headers[i] * h->sample_sizes[i];
continue;
}
h->head_offset += header_index0 * h->sample_sizes[i];
break;
}
#if 0
VGM_LOG("AUDIOPKG: subsong:\n");
VGM_LOG(" subsongs=%i / %i\n", h->target_subsong, h->total_subsongs);
VGM_LOG(" target offset=%x\n", target_offset);
VGM_LOG(" header offset=%x + %x\n", h->head_offset, h->head_size);
VGM_LOG(" channels=%i\n", h->channels);
#endif
return true;
}
// read subsong header
static bool parse_sample_header(audiopkg_header_t* h, STREAMFILE* sf) {
read_s32_t read_s32 = h->big_endian ? read_s32be : read_s32le;
read_u32_t read_u32 = h->big_endian ? read_u32be : read_u32le;
h->type = -1;
uint32_t offset = h->head_offset;
// 00: memory offset? (null)
h->stream_offset = read_u32(offset + 0x04, sf); // absolute
h->stream_size = read_u32(offset + 0x08, sf);
// 0c: lipsync offset? (-1 if not set)
// 10: breakpoint offset (-1 if not set)
h->type = read_s32(offset + 0x14, sf);
h->num_samples = read_s32(offset + 0x18, sf);
h->sample_rate = read_s32(offset + 0x1c, sf);
h->loop_start = read_s32(offset + 0x20, sf);
h->loop_end = read_s32(offset + 0x24, sf);
h->coefs_offset = offset + 0x28; // DSP only
h->coefs_spacing = h->head_size;
h->loop_flag = (h->loop_end > 0);
// map codec
switch(h->type) {
case 0x00:
if (h->platform == PT_PS2) {
h->codec = CODEC_PSX;
}
if (h->platform == PT_GC) {
h->codec = CODEC_DSP;
}
if (h->platform == PT_XBOX) {
h->codec = CODEC_XBOX;
}
if (h->platform == PT_PC) {
h->codec = CODEC_XBOX_PC;
}
break;
case 0x01:
h->codec = CODEC_PCM;
break;
case 0x02:
h->codec = CODEC_MP3;
break;
default:
VGM_LOG("AUDIOPKG: unknown codec\n");
return false;
}
// stereo files have 2 sample headers; if both point to the same stream it's interleaved, otherwise dual mono
if (h->channels == 2) {
offset += h->head_size;
h->stream_offset2 = read_u32(offset + 0x04, sf);
h->stream_size2 = read_u32(offset + 0x08, sf);
h->is_interleaved = h->stream_offset == h->stream_offset2;
}
#if 0
VGM_LOG("AUDIOPKG: header %x\n", h->head_offset);
VGM_LOG(" stream=%x / %x\n", h->stream_offset, h->stream_size);
VGM_LOG(" stream2=%x / %x\n", h->stream_offset2, h->stream_size2);
VGM_LOG(" srate=%i\n", h->sample_rate);
VGM_LOG(" samples=%i\n", h->num_samples);
VGM_LOG(" type=%i / interleaved=%i\n", h->type, h->is_interleaved);
#endif
return true;
}
// descriptor have a TLV-like header + flags, then optional size, then payload depending on type
// which is usually simple list pointing to sample indices
static bool parse_names_descriptor(audiopkg_header_t* h, STREAMFILE* sf, uint32_t offset, int depth) {
read_u16_t read_u16 = h->big_endian ? read_u16be : read_u16le;
read_u32_t read_u32 = h->big_endian ? read_u32be : read_u32le;
if (depth > 1) {
VGM_LOG("AUDIOPKG: recursion found\n");
return false;
}
uint16_t descriptor_header = read_u16(offset, sf);
// 02: flags (volume, pan, etc)
uint16_t descriptor_type = (descriptor_header >> 14) & 0x03;
uint16_t descriptor_params = (descriptor_header >> 13) & 0x01;
//uint16_t descriptor_value = (descriptor_header >> 0) & 0x1FFF; //always 0?
//VGM_LOG("AUDIOPKG: descriptor: type=%x, param=%x, value=%x at %x\n", descriptor_type, descriptor_params, descriptor_value, offset);
offset += 0x04;
if (descriptor_params) {
uint16_t params_size = read_u16(offset, sf);
offset += params_size;
}
int items = 0;
switch(descriptor_type) {
case 0x00: // simple cue
items = 1;
break;
case 0x01: // complex cue
items = read_u16(offset + 0x00, sf);
offset += 0x02;
break;
case 0x02: // random list
items = read_u16(offset + 0x00, sf);
offset += 0x02;
offset += 0x08; // up to 64 bits to make a random list
break;
case 0x03: // weighted list
items = read_u16(offset + 0x00, sf);
offset += 0x02;
offset += 0x02 * items; // weights
break;
default:
break;
}
//;VGM_LOG("AUDIOPKG: parse %i items at %x (type=%i)\n", items, offset, descriptor_type);
// parse index list
for (int i = 0; i < items; i++) {
if (descriptor_type == 0x01) {
offset += 0x02; //time?
}
uint16_t index_header = read_u16(offset, sf);
// 02: flags? (unused?)
uint16_t index_type = (index_header >> 14) & 0x03;
uint16_t index_params = (index_header >> 13) & 0x01;
uint16_t index_value = (index_header >> 0) & 0x1FFF;
//;VGM_LOG("AUDIOPKG: index: type=%x, param=%x, value=%x at %x\n", index_type, index_params, index_value, offset);
// index points to another descriptor
if (index_type == 0x03) {
uint32_t descriptor_index_offset = h->descriptors_index_offset + 0x04 * index_value;
uint32_t descriptor_offset = read_u32(descriptor_index_offset, sf);
descriptor_offset += h->descriptors_offset;
return parse_names_descriptor(h, sf, descriptor_offset, depth + 1);
}
// index points to sample (0=hot, 1=warm, 2=cold)
int index_temperature = index_type;
if (index_temperature == h->target_temperature && index_value == h->target_index) {
return true;
}
// skip to next index
offset += 0x04;
if (index_params) { // not seen but happens in theory
uint16_t params_size = read_u16(offset + 0x00, sf);
offset += params_size;
}
}
return false;
}
//todo safeops, avoid recalc lens
static void v_strcat(char* dst, int dst_max, const char* src) {
int dst_len = strlen(dst);
int src_len = strlen(dst);
if (dst_len + src_len > dst_max - 1)
return;
strcat(dst, src);
}
#if 0
static void v_strcpy(char* dst, int dst_max, const char* src) {
int src_len = strlen(dst);
if (src_len > dst_max - 1)
return;
strcpy(dst, src);
}
#endif
// Assign names based on identifiers pointing to our stream.
// In rare cases some streams don't have assigned names, probably an user mistake or just unused
// (ex. The Hobbit's SFX_GUI.AUDIOPKG: LOCKTIMER_TICK + LOCKTIMER_TOCK both point to #20, but the latter should be #21)
static bool parse_names(audiopkg_header_t* h, STREAMFILE* sf) {
read_u16_t read_u16 = h->big_endian ? read_u16be : read_u16le;
read_u32_t read_u32 = h->big_endian ? read_u32be : read_u32le;
uint32_t offset = h->identifiers_index_offset;
char identifier[0x100];
for (int i = 0; i < h->identifiers; i++) {
// identifier index table
uint16_t string_offset = read_u16(offset + 0x00, sf) + h->strings_offset;
uint16_t descriptor_index = read_u16(offset + 0x02, sf);
// 04: reserved (set to some value after load)
// descriptor index table
uint32_t descriptor_index_offset = h->descriptors_index_offset + 0x04 * descriptor_index;
uint32_t descriptor_offset = read_u32(descriptor_index_offset, sf);
uint16_t index_type = descriptor_offset >> 16; //unsure
descriptor_offset += h->descriptors_offset;
if (index_type != 0) {
VGM_LOG("AUDIOPKG: bad index=%i in descriptor %i\n", index_type, descriptor_index);
return false;
}
//read_string(identifier, sizeof(identifier), string_offset, sf);
//;VGM_LOG("AUDIOPKG: name=%s > %i\n", identifier, descriptor_index);
bool string_used = parse_names_descriptor(h, sf, descriptor_offset, 0);
if (string_used) {
if (h->name_count)
v_strcat(h->name, sizeof(h->name), "; ");
read_string(identifier, sizeof(identifier), string_offset, sf);
v_strcat(h->name, sizeof(h->name), identifier);
h->name_count++;
}
offset += 0x08;
}
return true;
}
// AUDIOPKG info:
// - defines a basic "package identifier" and "package header" with common parameters + tables
// - tables define N "identifiers" (cue names)
// - identifiers point to 1 "descriptor" (cue)
// - each descriptor points to N "sample indices"
// - each sample index points to 1 or 2 "sample headers"
// - sample headers have actual stream info
// To play anything devs would call some identifier by name, event style.
// Streams are divided into memory ("hot sample") and stream ("cold sample") data, but both work the same.
static bool parse_header(audiopkg_header_t* h, STREAMFILE* sf) {
h->target_subsong = sf->stream_index;
if (h->target_subsong == 0)
h->target_subsong = 1;
if (!parse_package_identifier(h, sf))
return false;
if (!parse_package_header(h, sf))
return false;
if (!parse_sample_indices(h, sf))
return false;
if (!parse_sample_header(h, sf))
return false;
if (!parse_names(h, sf))
return false;
return true;
}

View file

@ -0,0 +1,35 @@
#ifndef _LRMD_STREAMFILE_H_
#define _LRMD_STREAMFILE_H_
#include "deblock_streamfile.h"
static void block_callback(STREAMFILE* sf, deblock_io_data* data) {
data->block_size = 0x10000;
data->data_size = data->block_size - 0x10;
}
/* Deinterleaves .AUDIOPKG Xbox streams */
static STREAMFILE* setup_audiopkg_streamfile(STREAMFILE* sf, uint32_t stream_offset, uint32_t stream_size, int stream_number, int stream_count) {
STREAMFILE* new_sf = NULL;
// blocks: [B1-0x8000-ch1][B1-0x8000-ch2][B2-0x7FF0-ch1 + 0x10 padding][B2-0x7FF0-ch2 + 0x10 padding] xN
// For now add a deblock of L/R blocks, then another to remove padding.
// Probably should mix into a single deblocker but can't think of anything decent at the moment.
deblock_config_t cfg1 = {0};
cfg1.stream_start = stream_offset;
cfg1.stream_size = stream_size;
cfg1.step_start = stream_number;
cfg1.step_count = stream_count;
cfg1.chunk_size = 0x8000;
deblock_config_t cfg2 = {0};
cfg2.block_callback = block_callback;
//TODO: this SF is handled a bit differently than usual, improve
new_sf = reopen_streamfile(sf, 0); //open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg1);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg2);
return new_sf;
}
#endif

View file

@ -772,6 +772,7 @@ static const hcakey_info hcakey_list[] = {
{0x353e9b0a47ed61bc}, // music_0940001 {0x353e9b0a47ed61bc}, // music_0940001
{0x1b06e24d34d67ac8}, // music_1010001 {0x1b06e24d34d67ac8}, // music_1010001
{0xab02c0d6f229df05}, // music_1010002 {0xab02c0d6f229df05}, // music_1010002
{0x56a96f773f447a7e}, // music_1010003
{0x2a47feac8dc3ca9c}, // music_3010001 {0x2a47feac8dc3ca9c}, // music_3010001
{0x9ebbaf63ffe9d9ef}, // music_3010002 {0x9ebbaf63ffe9d9ef}, // music_3010002
{0xe553dba6592293d8}, // music_3010003 {0xe553dba6592293d8}, // music_3010003
@ -1272,12 +1273,20 @@ static const hcakey_info hcakey_list[] = {
{0x34c0f6db642145a0}, // music_5050307 {0x34c0f6db642145a0}, // music_5050307
{0xb7ecea9165c448da}, // music_5050308 {0xb7ecea9165c448da}, // music_5050308
{0xa5e9bd945c5caf2c}, // music_5050309 {0xa5e9bd945c5caf2c}, // music_5050309
{0x8f88e97a742ec2b6}, // music_5050310
{0xdafc7d4d918a6282}, // music_5050311
{0x9260652a706b3616}, // music_5050312
{0x91ff4aedae9ce2c3}, // music_5050313 {0x91ff4aedae9ce2c3}, // music_5050313
{0xeaaa417505d65dd1}, // music_5050322 {0xeaaa417505d65dd1}, // music_5050322
{0x591899d025c3beb7}, // music_5050323 {0x591899d025c3beb7}, // music_5050323
{0xa57678c62ef99124}, // music_5050324 {0xa57678c62ef99124}, // music_5050324
{0x925f360a8ccb4c32}, // music_5050325 {0x925f360a8ccb4c32}, // music_5050325
{0x2a9281f77161e068}, // music_5050326 {0x2a9281f77161e068}, // music_5050326
{0x3a00b50e407febcb}, // music_5050327
{0x83c86bfce70eebaa}, // music_5050328
{0x250a01be07e87ea4}, // music_5050329
{0x664c54cd6651be76}, // music_5050330
{0x1b51c4bfabf7a714}, // music_5050331
{0x52c250eade92393b}, // music_9010001 {0x52c250eade92393b}, // music_9010001
{0xf66e6bb5b0599b07}, // music_9010002 {0xf66e6bb5b0599b07}, // music_9010002
{0x8582b5a60dbbf948}, // music_9010003 {0x8582b5a60dbbf948}, // music_9010003

View file

@ -1035,4 +1035,6 @@ VGMSTREAM* init_vgmstream_2dx(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ssp(STREAMFILE* sf); VGMSTREAM* init_vgmstream_ssp(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_audiopkg(STREAMFILE* sf);
#endif #endif

View file

@ -5,6 +5,7 @@
#include "../util/text_reader.h" #include "../util/text_reader.h"
#include "../util/endianness.h" #include "../util/endianness.h"
#include "../util/paths.h" #include "../util/paths.h"
#include "../util/companion_files.h"
#define TXT_LINE_MAX 2048 /* probably ~1000 would be ok */ #define TXT_LINE_MAX 2048 /* probably ~1000 would be ok */
#define TXT_LINE_KEY_MAX 128 #define TXT_LINE_KEY_MAX 128
@ -1424,7 +1425,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
} }
/* HEADER/BODY CONFIG */ /* HEADER/BODY CONFIG */
else if (is_string(key,"header_file")) { else if (is_string(key,"header_file") || is_string(key,"head_file")) {
/* first remove old head if needed */ /* first remove old head if needed */
if (txth->sf_head_opened) { if (txth->sf_head_opened) {
@ -1447,12 +1448,17 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
txth->sf_head = open_streamfile_by_ext(txth->sf, (val+2)); txth->sf_head = open_streamfile_by_ext(txth->sf, (val+2));
if (!txth->sf_head) goto fail; if (!txth->sf_head) goto fail;
txth->sf_head_opened = 1; txth->sf_head_opened = true;
}
else if (is_string(val,".txtm")) {
txth->sf_head = read_filemap_file(txth->sf, 0);
if (!txth->sf_head) goto fail;
txth->sf_head_opened = true;
} }
else { /* open file */ else { /* open file */
txth->sf_head = open_path_streamfile(txth->sf, val); txth->sf_head = open_path_streamfile(txth->sf, val);
if (!txth->sf_head) goto fail; if (!txth->sf_head) goto fail;
txth->sf_head_opened = 1; txth->sf_head_opened = true;
} }
} }
else if (is_string(key,"body_file")) { else if (is_string(key,"body_file")) {
@ -1478,12 +1484,17 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
txth->sf_body = open_streamfile_by_ext(txth->sf, (val+2)); txth->sf_body = open_streamfile_by_ext(txth->sf, (val+2));
if (!txth->sf_body) goto fail; if (!txth->sf_body) goto fail;
txth->sf_body_opened = 1; txth->sf_body_opened = true;
}
else if (is_string(val,".txtm")) {
txth->sf_body = read_filemap_file(txth->sf, 0);
if (!txth->sf_body) goto fail;
txth->sf_body_opened = true;
} }
else { /* open file */ else { /* open file */
txth->sf_body = open_path_streamfile(txth->sf, val); txth->sf_body = open_path_streamfile(txth->sf, val);
if (!txth->sf_body) goto fail; if (!txth->sf_body) goto fail;
txth->sf_body_opened = 1; txth->sf_body_opened = true;
} }
/* use body as header when opening a .txth directly to simplify things */ /* use body as header when opening a .txth directly to simplify things */

View file

@ -511,6 +511,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_undefind, init_vgmstream_undefind,
init_vgmstream_oor, init_vgmstream_oor,
init_vgmstream_mio, init_vgmstream_mio,
init_vgmstream_audiopkg,
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
init_vgmstream_agsc, init_vgmstream_agsc,

View file

@ -721,6 +721,7 @@ typedef enum {
meta_SHAA, meta_SHAA,
meta_OOR, meta_OOR,
meta_MIO, meta_MIO,
meta_AUDIOPKG,
} meta_t; } meta_t;
#endif #endif

View file

@ -413,6 +413,7 @@
<string>aud</string> <string>aud</string>
<string>audio</string> <string>audio</string>
<string>audio_data</string> <string>audio_data</string>
<string>audiopkg</string>
<string>aus</string> <string>aus</string>
<string>awa</string> <string>awa</string>
<string>awb</string> <string>awb</string>
@ -719,6 +720,7 @@
<string>omu</string> <string>omu</string>
<string>oor</string> <string>oor</string>
<string>opu</string> <string>opu</string>
<string>opusnx</string>
<string>opusx</string> <string>opusx</string>
<string>oto</string> <string>oto</string>
<string>ovb</string> <string>ovb</string>
@ -754,7 +756,6 @@
<string>rda</string> <string>rda</string>
<string>res</string> <string>res</string>
<string>rkv</string> <string>rkv</string>
<string>rnd</string>
<string>rof</string> <string>rof</string>
<string>rpgmvo</string> <string>rpgmvo</string>
<string>rrds</string> <string>rrds</string>
@ -833,6 +834,7 @@
<string>smk</string> <string>smk</string>
<string>smp</string> <string>smp</string>
<string>smv</string> <string>smv</string>
<string>sn0</string>
<string>snb</string> <string>snb</string>
<string>snd</string> <string>snd</string>
<string>snds</string> <string>snds</string>
@ -852,6 +854,7 @@
<string>srsa</string> <string>srsa</string>
<string>ss2</string> <string>ss2</string>
<string>ssd</string> <string>ssd</string>
<string>ssf</string>
<string>ssm</string> <string>ssm</string>
<string>sspr</string> <string>sspr</string>
<string>ssp</string> <string>ssp</string>
@ -906,7 +909,6 @@
<string>vai</string> <string>vai</string>
<string>vam</string> <string>vam</string>
<string>vas</string> <string>vas</string>
<string>vawx</string>
<string>vb</string> <string>vb</string>
<string>vbk</string> <string>vbk</string>
<string>vbx</string> <string>vbx</string>
@ -989,6 +991,7 @@
<string>xnb</string> <string>xnb</string>
<string>xsh</string> <string>xsh</string>
<string>xsf</string> <string>xsf</string>
<string>xst</string>
<string>xse</string> <string>xse</string>
<string>xsew</string> <string>xsew</string>
<string>xss</string> <string>xss</string>
@ -1398,6 +1401,41 @@
<key>LSTypeIsPackage</key> <key>LSTypeIsPackage</key>
<false/> <false/>
</dict> </dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>psf</string>
<string>minipsf</string>
<string>psf2</string>
<string>minipsf2</string>
<string>ssf</string>
<string>minissf</string>
<string>dsf</string>
<string>minidsf</string>
<string>qsf</string>
<string>miniqsf</string>
<string>gsf</string>
<string>minigsf</string>
<string>ncsf</string>
<string>minincsf</string>
<string>2sf</string>
<string>mini2sf</string>
<string>usf</string>
<string>miniusf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>vg.icns</string>
<key>CFBundleTypeIconSystemGenerated</key>
<integer>1</integer>
<key>CFBundleTypeName</key>
<string>PSF Format Files</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
<dict> <dict>
<key>CFBundleTypeExtensions</key> <key>CFBundleTypeExtensions</key>
<array> <array>
@ -1497,41 +1535,6 @@
<key>LSTypeIsPackage</key> <key>LSTypeIsPackage</key>
<false/> <false/>
</dict> </dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>psf</string>
<string>minipsf</string>
<string>psf2</string>
<string>minipsf2</string>
<string>ssf</string>
<string>minissf</string>
<string>dsf</string>
<string>minidsf</string>
<string>qsf</string>
<string>miniqsf</string>
<string>gsf</string>
<string>minigsf</string>
<string>ncsf</string>
<string>minincsf</string>
<string>2sf</string>
<string>mini2sf</string>
<string>usf</string>
<string>miniusf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>vg.icns</string>
<key>CFBundleTypeIconSystemGenerated</key>
<integer>1</integer>
<key>CFBundleTypeName</key>
<string>PSF Format Files</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
<dict> <dict>
<key>CFBundleTypeExtensions</key> <key>CFBundleTypeExtensions</key>
<array> <array>