mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-17 23:36:41 +01:00
commit
15362e0b59
40
README.md
40
README.md
@ -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/
|
||||
|
||||
You can find further info about other details in https://github.com/losnoco/vgmstream/tree/master/doc
|
||||
|
||||
## Needed extra files (for Windows)
|
||||
Support for some codecs (Ogg Vorbis, MPEG audio, etc) is done with external
|
||||
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
|
||||
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
|
||||
Quick list of codecs vgmstream supports, including many obscure ones that
|
||||
|
@ -20,6 +20,7 @@ extern "C" {
|
||||
typedef struct {
|
||||
STREAMFILE sf; /* callbacks */
|
||||
|
||||
bool m_file_opened; /* if foobar IO service opened the file */
|
||||
service_ptr_t<file> m_file; /* foobar IO service */
|
||||
abort_callback * p_abort; /* foobar error stuff */
|
||||
char * name; /* IO filename */
|
||||
@ -32,12 +33,12 @@ typedef struct {
|
||||
} 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_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) {
|
||||
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;
|
||||
|
||||
/* 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) {
|
||||
streamfile->m_file.release();
|
||||
if (streamfile->m_file_opened)
|
||||
streamfile->m_file.release();
|
||||
free(streamfile->name);
|
||||
free(streamfile->buffer);
|
||||
free(streamfile);
|
||||
@ -139,22 +141,22 @@ static STREAMFILE *open_foo(FOO_STREAMFILE *streamFile,const char * const filena
|
||||
return NULL;
|
||||
|
||||
// if same name, duplicate the file pointer we already have open
|
||||
if (!strcmp(streamFile->name,filename)) {
|
||||
m_file = streamFile->m_file;
|
||||
if (streamFile->m_file_opened && !strcmp(streamFile->name,filename)) {
|
||||
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) {
|
||||
return newstreamFile;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
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.close = (void (__cdecl *)(_STREAMFILE *)) close_foo;
|
||||
|
||||
streamfile->m_file_opened = m_file_opened;
|
||||
streamfile->m_file = m_file;
|
||||
streamfile->p_abort = p_abort;
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
@ -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) {
|
||||
STREAMFILE *streamFile;
|
||||
service_ptr_t<file> infile;
|
||||
bool infile_exists;
|
||||
|
||||
if(!(filesystem::g_exists(filename, *p_abort)))
|
||||
return NULL;
|
||||
infile_exists = filesystem::g_exists(filename, *p_abort);
|
||||
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(stats) *stats = infile->get_stats(*p_abort);
|
||||
if (infile_exists) {
|
||||
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) {
|
||||
// fclose(infile);
|
||||
//m_file.release(); //todo not needed after g_open_read?
|
||||
}
|
||||
|
||||
return streamFile;
|
||||
|
@ -73,20 +73,28 @@ input_vgmstream::~input_vgmstream() {
|
||||
}
|
||||
|
||||
// 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
|
||||
throw exception_io_data();
|
||||
return;
|
||||
return; //???
|
||||
}
|
||||
|
||||
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)
|
||||
if ( p_filehint.is_empty() )
|
||||
input_open_file_helper( p_filehint, filename, p_reason, p_abort );
|
||||
stats = p_filehint->get_stats( p_abort );
|
||||
// don't try to open virtual files as it'll fail
|
||||
// (doesn't seem to have any adverse effect, except maybe no stats)
|
||||
// setup_vgmstream also makes further checks before file is finally opened
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, &stats);
|
||||
STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, NULL);
|
||||
if (tagFile != NULL) {
|
||||
VGMSTREAM_TAGS *tags;
|
||||
const char *tag_key, *tag_val;
|
||||
|
@ -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) {
|
||||
size_t length_read_total = 0;
|
||||
|
||||
if (!streamfile || !dest || length <= 0 || offset < 0)
|
||||
if (!streamfile || !streamfile->infile || !dest || length <= 0 || offset < 0)
|
||||
return 0;
|
||||
|
||||
/* 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';
|
||||
}
|
||||
static void close_stdio(STDIOSTREAMFILE * streamfile) {
|
||||
fclose(streamfile->infile);
|
||||
if (streamfile->infile)
|
||||
fclose(streamfile->infile);
|
||||
free(streamfile->buffer);
|
||||
free(streamfile);
|
||||
}
|
||||
@ -124,7 +125,7 @@ static STREAMFILE *open_stdio(STDIOSTREAMFILE *streamFile,const char * const fil
|
||||
return NULL;
|
||||
#if !defined (__ANDROID__)
|
||||
// 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) &&
|
||||
(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);
|
||||
}
|
||||
|
||||
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;
|
||||
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';
|
||||
|
||||
/* cache filesize */
|
||||
fseeko(streamfile->infile,0,SEEK_END);
|
||||
streamfile->filesize = ftello(streamfile->infile);
|
||||
if (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
|
||||
* (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;
|
||||
|
||||
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);
|
||||
if (!streamFile) {
|
||||
fclose(infile);
|
||||
if (infile) fclose(infile);
|
||||
}
|
||||
|
||||
return streamFile;
|
||||
|
@ -2882,3 +2882,20 @@ fail:
|
||||
/* open streams will be closed in close_vgmstream(), hopefully called by the meta */
|
||||
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;
|
||||
}
|
||||
|
@ -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) */
|
||||
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 */
|
||||
/* -------------------------------------------------------------------------*/
|
||||
|
@ -210,7 +210,7 @@ static FILE* wa_fdopen(int fd) {
|
||||
typedef struct {
|
||||
STREAMFILE sf;
|
||||
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;
|
||||
|
||||
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
|
||||
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) &&
|
||||
(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));
|
||||
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;
|
||||
|
||||
this_sf->sf.read = (void*)wasf_read;
|
||||
@ -302,16 +302,21 @@ static STREAMFILE *open_winamp_streamfile_by_ipath(const in_char *wpath) {
|
||||
STREAMFILE *streamFile;
|
||||
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 */
|
||||
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);
|
||||
if (!streamFile) {
|
||||
fclose(infile);
|
||||
if (infile) fclose(infile);
|
||||
}
|
||||
|
||||
return streamFile;
|
||||
|
Loading…
x
Reference in New Issue
Block a user