vgmstream/src/streamfile.h

381 lines
17 KiB
C
Raw Normal View History

/*
* streamfile.h - definitions for buffered file reading with STREAMFILE
*/
#ifndef _STREAMFILE_H
#define _STREAMFILE_H
#ifdef _MSC_VER
#define _CRT_SECURE_NO_DEPRECATE
#endif
2021-10-10 15:09:58 +02:00
//TODO cleanup
//NULL, allocs
#include <stdlib.h>
2021-10-10 15:09:58 +02:00
//FILE
#include <stdio.h>
2021-10-10 15:09:58 +02:00
//string functions in meta and so on
#include <string.h>
2021-10-10 15:09:58 +02:00
//off_t
#include <sys/types.h>
#include "streamtypes.h"
#include "util.h"
/* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */
#if defined(__MSVCRT__) || defined(_MSC_VER)
2021-09-04 21:57:23 +02:00
#include <io.h>
#endif
2021-09-04 21:57:23 +02:00
/* 64-bit offset is needed for banks that hit +2.5GB (like .fsb or .ktsl2stbin).
* Leave as typedef to toggle since it's theoretically slower when compiled as 32-bit.
* ATM it's only used in choice places until more performance tests are done.
* uint32_t could be an option but needs to test when/how neg offsets are used.
*
* On POSIX 32-bit off_t can become off64_t by passing -D_FILE_OFFSET_BITS=64,
* but not on MSVC as it doesn't have proper POSIX support, so a custom type is needed.
* fseeks/tells also need to be adjusted for 64-bit support.
*/
typedef int64_t offv_t; //off64_t
//typedef int64_t sizev_t; // size_t int64_t off64_t
2019-11-03 17:57:07 +01:00
/* Streamfiles normally use an internal buffer to increase performance, configurable
* but usually of this size. Lower increases the number of freads/system calls (slower).
* However some formats need to jump around causing more buffer trashing than usual,
* higher may needlessly read data that may be going to be trashed.
*
* Value can be adjusted freely but 8k is a good enough compromise. */
#define STREAMFILE_DEFAULT_BUFFER_SIZE 0x8000
2017-08-12 11:14:16 +02:00
/* struct representing a file with callbacks. Code should use STREAMFILEs and not std C functions
* to do file operations, as plugins may need to provide their own callbacks.
* Reads from arbitrary offsets, meaning internally may need fseek equivalents during reads. */
typedef struct _STREAMFILE {
2021-09-04 21:57:23 +02:00
/* read 'length' data at 'offset' to 'dst' */
size_t (*read)(struct _STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length);
2017-08-12 11:14:16 +02:00
2021-09-04 21:57:23 +02:00
/* get max offset */
size_t (*get_size)(struct _STREAMFILE* sf);
//todo: DO NOT USE, NOT RESET PROPERLY (remove?)
offv_t (*get_offset)(struct _STREAMFILE* sf);
2021-09-04 21:57:23 +02:00
/* copy current filename to name buf */
void (*get_name)(struct _STREAMFILE* sf, char* name, size_t name_size);
/* open another streamfile from filename */
struct _STREAMFILE* (*open)(struct _STREAMFILE* sf, const char* const filename, size_t buf_size);
2021-09-04 21:57:23 +02:00
/* free current STREAMFILE */
void (*close)(struct _STREAMFILE* sf);
2021-09-04 21:57:23 +02:00
/* Substream selection for formats with subsongs.
* Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */
int stream_index; /* 0=default/auto (first), 1=first, N=Nth */
} STREAMFILE;
2019-10-19 11:07:28 +02:00
/* All open_ fuctions should be safe to call with wrong/null parameters.
* _f versions are the same but free the passed streamfile on failure and return NULL,
* to ease chaining by avoiding realloc-style temp ptr verbosity */
/* Opens a standard STREAMFILE, opening from path.
* Uses stdio (FILE) for operations, thus plugins may not want to use it. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_stdio_streamfile(const char* filename);
/* Opens a standard STREAMFILE from a pre-opened FILE. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_stdio_streamfile_by_file(FILE* file, const char* filename);
2017-08-12 11:14:16 +02:00
/* Opens a STREAMFILE that does buffered IO.
* Can be used when the underlying IO may be slow (like when using custom IO).
* Buffer size is optional. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_buffer_streamfile(STREAMFILE* sf, size_t buffer_size);
STREAMFILE* open_buffer_streamfile_f(STREAMFILE* sf, size_t buffer_size);
/* Opens a STREAMFILE that doesn't close the underlying streamfile.
* Calls to open won't wrap the new SF (assumes it needs to be closed).
* Can be used in metas to test custom IO without closing the external SF. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_wrap_streamfile(STREAMFILE* sf);
STREAMFILE* open_wrap_streamfile_f(STREAMFILE* sf);
/* Opens a STREAMFILE that clamps reads to a section of a larger streamfile.
* Can be used with subfiles inside a bigger file (to fool metas, or to simplify custom IO). */
2021-09-04 21:57:23 +02:00
STREAMFILE* open_clamp_streamfile(STREAMFILE* sf, offv_t start, size_t size);
STREAMFILE* open_clamp_streamfile_f(STREAMFILE* sf, offv_t start, size_t size);
/* Opens a STREAMFILE that uses custom IO for streamfile reads.
* Can be used to modify data on the fly (ex. decryption), or even transform it from a format to another.
* Data is an optional state struct of some size what will be malloc+copied on open. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_io_streamfile(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback);
STREAMFILE* open_io_streamfile_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback);
/* Same, but calls init on SF open and close on close, when malloc/free is needed.
* Data struct may be used to hold malloc'd pointers and stuff. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_io_streamfile_ex(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback);
STREAMFILE* open_io_streamfile_ex_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback);
/* Opens a STREAMFILE that reports a fake name, but still re-opens itself properly.
* Can be used to trick a meta's extension check (to call from another, with a modified SF).
* When fakename isn't supplied it's read from the streamfile, and the extension swapped with fakeext.
* If the fakename is an existing file, open won't work on it as it'll reopen the fake-named streamfile. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const char* fakeext);
STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext);
/* Opens streamfile formed from multiple streamfiles, their data joined during reads.
2018-01-27 13:58:46 +01:00
* Can be used when data is segmented in multiple separate files.
* The first streamfile is used to get names, stream index and so on. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_multifile_streamfile(STREAMFILE** sfs, size_t sfs_size);
STREAMFILE* open_multifile_streamfile_f(STREAMFILE** sfs, size_t sfs_size);
2018-01-27 13:58:46 +01:00
2018-08-04 20:42:00 +02:00
/* Opens a STREAMFILE from a (path)+filename.
* Just a wrapper, to avoid having to access the STREAMFILE's callbacks directly. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_streamfile(STREAMFILE* sf, const char* pathname);
2018-08-04 20:42:00 +02:00
/* Opens a STREAMFILE from a base pathname + new extension
* Can be used to get companion headers. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_streamfile_by_ext(STREAMFILE* sf, const char* ext);
/* Opens a STREAMFILE from a base path + new filename.
* Can be used to get companion files. Relative paths like
* './filename', '../filename', 'dir/filename' also work. */
2020-07-18 00:27:53 +02:00
STREAMFILE* open_streamfile_by_filename(STREAMFILE* sf, const char* filename);
2019-01-01 23:21:08 +01:00
/* Reopen a STREAMFILE with a different buffer size, for fine-tuned bigfile parsing.
* Uses default buffer size when buffer_size is 0 */
2020-07-18 00:27:53 +02:00
STREAMFILE* reopen_streamfile(STREAMFILE* sf, size_t buffer_size);
2019-01-01 23:21:08 +01:00
2017-08-12 11:14:16 +02:00
/* close a file, destroy the STREAMFILE object */
2020-07-18 00:27:53 +02:00
static inline void close_streamfile(STREAMFILE* sf) {
if (sf != NULL)
sf->close(sf);
}
2017-08-12 11:14:16 +02:00
/* read from a file, returns number of bytes read */
2021-09-04 21:57:23 +02:00
static inline size_t read_streamfile(uint8_t* dst, offv_t offset, size_t length, STREAMFILE* sf) {
return sf->read(sf, dst, offset, length);
}
/* return file size */
2020-07-18 00:27:53 +02:00
static inline size_t get_streamfile_size(STREAMFILE* sf) {
return sf->get_size(sf);
}
/* Sometimes you just need an int, and we're doing the buffering.
* Note, however, that if these fail to read they'll return -1,
* so that should not be a valid value or there should be some backup. */
2020-07-18 00:27:53 +02:00
static inline int16_t read_16bitLE(off_t offset, STREAMFILE* sf) {
uint8_t buf[2];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,2,sf)!=2) return -1;
return get_16bitLE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int16_t read_16bitBE(off_t offset, STREAMFILE* sf) {
uint8_t buf[2];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,2,sf)!=2) return -1;
return get_16bitBE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int32_t read_32bitLE(off_t offset, STREAMFILE* sf) {
uint8_t buf[4];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,4,sf)!=4) return -1;
return get_32bitLE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int32_t read_32bitBE(off_t offset, STREAMFILE* sf) {
uint8_t buf[4];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,4,sf)!=4) return -1;
return get_32bitBE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int64_t read_64bitLE(off_t offset, STREAMFILE* sf) {
uint8_t buf[8];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,8,sf)!=8) return -1;
return get_64bitLE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int64_t read_64bitBE(off_t offset, STREAMFILE* sf) {
uint8_t buf[8];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,8,sf)!=8) return -1;
return get_64bitBE(buf);
}
2020-07-18 00:27:53 +02:00
static inline int8_t read_8bit(off_t offset, STREAMFILE* sf) {
uint8_t buf[1];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf,offset,1,sf)!=1) return -1;
return buf[0];
}
/* alias of the above */
2020-07-18 00:27:53 +02:00
static inline int8_t read_s8 (off_t offset, STREAMFILE* sf) { return read_8bit(offset, sf); }
static inline uint8_t read_u8 (off_t offset, STREAMFILE* sf) { return (uint8_t) read_8bit(offset, sf); }
static inline int16_t read_s16le(off_t offset, STREAMFILE* sf) { return read_16bitLE(offset, sf); }
static inline uint16_t read_u16le(off_t offset, STREAMFILE* sf) { return (uint16_t)read_16bitLE(offset, sf); }
static inline int16_t read_s16be(off_t offset, STREAMFILE* sf) { return read_16bitBE(offset, sf); }
static inline uint16_t read_u16be(off_t offset, STREAMFILE* sf) { return (uint16_t)read_16bitBE(offset, sf); }
static inline int32_t read_s32le(off_t offset, STREAMFILE* sf) { return read_32bitLE(offset, sf); }
static inline uint32_t read_u32le(off_t offset, STREAMFILE* sf) { return (uint32_t)read_32bitLE(offset, sf); }
static inline int32_t read_s32be(off_t offset, STREAMFILE* sf) { return read_32bitBE(offset, sf); }
static inline uint32_t read_u32be(off_t offset, STREAMFILE* sf) { return (uint32_t)read_32bitBE(offset, sf); }
static inline int64_t read_s64be(off_t offset, STREAMFILE* sf) { return read_64bitBE(offset, sf); }
static inline uint64_t read_u64be(off_t offset, STREAMFILE* sf) { return (uint64_t)read_64bitBE(offset, sf); }
static inline int64_t read_s64le(off_t offset, STREAMFILE* sf) { return read_64bitLE(offset, sf); }
static inline uint64_t read_u64le(off_t offset, STREAMFILE* sf) { return (uint64_t)read_64bitLE(offset, sf); }
2019-11-02 20:12:12 +01:00
2021-09-11 13:10:04 +02:00
static inline float read_f32be(off_t offset, STREAMFILE* sf) {
uint8_t buf[4];
if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf))
return -1;
return get_f32be(buf);
2019-11-02 20:12:12 +01:00
}
2020-07-18 00:27:53 +02:00
static inline float read_f32le(off_t offset, STREAMFILE* sf) {
2021-09-11 13:10:04 +02:00
uint8_t buf[4];
if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf))
return -1;
return get_f32le(buf);
2019-03-02 19:23:37 +01:00
}
2021-07-04 20:09:26 +02:00
2019-11-02 20:12:12 +01:00
#if 0
2021-07-04 20:09:26 +02:00
// on GCC, this reader will be correctly optimized out (as long as it's static/inline), would be same as declaring:
// uintXX_t (*read_uXX)(off_t,uint8_t*) = be ? get_uXXbe : get_uXXle;
// only for the functions actually used in code, and inlined if possible (like big_endian param being a constant).
// on MSVC seems all read_X in sf_reader are compiled and included in the translation unit, plus ignores constants
// so may result on bloatness?
// (from godbolt tests, test more real cases)
2019-11-02 20:12:12 +01:00
/* collection of callbacks for quick access */
typedef struct sf_reader {
2021-07-04 20:09:26 +02:00
int32_t (*read_s32)(off_t,STREAMFILE*); //maybe r.s32
2019-11-02 20:12:12 +01:00
float (*read_f32)(off_t,STREAMFILE*);
/* ... */
} sf_reader;
2021-07-04 20:09:26 +02:00
static inline void sf_reader_init(sf_reader* r, int big_endian) {
2019-11-02 20:12:12 +01:00
memset(r, 0, sizeof(sf_reader));
if (big_endian) {
r->read_s32 = read_s32be;
r->read_f32 = read_f32be;
}
else {
r->read_s32 = read_s32le;
r->read_f32 = read_f32le;
}
}
2021-07-04 20:09:26 +02:00
2019-11-02 20:12:12 +01:00
/* sf_reader r;
* ...
* sf_reader_init(&r, big_endian);
* val = r.read_s32; //maybe r.s32?
*/
#endif
#if 0 //todo improve + test + simplify code (maybe not inline?)
2020-07-18 00:27:53 +02:00
static inline int read_s4h(off_t offset, STREAMFILE* sf) {
2019-03-02 19:23:37 +01:00
uint8_t byte = read_u8(offset, streamfile);
return get_nibble_signed(byte, 1);
}
2020-07-18 00:27:53 +02:00
static inline int read_u4h(off_t offset, STREAMFILE* sf) {
2019-03-02 19:23:37 +01:00
uint8_t byte = read_u8(offset, streamfile);
return (byte >> 4) & 0x0f;
}
2020-07-18 00:27:53 +02:00
static inline int read_s4l(off_t offset, STREAMFILE* sf) {
2019-03-02 19:23:37 +01:00
...
}
2020-07-18 00:27:53 +02:00
static inline int read_u4l(off_t offset, STREAMFILE* sf) {
2019-03-02 19:23:37 +01:00
...
}
static inline int max_s32(int32_t a, int32_t b) { return a > b ? a : b; }
static inline int min_s32(int32_t a, int32_t b) { return a < b ? a : b; }
//align32, align16, clamp16, etc
#endif
2021-01-12 15:13:04 +01:00
/* fastest to compare would be read_u32x == (uint32), but should be pre-optimized (see get_id32x) */
2021-01-23 15:49:29 +01:00
static inline /*const*/ int is_id32be(off_t offset, STREAMFILE* sf, const char* s) {
2020-12-19 12:51:31 +01:00
return read_u32be(offset, sf) == get_id32be(s);
}
2021-01-23 15:49:29 +01:00
static inline /*const*/ int is_id32le(off_t offset, STREAMFILE* sf, const char* s) {
2021-01-12 15:13:04 +01:00
return read_u32le(offset, sf) == get_id32be(s);
}
2021-01-23 15:49:29 +01:00
static inline /*const*/ int is_id64be(off_t offset, STREAMFILE* sf, const char* s) {
return read_u64be(offset, sf) == get_id64be(s);
}
2021-01-12 15:13:04 +01:00
2019-11-02 20:12:12 +01:00
//TODO: maybe move to streamfile.c
/* guess byte endianness from a given value, return true if big endian and false if little endian */
2020-07-18 00:27:53 +02:00
static inline int guess_endianness16bit(off_t offset, STREAMFILE* sf) {
2019-02-03 01:46:05 +01:00
uint8_t buf[0x02];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf, offset, 0x02, sf) != 0x02) return -1; /* ? */
2021-07-04 20:09:26 +02:00
return get_u16le(buf) > get_u16be(buf) ? 1 : 0;
}
2020-07-18 00:27:53 +02:00
static inline int guess_endianness32bit(off_t offset, STREAMFILE* sf) {
2019-02-03 01:46:05 +01:00
uint8_t buf[0x04];
2020-07-18 00:27:53 +02:00
if (read_streamfile(buf, offset, 0x04, sf) != 0x04) return -1; /* ? */
2021-07-04 20:09:26 +02:00
return get_u32le(buf) > get_u32be(buf) ? 1 : 0;
}
2018-12-31 21:01:46 +01:00
static inline size_t align_size_to_block(size_t value, size_t block_align) {
2018-12-30 21:39:27 +01:00
size_t extra_size = value % block_align;
if (extra_size == 0) return value;
return (value + block_align - extra_size);
}
2017-08-12 11:14:16 +02:00
/* various STREAMFILE helpers functions */
/* Read into dst a line delimited by CRLF (Windows) / LF (Unux) / CR (Mac) / EOF, null-terminated
* and without line feeds. Returns bytes read (including CR/LF), *not* the same as string length.
* p_line_ok is set to 1 if the complete line was read; pass NULL to ignore. */
2020-07-18 00:27:53 +02:00
size_t read_line(char* buf, int buf_size, off_t offset, STREAMFILE* sf, int* p_line_ok);
2021-07-08 22:26:21 +02:00
/* skip BOM if needed */
size_t read_bom(STREAMFILE* sf);
/* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */
2020-07-18 00:27:53 +02:00
size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
2019-11-03 22:56:37 +01:00
/* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */
size_t read_string_utf16(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf, int big_endian);
size_t read_string_utf16le(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
size_t read_string_utf16be(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
/* Opens a file containing decryption keys and copies to buffer.
* Tries "(name.ext)key" (per song), "(.ext)key" (per folder) keynames.
* returns size of key if found and copied */
2020-07-18 00:27:53 +02:00
size_t read_key_file(uint8_t* buf, size_t buf_size, STREAMFILE* sf);
/* Opens .txtm file containing file:companion file(-s) mappings and tries to see if there's a match
* then loads the associated companion file if one is found */
STREAMFILE* read_filemap_file(STREAMFILE *sf, int file_num);
STREAMFILE* read_filemap_file_pos(STREAMFILE *sf, int file_num, int* p_pos);
/* hack to allow relative paths in various OSs */
2020-07-18 00:27:53 +02:00
void fix_dir_separators(char* filename);
2018-11-24 00:44:17 +01:00
/* Checks if the stream filename is one of the extensions (comma-separated, ex. "adx" or "adx,aix").
* Empty is ok to accept files without extension ("", "adx,,aix"). Returns 0 on failure */
2020-07-18 00:27:53 +02:00
int check_extensions(STREAMFILE* sf, const char* cmp_exts);
/* chunk-style file helpers */
2020-07-18 00:27:53 +02:00
int find_chunk_be(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t* p_chunk_offset, size_t* p_chunk_size);
int find_chunk_le(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t* p_chunk_offset, size_t* p_chunk_size);
int find_chunk(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t* p_chunk_offset, size_t* p_chunk_size, int big_endian_size, int zero_size_end);
2019-09-14 12:39:47 +02:00
/* find a RIFF-style chunk (with chunk_size not including id and size) */
2020-07-18 00:27:53 +02:00
int find_chunk_riff_le(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t* p_chunk_offset, size_t* p_chunk_size);
int find_chunk_riff_be(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t* p_chunk_offset, size_t* p_chunk_size);
2019-09-14 12:39:47 +02:00
/* same with chunk ids in variable endianess (so instead of "fmt " has " tmf" */
2020-07-18 00:27:53 +02:00
int find_chunk_riff_ve(STREAMFILE* sf, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t* p_chunk_offset, size_t* p_chunk_size, int big_endian);
2019-09-08 20:23:59 +02:00
/* filename helpers */
2020-07-18 00:27:53 +02:00
void get_streamfile_name(STREAMFILE* sf, char* buf, size_t size);
void get_streamfile_filename(STREAMFILE* sf, char* buf, size_t size);
void get_streamfile_basename(STREAMFILE* sf, char* buf, size_t size);
void get_streamfile_path(STREAMFILE* sf, char* buf, size_t size);
void get_streamfile_ext(STREAMFILE* sf, char* buf, size_t size);
2020-07-18 00:27:53 +02:00
void dump_streamfile(STREAMFILE* sf, int num);
#endif