From 1eb2270049ff2fea55bffca6cbbf430b821638b9 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 6 Jan 2022 22:12:34 -0800 Subject: [PATCH] VGMStream: Rewrite file interface based on the VGMStream stdio interface, only using CogSource files --- Plugins/vgmstream/vgmstream/VGMDecoder.m | 2 +- Plugins/vgmstream/vgmstream/VGMInterface.h | 19 +- Plugins/vgmstream/vgmstream/VGMInterface.m | 284 ++++++++++++++------- 3 files changed, 211 insertions(+), 94 deletions(-) diff --git a/Plugins/vgmstream/vgmstream/VGMDecoder.m b/Plugins/vgmstream/vgmstream/VGMDecoder.m index 1b37acc53..dadb8c393 100644 --- a/Plugins/vgmstream/vgmstream/VGMDecoder.m +++ b/Plugins/vgmstream/vgmstream/VGMDecoder.m @@ -106,7 +106,7 @@ static NSString* get_description_tag(const char* description, const char *tag, c codec = get_description_tag(description, "encoding: ", 0); - STREAMFILE *tagFile = cogsf_create_from_url(tagurl); + STREAMFILE *tagFile = open_cog_streamfile_from_url(tagurl); if (tagFile) { VGMSTREAM_TAGS *tags; const char *tag_key, *tag_val; diff --git a/Plugins/vgmstream/vgmstream/VGMInterface.h b/Plugins/vgmstream/vgmstream/VGMInterface.h index d8d1593e0..4202228c8 100644 --- a/Plugins/vgmstream/vgmstream/VGMInterface.h +++ b/Plugins/vgmstream/vgmstream/VGMInterface.h @@ -9,14 +9,23 @@ #import #import +/* a STREAMFILE that operates via standard IO using a buffer */ typedef struct _COGSTREAMFILE { - STREAMFILE sf; - void *file; - offv_t offset; - char name[PATH_LIMIT]; + STREAMFILE vt; /* callbacks */ + + void* infile; /* CogSource, retained */ + char name[PATH_LIMIT]; /* FILE filename */ + int name_len; /* cache */ + offv_t offset; /* last read offset (info) */ + offv_t buf_offset; /* current buffer data start */ + uint8_t* buf; /* data buffer */ + size_t buf_size; /* max buffer size */ + size_t valid_size; /* current buffer size */ + size_t file_size; /* buffered file size */ } COGSTREAMFILE; -STREAMFILE *cogsf_create_from_url(NSURL * url); +STREAMFILE* open_cog_streamfile_from_url(NSURL* url); +STREAMFILE* open_cog_streamfile(const char* filename); VGMSTREAM *init_vgmstream_from_cogfile(const char *path, int subsong); diff --git a/Plugins/vgmstream/vgmstream/VGMInterface.m b/Plugins/vgmstream/vgmstream/VGMInterface.m index 160574e61..fa38240dc 100644 --- a/Plugins/vgmstream/vgmstream/VGMInterface.m +++ b/Plugins/vgmstream/vgmstream/VGMInterface.m @@ -20,90 +20,201 @@ void register_log_callback() { vgmstream_set_log_callback(VGM_LOG_LEVEL_ALL, &log_callback); } -static void cogsf_seek(COGSTREAMFILE *this, offv_t offset) { - if (!this) return; - NSObject* _file = (__bridge NSObject *)(this->file); - id __unsafe_unretained file = (id) _file; - if ([file seek:offset whence:SEEK_SET] != 0) - this->offset = offset; - else - this->offset = [file tell]; -} +static STREAMFILE* open_cog_streamfile_buffer(const char* const filename, size_t buf_size); +static STREAMFILE* open_cog_streamfile_buffer_by_file(id infile, const char* const filename, size_t buf_size); -static offv_t cogsf_get_size(COGSTREAMFILE *this) { - if (!this) return 0; - NSObject* _file = (__bridge NSObject *)(this->file); - id __unsafe_unretained file = (id) _file; - offv_t offset = [file tell]; - [file seek:0 whence:SEEK_END]; - offv_t size = [file tell]; - [file seek:offset whence:SEEK_SET]; - return size; -} +static size_t cogsf_read(COGSTREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { + size_t read_total = 0; -static offv_t cogsf_get_offset(COGSTREAMFILE *this) { - if (!this) return 0; - return this->offset; -} + if (!sf || !sf->infile || !dst || length <= 0 || offset < 0) + return 0; -static void cogsf_get_name(COGSTREAMFILE *this, char *buffer, size_t length) { - if (!this) { - memset(buffer, 0, length); - return; + //;VGM_LOG("cogsf: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size); + + /* is the part of the requested length in the buffer? */ + if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) { + size_t buf_limit; + int buf_into = (int)(offset - sf->buf_offset); + + buf_limit = sf->valid_size - buf_into; + if (buf_limit > length) + buf_limit = length; + + //;VGM_LOG("cogsf: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size); + + memcpy(dst, sf->buf + buf_into, buf_limit); + read_total += buf_limit; + length -= buf_limit; + offset += buf_limit; + dst += buf_limit; } - if (length > sizeof(this->name)) - length = sizeof(this->name); - strncpy(buffer, this->name, length); - buffer[length-1]='\0'; -} -static size_t cogsf_read(COGSTREAMFILE *this, uint8_t *dest, offv_t offset, size_t length) { - if (!this) return 0; - NSObject* _file = (__bridge NSObject *)(this->file); - id __unsafe_unretained file = (id) _file; - size_t read; - if (this->offset != offset) - cogsf_seek(this, offset); - read = [file read:dest amount:length]; - if (read > 0) - this->offset += read; - return read; -} - -static void cogsf_close(COGSTREAMFILE *this) { - if (this) { - CFBridgingRelease(this->file); - free(this); +#ifdef VGM_DEBUG_OUTPUT + if (offset < sf->buf_offset && length > 0) { + //VGM_LOG("cogsf: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf); + //sf->rebuffer++; + //if (rebuffer > N) ... } +#endif + NSObject* _file = (__bridge NSObject *)(sf->infile); + id __unsafe_unretained file = (id) _file; + + /* read the rest of the requested length */ + while (length > 0) { + size_t length_to_read; + + /* ignore requests at EOF */ + if (offset >= sf->file_size) { + //offset = sf->file_size; /* seems fseek doesn't clamp offset */ + VGM_ASSERT_ONCE(offset > sf->file_size, "COGSF: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length); + break; + } + + /* position to new offset */ + if (![file seek:offset whence:SEEK_SET]) { + break; /* this shouldn't happen in our code */ + } + + /* fill the buffer (offset now is beyond buf_offset) */ + sf->buf_offset = offset; + sf->valid_size = [file read:sf->buf amount:sf->buf_size]; + //;VGM_LOG("cogsf: read buf %lx + %x\n", sf->buf_offset, sf->valid_size); + + /* decide how much must be read this time */ + if (length > sf->buf_size) + length_to_read = sf->buf_size; + else + length_to_read = length; + + /* give up on partial reads (EOF) */ + if (sf->valid_size < length_to_read) { + memcpy(dst, sf->buf, sf->valid_size); + offset += sf->valid_size; + read_total += sf->valid_size; + break; + } + + /* use the new buffer */ + memcpy(dst, sf->buf, length_to_read); + offset += length_to_read; + read_total += length_to_read; + length -= length_to_read; + dst += length_to_read; + } + + sf->offset = offset; /* last fread offset */ + return read_total; +} +static size_t cogsf_get_size(COGSTREAMFILE* sf) { + return sf->file_size; +} +static offv_t cogsf_get_offset(COGSTREAMFILE* sf) { + return sf->offset; +} +static void cogsf_get_name(COGSTREAMFILE* sf, char* name, size_t name_size) { + int copy_size = sf->name_len + 1; + if (copy_size > name_size) + copy_size = name_size; + + memcpy(name, sf->name, copy_size); + name[copy_size - 1] = '\0'; } -static STREAMFILE *cogsf_create_from_path(const char *path); -static STREAMFILE *cogsf_open(COGSTREAMFILE *this, const char *const filename,size_t buffersize) { - if (!filename) return NULL; - return cogsf_create_from_path(filename); +static STREAMFILE* cogsf_open(COGSTREAMFILE* sf, const char* const filename, size_t buf_size) { + if (!filename) + return NULL; + + // a normal open, open a new file + return open_cog_streamfile_buffer(filename, buf_size); } -static STREAMFILE *cogsf_create(id file, const char *path) { - COGSTREAMFILE *streamfile = malloc(sizeof(COGSTREAMFILE)); - - if (!streamfile) return NULL; - - memset(streamfile,0,sizeof(COGSTREAMFILE)); - streamfile->sf.read = (void*)cogsf_read; - streamfile->sf.get_size = (void*)cogsf_get_size; - streamfile->sf.get_offset = (void*)cogsf_get_offset; - streamfile->sf.get_name = (void*)cogsf_get_name; - streamfile->sf.open = (void*)cogsf_open; - streamfile->sf.close = (void*)cogsf_close; - streamfile->file = (void*)CFBridgingRetain(file); - streamfile->offset = 0; - strncpy(streamfile->name, path, sizeof(streamfile->name)); - - return &streamfile->sf; +static void cogsf_close(COGSTREAMFILE* sf) { + if (sf->infile) + CFBridgingRelease(sf->infile); + free(sf->buf); + free(sf); } -STREAMFILE *cogsf_create_from_path(const char *path) { - NSString * urlString = [NSString stringWithUTF8String:path]; + +static STREAMFILE* open_cog_streamfile_buffer_by_file(id infile, const char* const filename, size_t buf_size) { + uint8_t* buf = NULL; + COGSTREAMFILE* this_sf = NULL; + int infile_retained = 0; + + buf = calloc(buf_size, sizeof(uint8_t)); + if (!buf) goto fail; + + this_sf = calloc(1, sizeof(COGSTREAMFILE)); + if (!this_sf) goto fail; + + this_sf->vt.read = (void*)cogsf_read; + this_sf->vt.get_size = (void*)cogsf_get_size; + this_sf->vt.get_offset = (void*)cogsf_get_offset; + this_sf->vt.get_name = (void*)cogsf_get_name; + this_sf->vt.open = (void*)cogsf_open; + this_sf->vt.close = (void*)cogsf_close; + + if (infile) { + this_sf->infile = (void*)CFBridgingRetain(infile); + infile_retained = 1; + } + + this_sf->buf_size = buf_size; + this_sf->buf = buf; + + this_sf->name_len = strlen(filename); + if (this_sf->name_len >= sizeof(this_sf->name)) + goto fail; + memcpy(this_sf->name, filename, this_sf->name_len); + this_sf->name[this_sf->name_len] = '\0'; + + /* cache file_size */ + if (infile) { + [infile seek:0 whence:SEEK_END]; + this_sf->file_size = [infile tell]; + [infile seek:0 whence:SEEK_SET]; + } + else { + this_sf->file_size = 0; /* allow virtual, non-existing files */ + } + + /* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF + * (happens in banks like FSB, though rarely). Should work if configured properly, log otherwise. */ + if (this_sf->file_size == 0xFFFFFFFF) { /* -1 on error */ + vgm_logi("STREAMFILE: file size too big (report)\n"); + goto fail; /* can be ignored but may result in strange/unexpected behaviors */ + } + + return &this_sf->vt; + +fail: + if (infile_retained) CFBridgingRelease(this_sf->infile); + free(buf); + free(this_sf); + return NULL; +} + +static STREAMFILE* open_cog_streamfile_buffer_from_url(NSURL* url, const char* const filename, size_t bufsize) { + id infile; + STREAMFILE* sf = NULL; + + id audioSourceClass = NSClassFromString(@"AudioSource"); + infile = [audioSourceClass audioSourceForURL:url]; + + if (![infile open:url]) { + /* allow non-existing files in some cases */ + if (!vgmstream_is_virtual_filename(filename)) + return NULL; + } + + if (![infile seekable]) + return NULL; + + return open_cog_streamfile_buffer_by_file(infile, filename, bufsize); +} + +static STREAMFILE* open_cog_streamfile_buffer(const char* const filename, size_t bufsize) { + NSString * urlString = [NSString stringWithUTF8String:filename]; NSURL * url = [NSURL URLWithDataRepresentation:[urlString dataUsingEncoding:NSUTF8StringEncoding] relativeToURL:nil]; if ([url fragment]) { @@ -115,24 +226,22 @@ STREAMFILE *cogsf_create_from_path(const char *path) { url = [NSURL URLWithDataRepresentation:[urlString dataUsingEncoding:NSUTF8StringEncoding] relativeToURL:nil]; } } - - return cogsf_create_from_url(url); + + return open_cog_streamfile_buffer_from_url(url, filename, bufsize); } -STREAMFILE *cogsf_create_from_url(NSURL * url) { - id source; - id audioSourceClass = NSClassFromString(@"AudioSource"); - source = [audioSourceClass audioSourceForURL:url]; - - if (![source open:url]) - return 0; - - if (![source seekable]) - return 0; - - return cogsf_create(source, [[[url absoluteString] stringByRemovingPercentEncoding] UTF8String]); +STREAMFILE* open_cog_streamfile_from_url(NSURL* url) { + return open_cog_streamfile_buffer_from_url(url, [[[url absoluteString] stringByRemovingPercentEncoding] UTF8String], STREAMFILE_DEFAULT_BUFFER_SIZE); } +STREAMFILE* open_cog_streamfile(const char* filename) { + return open_cog_streamfile_buffer(filename, STREAMFILE_DEFAULT_BUFFER_SIZE); +} + +//STREAMFILE* open_cog_streamfile_by_file(id file, const char* filename) { +// return open_cog_streamfile_buffer_by_file(file, filename, STREAMFILE_DEFAULT_BUFFER_SIZE); +//} + VGMSTREAM *init_vgmstream_from_cogfile(const char *path, int subsong) { STREAMFILE *sf; VGMSTREAM *vgm = NULL; @@ -140,7 +249,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path, int subsong) { if (!subsong) subsong = 1; - sf = cogsf_create_from_path(path); + sf = open_cog_streamfile(path); if (sf) { sf->stream_index = subsong; @@ -150,4 +259,3 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path, int subsong) { return vgm; } -