Compare commits

..

No commits in common. "7839f661a13180f0d716a913ca708d9357aeee2b" and "69533e12c7f7dd9da76766266b1a14b7068393ab" have entirely different histories.

13 changed files with 141 additions and 919 deletions

View file

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

View file

@ -673,8 +673,6 @@
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 */; };
@ -1631,8 +1629,6 @@
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>"; };
@ -2344,8 +2340,6 @@
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 */,
@ -3075,7 +3069,6 @@
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 */,
@ -3615,7 +3608,6 @@
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,11 +137,6 @@ 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,7 +89,6 @@ 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",
@ -1480,7 +1479,6 @@ 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

@ -1,743 +0,0 @@
#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

@ -1,35 +0,0 @@
#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,7 +772,6 @@ 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
@ -1273,20 +1272,12 @@ 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,6 +1035,4 @@ VGMSTREAM* init_vgmstream_2dx(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ssp(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_audiopkg(STREAMFILE* sf);
#endif

View file

@ -5,7 +5,6 @@
#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
@ -1425,7 +1424,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
}
/* HEADER/BODY CONFIG */
else if (is_string(key,"header_file") || is_string(key,"head_file")) {
else if (is_string(key,"header_file")) {
/* first remove old head if needed */
if (txth->sf_head_opened) {
@ -1448,17 +1447,12 @@ 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 = 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;
txth->sf_head_opened = 1;
}
else { /* open file */
txth->sf_head = open_path_streamfile(txth->sf, val);
if (!txth->sf_head) goto fail;
txth->sf_head_opened = true;
txth->sf_head_opened = 1;
}
}
else if (is_string(key,"body_file")) {
@ -1484,17 +1478,12 @@ 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 = 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;
txth->sf_body_opened = 1;
}
else { /* open file */
txth->sf_body = open_path_streamfile(txth->sf, val);
if (!txth->sf_body) goto fail;
txth->sf_body_opened = true;
txth->sf_body_opened = 1;
}
/* use body as header when opening a .txth directly to simplify things */

View file

@ -511,7 +511,6 @@ 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,7 +721,6 @@ typedef enum {
meta_SHAA,
meta_OOR,
meta_MIO,
meta_AUDIOPKG,
} meta_t;
#endif

View file

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

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 NSMutableDictionary *expandedURLs = [[NSMutableDictionary alloc] init];
__block NSMutableDictionary *loadedURLs = [[NSMutableDictionary alloc] init];
__block NSMutableArray *expandedURLs = [[NSMutableArray alloc] init];
__block NSMutableArray *containedURLs = [[NSMutableArray alloc] init];
__block NSMutableArray *fileURLs = [[NSMutableArray alloc] init];
NSMutableArray *validURLs = [[NSMutableArray alloc] init];
NSMutableArray *folderURLs = [[NSMutableArray alloc] init];
@ -394,28 +394,22 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(isDir == YES) {
// Get subpaths
[[SandboxBroker sharedSandboxBroker] addFolderIfMissing:url];
NSArray *pathURLs = [self fileURLsAtPath:[url path]];
for(NSURL *url in pathURLs) {
[expandedURLs setValue:url forKey:[url absoluteString]];
}
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]];
} else if(addOtherFilesInFolder) {
NSURL *folderUrl = [url URLByDeletingLastPathComponent];
if(![folderURLs containsObject:folderUrl]) {
[[SandboxBroker sharedSandboxBroker] requestFolderForFile:url];
NSArray *pathURLs = [self fileURLsAtPath:[folderUrl path]];
for(NSURL *url in pathURLs) {
[expandedURLs setValue:url forKey:[url absoluteString]];
}
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[folderUrl path]]];
[folderURLs addObject:folderUrl];
}
} else {
[[SandboxBroker sharedSandboxBroker] addFileIfMissing:url];
[expandedURLs setValue:url forKey:[url absoluteString]];
[expandedURLs addObject:[NSURL fileURLWithPath:[url path]]];
}
}
} else {
// Non-file URL..
[expandedURLs setValue:url forKey:[url absoluteString]];
[expandedURLs addObject:url];
}
[pathTask finish];
@ -458,7 +452,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
__block double weakProgressstep = progressstep;
// Container vs non-container url
[expandedURLs enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
for(size_t i = 0, j = [expandedURLs count]; i < j; ++i) {
NSBlockOperation *op = [[NSBlockOperation alloc] init];
[op addExecutionBlock:^{
@ -470,12 +464,9 @@ 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];
if([uniqueURLs containsObject:url]) {
[lock unlock];
return;
}
url = [expandedURLs objectAtIndex:0];
[expandedURLs removeObjectAtIndex:0];
[lock unlock];
if([acceptableContainerTypes containsObject:[[url pathExtension] lowercaseString]]) {
@ -487,7 +478,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(urls != nil && [urls count] != 0) {
[lock lock];
[loadedURLs setValue:urls forKey:key];
[containedURLs addObjectsFromArray:urls];
[lock unlock];
// Make sure the container isn't added twice.
@ -523,7 +514,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];
[loadedURLs setValue:url forKey:key];
[fileURLs addObject:url];
[lock unlock];
}
if(innerTask) {
@ -536,7 +527,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[lock unlock];
} else {
[lock lock];
[loadedURLs setValue:url forKey:key];
[fileURLs addObject:url];
[lock unlock];
}
if(pathTask) {
@ -565,8 +556,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[lock unlock];
}];
[self->containerQueue addOperation:op];
}];
[containerQueue addOperation:op];
}
[containerQueue waitUntilAllOperationsAreFinished];
@ -578,7 +569,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
progress = 0.0;
[self completeProgressJob];
if([loadedURLs count] > 0) {
if([fileURLs count] > 0) {
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringFiles", @"") percentOfTotal:20.0];
} else {
[self setProgressStatus:60.0];
@ -588,39 +579,23 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
id<SentrySpan> filterTask = [mainTask startChildWithOperation:@"Filtering URLs for dupes and supported tracks"];
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];
// 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;
}
} else if([obj isKindOfClass:[NSArray class]]) {
for(NSURL *url in obj) {
if(![uniqueURLs containsObject:url]) {
[uniqueURLs addObject:url];
}
}
}
}
// 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(@"File urls: %@", fileURLs);
DLog(@"Contained urls: %@", containedURLs);
progressstep = [fileURLs count] ? 100.0 / (double)([fileURLs count]) : 0;
for(url in fileURLs) {
@ -640,7 +615,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if([url isFileURL] && ![fileTypes containsObject:ext])
continue;
[validURLs addObject:url];
if(![uniqueURLs containsObject:url]) {
[validURLs addObject:url];
[uniqueURLs addObject:url];
}
[fileTask finish];
}
@ -667,9 +646,65 @@ 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];
@ -681,15 +716,22 @@ 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", [validURLs count]]];
__block id<SentrySpan> addTask = [mainTask startChildWithOperation:@"Add entries to playlist" description:[NSString stringWithFormat:@"Adding %lu entries to the playlist", [sortedURLs count]]];
NSInteger i = 0;
__block NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for(NSURL *url in validURLs) {
for(NSURL *url in sortedURLs) {
__block PlaylistEntry *pe;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{