Compare commits

...

3 commits

Author SHA1 Message Date
Christopher Snowhill
7839f661a1 Sentry: Update sentry-cocoa to version 8.50.1
Some checks failed
Check if Cog buildable / Build Universal Cog.app (push) Has been cancelled
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-05-10 00:40:24 -07:00
Christopher Snowhill
30db270af3 VGMStream: Updated libvgmstream code base
Updated VGMStream to r1980-277-g72cb4b89

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-05-09 21:13:34 -07:00
Christopher Snowhill
ab7c0a0afd Track loading: Change file import and sorting
Files are now loaded to unique keys, and containers such as playlists
and CUE sheets maintain their file order. Deduplication now only applies
to top level files and not playlist contents. Sorting applies to top
level files, and playlist or container names, but not their contents.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-05-09 21:13:28 -07:00
13 changed files with 919 additions and 141 deletions

View file

@ -6,10 +6,10 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/getsentry/sentry-cocoa.git",
"state" : {
"revision" : "21223d1c864db0561d91f48d80f269a363a1625d",
"version" : "8.47.0"
"revision" : "6c81e671154e63464dd6749b7ba3279dd390a146",
"version" : "8.50.1"
}
}
],
"version" : 2
"version" : 3
}

View file

@ -673,6 +673,8 @@
83852B0B2680247900378854 /* rxws.c in Sources */ = {isa = PBXBuildFile; fileRef = 83852B092680247900378854 /* rxws.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 */; };
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 */; };
838BDB6A1D3AF7140022CA6F /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB691D3AF7140022CA6F /* libiconv.tbd */; };
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>"; };
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>"; };
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; };
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>"; };
@ -2340,6 +2344,8 @@
83AB8C741E8072A100086084 /* astb.c */,
8349A8F01FE6257C00E26435 /* astl.c */,
8306B0D520984590000302D4 /* atsl.c */,
838A6FEF2DCF0850009CBEE7 /* audiopkg.c */,
838A6FF02DCF0850009CBEE7 /* audiopkg_streamfile.h */,
83EED5D2203A8BC7008BEB45 /* aus.c */,
83A16D2722D2ADE700B90C4C /* awb.c */,
83AA5D201F6E2F9B0020821C /* awc.c */,
@ -3069,6 +3075,7 @@
836F6F4D18BDC2190095E648 /* layout.h in Headers */,
83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */,
83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */,
838A6FF12DCF0850009CBEE7 /* audiopkg_streamfile.h in Headers */,
834F7E832C709F5B003AC386 /* apa3_streamfile.h in Headers */,
83256CDA28666C620036D9C0 /* l2tables.h in Headers */,
83256CC828666C620036D9C0 /* getbits.h in Headers */,
@ -3608,6 +3615,7 @@
834F7DDD2C7093EA003AC386 /* mpeg_decoder.c in Sources */,
83A21F8B201D8982000F04B9 /* sps_n1.c in Sources */,
836F6F9E18BDC2190095E648 /* mus_acm.c in Sources */,
838A6FF22DCF0850009CBEE7 /* audiopkg.c in Sources */,
83A16D2B22D2ADE800B90C4C /* awb.c in Sources */,
834F7EC72C70A786003AC386 /* api_tags.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;
}
// 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
priv->dec.buf = buf;
priv->dec.buf_samples = buf_copied;

View file

@ -89,6 +89,7 @@ static const char* extension_list[] = {
"aud",
"audio", //txth/reserved [Grimm Echoes (Android)]
"audio_data",
"audiopkg",
"aus",
"awa", //txth/reserved [Missing Parts Side A (PS2)]
"awb",
@ -1479,6 +1480,7 @@ static const meta_info meta_info_list[] = {
{meta_SHAA, "Nintendo SHAA header"},
{meta_OOR, "age .OOR header"},
{meta_MIO, "Entis .MIO header"},
{meta_AUDIOPKG, "Inevitable .AUDIOPKG header"},
};
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
{0x1b06e24d34d67ac8}, // music_1010001
{0xab02c0d6f229df05}, // music_1010002
{0x56a96f773f447a7e}, // music_1010003
{0x2a47feac8dc3ca9c}, // music_3010001
{0x9ebbaf63ffe9d9ef}, // music_3010002
{0xe553dba6592293d8}, // music_3010003
@ -1272,12 +1273,20 @@ static const hcakey_info hcakey_list[] = {
{0x34c0f6db642145a0}, // music_5050307
{0xb7ecea9165c448da}, // music_5050308
{0xa5e9bd945c5caf2c}, // music_5050309
{0x8f88e97a742ec2b6}, // music_5050310
{0xdafc7d4d918a6282}, // music_5050311
{0x9260652a706b3616}, // music_5050312
{0x91ff4aedae9ce2c3}, // music_5050313
{0xeaaa417505d65dd1}, // music_5050322
{0x591899d025c3beb7}, // music_5050323
{0xa57678c62ef99124}, // music_5050324
{0x925f360a8ccb4c32}, // music_5050325
{0x2a9281f77161e068}, // music_5050326
{0x3a00b50e407febcb}, // music_5050327
{0x83c86bfce70eebaa}, // music_5050328
{0x250a01be07e87ea4}, // music_5050329
{0x664c54cd6651be76}, // music_5050330
{0x1b51c4bfabf7a714}, // music_5050331
{0x52c250eade92393b}, // music_9010001
{0xf66e6bb5b0599b07}, // music_9010002
{0x8582b5a60dbbf948}, // music_9010003

View file

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

View file

@ -5,6 +5,7 @@
#include "../util/text_reader.h"
#include "../util/endianness.h"
#include "../util/paths.h"
#include "../util/companion_files.h"
#define TXT_LINE_MAX 2048 /* probably ~1000 would be ok */
#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 */
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 */
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 */
txth->sf_head = open_streamfile_by_ext(txth->sf, (val+2));
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 */
txth->sf_head = open_path_streamfile(txth->sf, val);
if (!txth->sf_head) goto fail;
txth->sf_head_opened = 1;
txth->sf_head_opened = true;
}
}
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 */
txth->sf_body = open_streamfile_by_ext(txth->sf, (val+2));
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 */
txth->sf_body = open_path_streamfile(txth->sf, val);
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 */

View file

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

View file

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

View file

@ -413,6 +413,7 @@
<string>aud</string>
<string>audio</string>
<string>audio_data</string>
<string>audiopkg</string>
<string>aus</string>
<string>awa</string>
<string>awb</string>
@ -719,6 +720,7 @@
<string>omu</string>
<string>oor</string>
<string>opu</string>
<string>opusnx</string>
<string>opusx</string>
<string>oto</string>
<string>ovb</string>
@ -754,7 +756,6 @@
<string>rda</string>
<string>res</string>
<string>rkv</string>
<string>rnd</string>
<string>rof</string>
<string>rpgmvo</string>
<string>rrds</string>
@ -833,6 +834,7 @@
<string>smk</string>
<string>smp</string>
<string>smv</string>
<string>sn0</string>
<string>snb</string>
<string>snd</string>
<string>snds</string>
@ -852,6 +854,7 @@
<string>srsa</string>
<string>ss2</string>
<string>ssd</string>
<string>ssf</string>
<string>ssm</string>
<string>sspr</string>
<string>ssp</string>
@ -906,7 +909,6 @@
<string>vai</string>
<string>vam</string>
<string>vas</string>
<string>vawx</string>
<string>vb</string>
<string>vbk</string>
<string>vbx</string>
@ -989,6 +991,7 @@
<string>xnb</string>
<string>xsh</string>
<string>xsf</string>
<string>xst</string>
<string>xse</string>
<string>xsew</string>
<string>xss</string>
@ -1398,6 +1401,41 @@
<key>LSTypeIsPackage</key>
<false/>
</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>
<key>CFBundleTypeExtensions</key>
<array>
@ -1497,41 +1535,6 @@
<key>LSTypeIsPackage</key>
<false/>
</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>
<key>CFBundleTypeExtensions</key>
<array>

View file

@ -352,8 +352,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
- (NSArray *)insertURLs:(NSArray *)urls atIndex:(NSInteger)index sort:(BOOL)sort {
__block NSMutableSet *uniqueURLs = [NSMutableSet set];
__block NSMutableArray *expandedURLs = [[NSMutableArray alloc] init];
__block NSMutableArray *containedURLs = [[NSMutableArray alloc] init];
__block NSMutableDictionary *expandedURLs = [[NSMutableDictionary alloc] init];
__block NSMutableDictionary *loadedURLs = [[NSMutableDictionary alloc] init];
__block NSMutableArray *fileURLs = [[NSMutableArray alloc] init];
NSMutableArray *validURLs = [[NSMutableArray alloc] init];
NSMutableArray *folderURLs = [[NSMutableArray alloc] init];
@ -394,22 +394,28 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(isDir == YES) {
// Get subpaths
[[SandboxBroker sharedSandboxBroker] addFolderIfMissing:url];
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]];
NSArray *pathURLs = [self fileURLsAtPath:[url path]];
for(NSURL *url in pathURLs) {
[expandedURLs setValue:url forKey:[url absoluteString]];
}
} else if(addOtherFilesInFolder) {
NSURL *folderUrl = [url URLByDeletingLastPathComponent];
if(![folderURLs containsObject:folderUrl]) {
[[SandboxBroker sharedSandboxBroker] requestFolderForFile:url];
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[folderUrl path]]];
NSArray *pathURLs = [self fileURLsAtPath:[folderUrl path]];
for(NSURL *url in pathURLs) {
[expandedURLs setValue:url forKey:[url absoluteString]];
}
[folderURLs addObject:folderUrl];
}
} else {
[[SandboxBroker sharedSandboxBroker] addFileIfMissing:url];
[expandedURLs addObject:[NSURL fileURLWithPath:[url path]]];
[expandedURLs setValue:url forKey:[url absoluteString]];
}
}
} else {
// Non-file URL..
[expandedURLs addObject:url];
[expandedURLs setValue:url forKey:[url absoluteString]];
}
[pathTask finish];
@ -452,7 +458,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
__block double weakProgressstep = progressstep;
// Container vs non-container url
for(size_t i = 0, j = [expandedURLs count]; i < j; ++i) {
[expandedURLs enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSBlockOperation *op = [[NSBlockOperation alloc] init];
[op addExecutionBlock:^{
@ -464,9 +470,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
pathTask = [containerTask startChildWithOperation:@"Process path as container" description:[NSString stringWithFormat:@"Checking if file is container: %@", url]];
}
url = obj;
[lock lock];
url = [expandedURLs objectAtIndex:0];
[expandedURLs removeObjectAtIndex:0];
if([uniqueURLs containsObject:url]) {
[lock unlock];
return;
}
[lock unlock];
if([acceptableContainerTypes containsObject:[[url pathExtension] lowercaseString]]) {
@ -478,7 +487,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(urls != nil && [urls count] != 0) {
[lock lock];
[containedURLs addObjectsFromArray:urls];
[loadedURLs setValue:urls forKey:key];
[lock unlock];
// Make sure the container isn't added twice.
@ -514,7 +523,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} else {
/* Fall back on adding the raw file if all container parsers have failed. */
[lock lock];
[fileURLs addObject:url];
[loadedURLs setValue:url forKey:key];
[lock unlock];
}
if(innerTask) {
@ -527,7 +536,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[lock unlock];
} else {
[lock lock];
[fileURLs addObject:url];
[loadedURLs setValue:url forKey:key];
[lock unlock];
}
if(pathTask) {
@ -556,8 +565,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[lock unlock];
}];
[containerQueue addOperation:op];
}
[self->containerQueue addOperation:op];
}];
[containerQueue waitUntilAllOperationsAreFinished];
@ -569,7 +578,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
progress = 0.0;
[self completeProgressJob];
if([fileURLs count] > 0) {
if([loadedURLs count] > 0) {
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringFiles", @"") percentOfTotal:20.0];
} else {
[self setProgressStatus:60.0];
@ -579,22 +588,38 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
id<SentrySpan> filterTask = [mainTask startChildWithOperation:@"Filtering URLs for dupes and supported tracks"];
// Deduplication of contained URLs
[fileURLs removeObjectsInArray:containedURLs];
[fileURLs removeObjectsInArray:dependencyURLs];
for(NSURL *u in dependencyURLs) {
for(NSUInteger c = 0; c < [containedURLs count];) {
if([[u path] isEqualToString:[containedURLs[c] path]]) {
[containedURLs removeObjectAtIndex:c];
} else {
++c;
NSArray *keys = [loadedURLs allKeys];
if(sort) {
keys = [keys sortedArrayUsingSelector:@selector(finderCompare:)];
}
NSArray *objs = [loadedURLs objectsForKeys:keys notFoundMarker:[NSNull null]];
// Pass 1: Collect unique URLs
for(id obj in objs) {
if([obj isKindOfClass:[NSURL class]]) {
if(![uniqueURLs containsObject:obj]) {
[uniqueURLs addObject:obj];
}
} else if([obj isKindOfClass:[NSArray class]]) {
for(NSURL *url in obj) {
if(![uniqueURLs containsObject:url]) {
[uniqueURLs addObject:url];
}
}
}
}
DLog(@"File urls: %@", fileURLs);
// Pass 2: Only add outer URLs that are unique, but add all contained URLs
for(id obj in objs) {
if([obj isKindOfClass:[NSURL class]]) {
if(![uniqueURLs containsObject:obj]) {
[fileURLs addObject:obj];
}
} else if([obj isKindOfClass:[NSArray class]]) {
[fileURLs addObjectsFromArray:obj];
}
}
DLog(@"Contained urls: %@", containedURLs);
DLog(@"File urls: %@", fileURLs);
progressstep = [fileURLs count] ? 100.0 / (double)([fileURLs count]) : 0;
@ -615,11 +640,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if([url isFileURL] && ![fileTypes containsObject:ext])
continue;
if(![uniqueURLs containsObject:url]) {
[validURLs addObject:url];
[uniqueURLs addObject:url];
}
[validURLs addObject:url];
[fileTask finish];
}
@ -646,65 +667,9 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[self completeProgressJob];
}
id<SentrySpan> containedTask = nil;
if([containedURLs count] > 0) {
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringContainedFiles", @"") percentOfTotal:20.0];
containedTask = [mainTask startChildWithOperation:@"Filtering contained URLs for supported tracks"];
} else {
[self setProgressStatus:80.0];
}
DLog(@"Valid urls: %@", validURLs);
progressstep = [containedURLs count] ? 100.0 / (double)([containedURLs count]) : 0;
for(url in containedURLs) {
id<SentrySpan> containedUrlTask = nil;
@try {
containedUrlTask = [containedTask startChildWithOperation:@"Filtering contained URL" description:[NSString stringWithFormat:@"Track URL: %@", url]];
progress += progressstep;
if(![[AudioPlayer schemes] containsObject:[url scheme]]) {
[containedUrlTask finish];
continue;
}
// Need a better way to determine acceptable file types than basing it on extensions.
if([url isFileURL] && ![fileTypes containsObject:[[url pathExtension] lowercaseString]]) {
[containedUrlTask finish];
continue;
}
[validURLs addObject:url];
[self setProgressJobStatus:progress];
[containedUrlTask finish];
}
@catch(NSException *e) {
DLog(@"Exception caught filtering contained URL: %@", e);
if(e) {
[SentrySDK captureException:e];
} else {
[SentrySDK captureMessage:[NSString stringWithFormat:@"Null exception caught when filtering contained URL: %@", url]];
}
if(containedUrlTask) {
[containedUrlTask finishWithStatus:kSentrySpanStatusInternalError];
}
}
}
if(containedTask) {
[containedTask finish];
}
progress = 0.0;
if([containedURLs count] > 0) {
[self completeProgressJob];
}
// Create actual entries
int count = (int)[validURLs count];
@ -716,22 +681,15 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return @[];
}
NSArray *sortedURLs;
if(sort == YES) {
sortedURLs = [validURLs sortedArrayUsingSelector:@selector(finderCompare:)];
} else {
sortedURLs = validURLs;
}
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderAddingEntries", @"") percentOfTotal:20.0];
progressstep = 100.0 / (double)(count);
__block id<SentrySpan> addTask = [mainTask startChildWithOperation:@"Add entries to playlist" description:[NSString stringWithFormat:@"Adding %lu entries to the playlist", [sortedURLs count]]];
__block id<SentrySpan> addTask = [mainTask startChildWithOperation:@"Add entries to playlist" description:[NSString stringWithFormat:@"Adding %lu entries to the playlist", [validURLs count]]];
NSInteger i = 0;
__block NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for(NSURL *url in sortedURLs) {
for(NSURL *url in validURLs) {
__block PlaylistEntry *pe;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{