Merge pull request #473 from bnnm/faketxtp

faketxtp
This commit is contained in:
Christopher Snowhill 2019-09-15 20:42:33 -07:00 committed by GitHub
commit 15362e0b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 37 deletions

View File

@ -18,6 +18,8 @@ Help and newest builds can be found here: https://www.hcs64.com/
Latest development is usually here: https://github.com/losnoco/vgmstream/ Latest development is usually here: https://github.com/losnoco/vgmstream/
You can find further info about other details in https://github.com/losnoco/vgmstream/tree/master/doc
## Needed extra files (for Windows) ## Needed extra files (for Windows)
Support for some codecs (Ogg Vorbis, MPEG audio, etc) is done with external Support for some codecs (Ogg Vorbis, MPEG audio, etc) is done with external
libraries, so you will need to have certain DLL files. libraries, so you will need to have certain DLL files.
@ -307,6 +309,44 @@ named and that filenames inside match the song filename. For Winamp you need
to make sure options > titles > advanced title formatting checkbox is set and to make sure options > titles > advanced title formatting checkbox is set and
the format defined. the format defined.
## Virtual TXTP files
Some of vgmstream's plugins allow you to use virtual .txtp files, that combined
with playlists let you make quick song configs.
Normally you can create a physical .txtp file that points to another file with
config, and .txtp have a "mini-txtp" mode that configures files with only the
filename.
Instead of manually creating .txtp files you can put non-existing virtual .txtp
in a `.m3u` playlist:
```
# playlist that opens subsongs directly without having to create .txtp
# notice the full filename, then #(config), then ".txtp" (spaces are optional)
bank_bgm_full.nub #s1 .txtp
bank_bgm_full.nub #s10 .txtp
```
Combine with tagging (see above) for extra fun OST-like config.
```
# @ALBUM GOD HAND
# play 1 loop, delay and do a longer fade
# %TITLE Too Hot !!
circus_a_mix_ver2.adx #l 1.0 #d 5.0 #f 15.0 .txtp
# play 1 loop instead of the default 2 then fade with the song's internal fading
# %TITLE Yet... Oh see mind
boss2_3ningumi_ver6.adx #l 1.0 #F .txtp
...
```
You can also use it in CLI for quick access to some txtp-exclusive functions:
```
# force change sample rate to 22050
test.exe btl_koopa1_44k_lp.brstm #h22050.txtp -o btl_koopa1_44k_lp.wav
```
## Supported codec types ## Supported codec types
Quick list of codecs vgmstream supports, including many obscure ones that Quick list of codecs vgmstream supports, including many obscure ones that

View File

@ -20,6 +20,7 @@ extern "C" {
typedef struct { typedef struct {
STREAMFILE sf; /* callbacks */ STREAMFILE sf; /* callbacks */
bool m_file_opened; /* if foobar IO service opened the file */
service_ptr_t<file> m_file; /* foobar IO service */ service_ptr_t<file> m_file; /* foobar IO service */
abort_callback * p_abort; /* foobar error stuff */ abort_callback * p_abort; /* foobar error stuff */
char * name; /* IO filename */ char * name; /* IO filename */
@ -32,12 +33,12 @@ typedef struct {
} FOO_STREAMFILE; } FOO_STREAMFILE;
static STREAMFILE * open_foo_streamfile_buffer(const char * const filename, size_t buffersize, abort_callback * p_abort, t_filestats * stats); static STREAMFILE * open_foo_streamfile_buffer(const char * const filename, size_t buffersize, abort_callback * p_abort, t_filestats * stats);
static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file,const char * const filename, size_t buffersize, abort_callback * p_abort); static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file, bool m_file_opened, const char * const filename, size_t buffersize, abort_callback * p_abort);
static size_t read_foo(FOO_STREAMFILE *streamfile, uint8_t * dest, off_t offset, size_t length) { static size_t read_foo(FOO_STREAMFILE *streamfile, uint8_t * dest, off_t offset, size_t length) {
size_t length_read_total = 0; size_t length_read_total = 0;
if (!streamfile || !dest || length <= 0 || offset < 0) if (!streamfile || !streamfile->m_file_opened || !dest || length <= 0 || offset < 0)
return 0; return 0;
/* is the part of the requested length in the buffer? */ /* is the part of the requested length in the buffer? */
@ -124,7 +125,8 @@ static void get_name_foo(FOO_STREAMFILE *streamfile,char *buffer,size_t length)
} }
} }
static void close_foo(FOO_STREAMFILE * streamfile) { static void close_foo(FOO_STREAMFILE * streamfile) {
streamfile->m_file.release(); if (streamfile->m_file_opened)
streamfile->m_file.release();
free(streamfile->name); free(streamfile->name);
free(streamfile->buffer); free(streamfile->buffer);
free(streamfile); free(streamfile);
@ -139,22 +141,22 @@ static STREAMFILE *open_foo(FOO_STREAMFILE *streamFile,const char * const filena
return NULL; return NULL;
// if same name, duplicate the file pointer we already have open // if same name, duplicate the file pointer we already have open
if (!strcmp(streamFile->name,filename)) { if (streamFile->m_file_opened && !strcmp(streamFile->name,filename)) {
m_file = streamFile->m_file; m_file = streamFile->m_file; //copy?
{ {
newstreamFile = open_foo_streamfile_buffer_by_file(m_file,filename,buffersize,streamFile->p_abort); newstreamFile = open_foo_streamfile_buffer_by_file(m_file, streamFile->m_file_opened, filename, buffersize, streamFile->p_abort);
if (newstreamFile) { if (newstreamFile) {
return newstreamFile; return newstreamFile;
} }
// failure, close it and try the default path (which will probably fail a second time) // failure, close it and try the default path (which will probably fail a second time)
} }
} }
// a normal open, open a new file
// a normal open, open a new file
return open_foo_streamfile_buffer(filename,buffersize,streamFile->p_abort,NULL); return open_foo_streamfile_buffer(filename,buffersize,streamFile->p_abort,NULL);
} }
static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file,const char * const filename, size_t buffersize, abort_callback * p_abort) { static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file, bool m_file_opened, const char * const filename, size_t buffersize, abort_callback * p_abort) {
uint8_t * buffer; uint8_t * buffer;
FOO_STREAMFILE * streamfile; FOO_STREAMFILE * streamfile;
@ -171,6 +173,7 @@ static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_fil
streamfile->sf.open = (_STREAMFILE *(__cdecl *)(_STREAMFILE *,const char *const ,size_t)) open_foo; streamfile->sf.open = (_STREAMFILE *(__cdecl *)(_STREAMFILE *,const char *const ,size_t)) open_foo;
streamfile->sf.close = (void (__cdecl *)(_STREAMFILE *)) close_foo; streamfile->sf.close = (void (__cdecl *)(_STREAMFILE *)) close_foo;
streamfile->m_file_opened = m_file_opened;
streamfile->m_file = m_file; streamfile->m_file = m_file;
streamfile->p_abort = p_abort; streamfile->p_abort = p_abort;
streamfile->buffersize = buffersize; streamfile->buffersize = buffersize;
@ -180,7 +183,10 @@ static STREAMFILE * open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_fil
if (!streamfile->name) goto fail; if (!streamfile->name) goto fail;
/* cache filesize */ /* cache filesize */
streamfile->filesize = streamfile->m_file->get_size(*streamfile->p_abort); if (streamfile->m_file_opened)
streamfile->filesize = streamfile->m_file->get_size(*streamfile->p_abort);
else
streamfile->filesize = 0;
return &streamfile->sf; return &streamfile->sf;
@ -193,16 +199,23 @@ fail:
static STREAMFILE * open_foo_streamfile_buffer(const char * const filename, size_t buffersize, abort_callback * p_abort, t_filestats * stats) { static STREAMFILE * open_foo_streamfile_buffer(const char * const filename, size_t buffersize, abort_callback * p_abort, t_filestats * stats) {
STREAMFILE *streamFile; STREAMFILE *streamFile;
service_ptr_t<file> infile; service_ptr_t<file> infile;
bool infile_exists;
if(!(filesystem::g_exists(filename, *p_abort))) infile_exists = filesystem::g_exists(filename, *p_abort);
return NULL; if(!infile_exists) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(filename))
return NULL;
}
filesystem::g_open_read(infile,filename,*p_abort); if (infile_exists) {
if(stats) *stats = infile->get_stats(*p_abort); filesystem::g_open_read(infile,filename,*p_abort);
if(stats) *stats = infile->get_stats(*p_abort);
}
streamFile = open_foo_streamfile_buffer_by_file(infile,filename,buffersize,p_abort); streamFile = open_foo_streamfile_buffer_by_file(infile, infile_exists, filename, buffersize, p_abort);
if (!streamFile) { if (!streamFile) {
// fclose(infile); //m_file.release(); //todo not needed after g_open_read?
} }
return streamFile; return streamFile;

View File

@ -73,20 +73,28 @@ input_vgmstream::~input_vgmstream() {
} }
// called first when a new file is opened // called first when a new file is opened
void input_vgmstream::open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
if (!p_path) { // shouldn't be possible if (!p_path) { // shouldn't be possible
throw exception_io_data(); throw exception_io_data();
return; return; //???
} }
filename = p_path; filename = p_path;
// allow non-existing files in some cases
bool infile_virtual = !filesystem::g_exists(p_path, p_abort)
&& vgmstream_is_virtual_filename(filename) == 1;
// keep file stats around (timestamp, filesize) // don't try to open virtual files as it'll fail
if ( p_filehint.is_empty() ) // (doesn't seem to have any adverse effect, except maybe no stats)
input_open_file_helper( p_filehint, filename, p_reason, p_abort ); // setup_vgmstream also makes further checks before file is finally opened
stats = p_filehint->get_stats( p_abort ); if (!infile_virtual) {
// keep file stats around (timestamp, filesize)
if ( p_filehint.is_empty() )
input_open_file_helper( p_filehint, filename, p_reason, p_abort );
stats = p_filehint->get_stats( p_abort );
}
switch(p_reason) { switch(p_reason) {
case input_open_decode: // prepare to retrieve info and decode case input_open_decode: // prepare to retrieve info and decode
@ -169,7 +177,7 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
strcpy(tagfile_path,tagfile_name); strcpy(tagfile_path,tagfile_name);
} }
STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, &stats); STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, NULL);
if (tagFile != NULL) { if (tagFile != NULL) {
VGMSTREAM_TAGS *tags; VGMSTREAM_TAGS *tags;
const char *tag_key, *tag_val; const char *tag_key, *tag_val;

View File

@ -26,7 +26,7 @@ static STREAMFILE * open_stdio_streamfile_buffer_by_file(FILE *infile,const char
static size_t read_stdio(STDIOSTREAMFILE *streamfile,uint8_t * dest, off_t offset, size_t length) { static size_t read_stdio(STDIOSTREAMFILE *streamfile,uint8_t * dest, off_t offset, size_t length) {
size_t length_read_total = 0; size_t length_read_total = 0;
if (!streamfile || !dest || length <= 0 || offset < 0) if (!streamfile || !streamfile->infile || !dest || length <= 0 || offset < 0)
return 0; return 0;
/* is the part of the requested length in the buffer? */ /* is the part of the requested length in the buffer? */
@ -110,7 +110,8 @@ static void get_name_stdio(STDIOSTREAMFILE *streamfile,char *buffer,size_t lengt
buffer[length-1]='\0'; buffer[length-1]='\0';
} }
static void close_stdio(STDIOSTREAMFILE * streamfile) { static void close_stdio(STDIOSTREAMFILE * streamfile) {
fclose(streamfile->infile); if (streamfile->infile)
fclose(streamfile->infile);
free(streamfile->buffer); free(streamfile->buffer);
free(streamfile); free(streamfile);
} }
@ -124,7 +125,7 @@ static STREAMFILE *open_stdio(STDIOSTREAMFILE *streamFile,const char * const fil
return NULL; return NULL;
#if !defined (__ANDROID__) #if !defined (__ANDROID__)
// if same name, duplicate the file pointer we already have open // if same name, duplicate the file pointer we already have open
if (!strcmp(streamFile->name,filename)) { if (streamFile->infile && !strcmp(streamFile->name,filename)) {
if (((newfd = dup(fileno(streamFile->infile))) >= 0) && if (((newfd = dup(fileno(streamFile->infile))) >= 0) &&
(newfile = fdopen( newfd, "rb" ))) (newfile = fdopen( newfd, "rb" )))
{ {
@ -141,7 +142,7 @@ static STREAMFILE *open_stdio(STDIOSTREAMFILE *streamFile,const char * const fil
return open_stdio_streamfile_buffer(filename,buffersize); return open_stdio_streamfile_buffer(filename,buffersize);
} }
static STREAMFILE * open_stdio_streamfile_buffer_by_file(FILE *infile,const char * const filename, size_t buffersize) { static STREAMFILE * open_stdio_streamfile_buffer_by_file(FILE *infile, const char * const filename, size_t buffersize) {
uint8_t * buffer = NULL; uint8_t * buffer = NULL;
STDIOSTREAMFILE * streamfile = NULL; STDIOSTREAMFILE * streamfile = NULL;
@ -166,8 +167,13 @@ static STREAMFILE * open_stdio_streamfile_buffer_by_file(FILE *infile,const char
streamfile->name[sizeof(streamfile->name)-1] = '\0'; streamfile->name[sizeof(streamfile->name)-1] = '\0';
/* cache filesize */ /* cache filesize */
fseeko(streamfile->infile,0,SEEK_END); if (infile) {
streamfile->filesize = ftello(streamfile->infile); fseeko(streamfile->infile,0,SEEK_END);
streamfile->filesize = ftello(streamfile->infile);
}
else {
streamfile->filesize = 0; /* allow virtual, non-existing files */
}
/* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF /* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF
* (happens in banks like FSB, though rarely). Can be remedied with the * (happens in banks like FSB, though rarely). Can be remedied with the
@ -190,11 +196,15 @@ static STREAMFILE * open_stdio_streamfile_buffer(const char * const filename, si
STREAMFILE *streamFile; STREAMFILE *streamFile;
infile = fopen(filename,"rb"); infile = fopen(filename,"rb");
if (!infile) return NULL; if (!infile) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(filename))
return NULL;
}
streamFile = open_stdio_streamfile_buffer_by_file(infile,filename,buffersize); streamFile = open_stdio_streamfile_buffer_by_file(infile,filename,buffersize);
if (!streamFile) { if (!streamFile) {
fclose(infile); if (infile) fclose(infile);
} }
return streamFile; return streamFile;

View File

@ -2882,3 +2882,20 @@ fail:
/* open streams will be closed in close_vgmstream(), hopefully called by the meta */ /* open streams will be closed in close_vgmstream(), hopefully called by the meta */
return 0; return 0;
} }
int vgmstream_is_virtual_filename(const char* filename) {
int len = strlen(filename);
if (len < 6)
return 0;
/* vgmstream can play .txtp files that have size 0 but point to another file with config
* based only in the filename (ex. "file.fsb #2.txtp" plays 2nd subsong of file.fsb).
*
* Also, .m3u playlist can include files that don't exist, and players often allow filenames
* pointing to nothing (since could be some protocol/url).
*
* Plugins can use both quirks to allow "virtual files" (.txtp) in .m3u that don't need
* to exist but allow config. Plugins with this function if the filename is virtual,
* and their STREAMFILEs should be modified as to ignore null FILEs and report size 0. */
return strcmp(&filename[len-5], ".txtp") == 0;
}

View File

@ -1336,6 +1336,9 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa
/* Set number of max loops to do, then play up to stream end (for songs with proper endings) */ /* Set number of max loops to do, then play up to stream end (for songs with proper endings) */
void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target); void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target);
/* Return 1 if vgmstream detects from the filename that said file can be used even if doesn't physically exist */
int vgmstream_is_virtual_filename(const char* filename);
/* -------------------------------------------------------------------------*/ /* -------------------------------------------------------------------------*/
/* vgmstream "private" API */ /* vgmstream "private" API */
/* -------------------------------------------------------------------------*/ /* -------------------------------------------------------------------------*/

View File

@ -210,7 +210,7 @@ static FILE* wa_fdopen(int fd) {
typedef struct { typedef struct {
STREAMFILE sf; STREAMFILE sf;
STREAMFILE *stdiosf; STREAMFILE *stdiosf;
FILE *infile_ref; /* pointer to the infile in stdiosf */ FILE *infile_ref; /* pointer to the infile in stdiosf (partially handled by stdiosf) */
} WINAMP_STREAMFILE; } WINAMP_STREAMFILE;
static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path); static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path);
@ -244,7 +244,7 @@ static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const fi
/* if same name, duplicate the file pointer we already have open */ //unsure if all this is needed /* if same name, duplicate the file pointer we already have open */ //unsure if all this is needed
streamFile->stdiosf->get_name(streamFile->stdiosf, name, PATH_LIMIT); streamFile->stdiosf->get_name(streamFile->stdiosf, name, PATH_LIMIT);
if (!strcmp(name,filename)) { if (streamFile->infile_ref && !strcmp(name,filename)) {
if (((newfd = dup(fileno(streamFile->infile_ref))) >= 0) && if (((newfd = dup(fileno(streamFile->infile_ref))) >= 0) &&
(newfile = wa_fdopen(newfd))) (newfile = wa_fdopen(newfd)))
{ {
@ -275,7 +275,7 @@ static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * pat
this_sf = calloc(1,sizeof(WINAMP_STREAMFILE)); this_sf = calloc(1,sizeof(WINAMP_STREAMFILE));
if (!this_sf) goto fail; if (!this_sf) goto fail;
stdiosf = open_stdio_streamfile_by_file(infile,path); stdiosf = open_stdio_streamfile_by_file(infile, path);
if (!stdiosf) goto fail; if (!stdiosf) goto fail;
this_sf->sf.read = (void*)wasf_read; this_sf->sf.read = (void*)wasf_read;
@ -302,16 +302,21 @@ static STREAMFILE *open_winamp_streamfile_by_ipath(const in_char *wpath) {
STREAMFILE *streamFile; STREAMFILE *streamFile;
char path[PATH_LIMIT]; char path[PATH_LIMIT];
/* open a FILE from a Winamp (possibly UTF-16) path */
infile = wa_fopen(wpath);
if (!infile) return NULL;
/* convert to UTF-8 if needed for internal use */ /* convert to UTF-8 if needed for internal use */
wa_ichar_to_char(path,PATH_LIMIT, wpath); wa_ichar_to_char(path,PATH_LIMIT, wpath);
/* open a FILE from a Winamp (possibly UTF-16) path */
infile = wa_fopen(wpath);
if (!infile) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(path))
return NULL;
}
streamFile = open_winamp_streamfile_by_file(infile,path); streamFile = open_winamp_streamfile_by_file(infile,path);
if (!streamFile) { if (!streamFile) {
fclose(infile); if (infile) fclose(infile);
} }
return streamFile; return streamFile;