sm64coopdx/data/dynos_bin_compress.cpp
PeachyPeach 32f395fb0c
Some checks failed
Build coop / build-linux (push) Has been cancelled
Build coop / build-steamos (push) Has been cancelled
Build coop / build-windows-opengl (push) Has been cancelled
Build coop / build-windows-directx (push) Has been cancelled
Build coop / build-macos-arm (push) Has been cancelled
Build coop / build-macos-intel (push) Has been cancelled
ModFs improvements (#907)
* zip + json properties; check existing file in create file

* smlua_audio_utils_replace_sequence

* audio_stream_load, audio_sample_load, smlua_model_util_get_id

* get_texture_info + can also load PNG files

* smlua_collision_util_get

* add wildcard in properties files + set text mode

* filepath restrictions

* Some mod_storage improvements

- Cache mod storage files into a map to reduce file I/O
- Fix a bug in mod_storage_save
- Add mod_storage_load_all that returns all keys/values as a table

* shutdown; fix buffer overflow; fix warnings; lua table

* reject binary files starting with MZ or ELF

* function members

* better doc

* adding file rewind

* ModFS guide; replace yaml by ini; read string buffer changes
2025-10-21 19:42:06 +02:00

306 lines
11 KiB
C++

#include "dynos.cpp.h"
#include <zlib.h>
extern "C" {
#include "pc/mods/mod_fs.h"
}
static const u64 DYNOS_BIN_COMPRESS_MAGIC = 0x4E4942534F4E5944llu;
static FILE *sFile = NULL;
static u8 *sBufferUncompressed = NULL;
static u8 *sBufferCompressed = NULL;
static u64 sLengthUncompressed = 0;
static u64 sLengthCompressed = 0;
static inline void DynOS_Bin_Compress_Init() {
sFile = NULL;
sBufferUncompressed = NULL;
sBufferCompressed = NULL;
sLengthUncompressed = 0;
sLengthCompressed = 0;
}
static inline void DynOS_Bin_Compress_Close() {
if (sFile) f_close(sFile);
sFile = NULL;
}
static inline void DynOS_Bin_Compress_Free() {
if (sBufferCompressed) free(sBufferCompressed);
if (sBufferUncompressed) free(sBufferUncompressed);
DynOS_Bin_Compress_Close();
}
static inline bool DynOS_Bin_Compress_Check(bool condition, const char *function, const char *filename, const char *message) {
if (!condition) {
PrintError("ERROR: %s: File \"%s\": %s", function, filename, message);
DynOS_Bin_Compress_Free();
return false;
}
return true;
}
bool DynOS_Bin_IsCompressed(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
// Open input file
if (!DynOS_Bin_Compress_Check(
(sFile = fopen(aFilename.c_str(), "rb")) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot open file"
)) return false;
// Read magic
u64 _Magic = 0;
if (!DynOS_Bin_Compress_Check(
fread(&_Magic, sizeof(u64), 1, sFile) == 1,
__FUNCTION__, aFilename.c_str(), "Cannot read magic"
)) return false;
// Compare with magic constant
if (_Magic != DYNOS_BIN_COMPRESS_MAGIC) {
DynOS_Bin_Compress_Free();
return false;
}
// It is a compressed file
DynOS_Bin_Compress_Free();
return true;
}
bool DynOS_Bin_Compress(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
PrintNoNewLine("Compressing file \"%s\"...", aFilename.c_str());
// Open input file
if (!DynOS_Bin_Compress_Check(
(sFile = fopen(aFilename.c_str(), "rb")) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot open file"
)) return false;
// Retrieve file length
if (!DynOS_Bin_Compress_Check(
fseek(sFile, 0, SEEK_END) == 0,
__FUNCTION__, aFilename.c_str(), "Cannot retrieve file length"
)) return false;
// Check file length
if (!DynOS_Bin_Compress_Check(
(sLengthUncompressed = (u64) ftell(sFile)) != 0,
__FUNCTION__, aFilename.c_str(), "Empty file"
)) return false;
// Allocate memory for uncompressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferUncompressed = (u8 *) calloc(sLengthUncompressed, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for compression"
)) return false; else rewind(sFile);
// Read input data
if (!DynOS_Bin_Compress_Check(
fread(sBufferUncompressed, sizeof(u8), sLengthUncompressed, sFile) == sLengthUncompressed,
__FUNCTION__, aFilename.c_str(), "Cannot read uncompressed data"
)) return false; else DynOS_Bin_Compress_Close();
// Compute maximum output file size
if (!DynOS_Bin_Compress_Check(
(sLengthCompressed = compressBound(sLengthUncompressed)) != 0,
__FUNCTION__, aFilename.c_str(), "Cannot compute compressed size"
)) return false;
// Allocate memory for compressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferCompressed = (u8 *) calloc(sLengthCompressed, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for compression"
)) return false;
// Compress data
uLongf _LengthCompressed = (uLongf)sLengthCompressed;
if (!DynOS_Bin_Compress_Check(
compress2(sBufferCompressed, &_LengthCompressed, sBufferUncompressed, sLengthUncompressed, Z_BEST_COMPRESSION) == Z_OK,
__FUNCTION__, aFilename.c_str(), "Cannot compress data"
)) return false;
sLengthCompressed = _LengthCompressed;
// Check output length
// If the compression generates a bigger file, skip the process, but don't return a failure
if (!DynOS_Bin_Compress_Check(
sLengthCompressed < sLengthUncompressed,
__FUNCTION__, aFilename.c_str(), "Compressed data is bigger than uncompressed; Skipping compression"
)) return true;
// Open output file
if (!DynOS_Bin_Compress_Check(
(sFile = fopen(aFilename.c_str(), "wb")) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot open file"
)) return false;
// Write magic
if (!DynOS_Bin_Compress_Check(
fwrite(&DYNOS_BIN_COMPRESS_MAGIC, sizeof(u64), 1, sFile) == 1,
__FUNCTION__, aFilename.c_str(), "Cannot write magic"
)) return false;
// Write uncompressed file size
if (!DynOS_Bin_Compress_Check(
fwrite(&sLengthUncompressed, sizeof(u64), 1, sFile) == 1,
__FUNCTION__, aFilename.c_str(), "Cannot write uncompressed file size"
)) return false;
// Write compressed data
if (!DynOS_Bin_Compress_Check(
fwrite(sBufferCompressed, sizeof(u8), sLengthCompressed, sFile) == sLengthCompressed,
__FUNCTION__, aFilename.c_str(), "Cannot write compressed data"
)) return false;
// Done, free buffers and files
DynOS_Bin_Compress_Free();
Print(" Done.");
return true;
}
static BinFile *DynOS_Bin_Decompress_ModFs(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
// Read file data
void *_Buffer = NULL;
u32 _Size = 0;
if (!mod_fs_read_file_from_uri(aFilename.c_str(), &_Buffer, &_Size)) {
DynOS_Bin_Compress_Free();
return NULL;
}
sBufferCompressed = (u8 *) _Buffer;
sLengthCompressed = _Size;
// Check file length
u64 _LengthHeader = (u64) (sizeof(u64) + sizeof(u64));
if (!DynOS_Bin_Compress_Check(
sLengthCompressed >= _LengthHeader,
__FUNCTION__, aFilename.c_str(), "Empty file"
)) return NULL;
// Compare with magic constant
// If not equal, it's not a compressed file
u64 _Magic = ((u64 *) _Buffer)[0];
if (_Magic != DYNOS_BIN_COMPRESS_MAGIC) {
BinFile *_BinFile = BinFile::OpenB(sBufferCompressed, sLengthCompressed);
DynOS_Bin_Compress_Free();
return _BinFile;
}
PrintNoNewLine("Decompressing file \"%s\"...", aFilename.c_str());
// Read expected uncompressed file size
sLengthUncompressed = ((u64 *) _Buffer)[1];
sLengthCompressed -= _LengthHeader;
u8 *_BufferCompressed = sBufferCompressed + _LengthHeader;
// Allocate memory for uncompressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferUncompressed = (u8 *) calloc(sLengthUncompressed, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for decompression"
)) return NULL;
// Uncompress data
uLongf _LengthUncompressed = (uLongf)sLengthUncompressed;
int uncompressRc = uncompress(sBufferUncompressed, &_LengthUncompressed, _BufferCompressed, sLengthCompressed);
sLengthUncompressed = _LengthUncompressed;
if (!DynOS_Bin_Compress_Check(
uncompressRc == Z_OK,
__FUNCTION__, aFilename.c_str(), "Cannot uncompress data"
)) {
PrintError("ERROR: uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
return NULL;
}
Print("uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
// Return uncompressed data as a BinFile
BinFile *_BinFile = BinFile::OpenB(sBufferUncompressed, sLengthUncompressed);
DynOS_Bin_Compress_Free();
Print(" Done.");
return _BinFile;
}
BinFile *DynOS_Bin_Decompress(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
// Check modfs
if (is_mod_fs_file(aFilename.c_str())) {
return DynOS_Bin_Decompress_ModFs(aFilename);
}
// Open input file
if (!DynOS_Bin_Compress_Check(
(sFile = f_open_r(aFilename.c_str())) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot open file"
)) return NULL;
// Read magic
u64 _Magic = 0;
if (!DynOS_Bin_Compress_Check(
f_read(&_Magic, sizeof(u64), 1, sFile) == 1,
__FUNCTION__, aFilename.c_str(), "Cannot read magic"
)) return NULL;
// Compare with magic constant
// If not equal, it's not a compressed file
if (_Magic != DYNOS_BIN_COMPRESS_MAGIC) {
DynOS_Bin_Compress_Free();
return BinFile::OpenR(aFilename.c_str());
}
PrintNoNewLine("Decompressing file \"%s\"...", aFilename.c_str());
// Read expected uncompressed file size
if (!DynOS_Bin_Compress_Check(
f_read(&sLengthUncompressed, sizeof(u64), 1, sFile) == 1,
__FUNCTION__, aFilename.c_str(), "Cannot read uncompressed file size"
)) return NULL;
// Retrieve file length
if (!DynOS_Bin_Compress_Check(
f_seek(sFile, 0, SEEK_END) == 0,
__FUNCTION__, aFilename.c_str(), "Cannot retrieve file length"
)) return NULL;
// Check file length
u64 _LengthHeader = (u64) (sizeof(u64) + sizeof(u64));
if (!DynOS_Bin_Compress_Check(
(sLengthCompressed = (u64) f_tell(sFile)) >= _LengthHeader,
__FUNCTION__, aFilename.c_str(), "Empty file"
)) return NULL;
// Allocate memory for compressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferCompressed = (u8 *) calloc(sLengthCompressed - _LengthHeader, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for decompression"
)) return NULL; else f_seek(sFile, _LengthHeader, SEEK_SET);
// Read input data
if (!DynOS_Bin_Compress_Check(
f_read(sBufferCompressed, sizeof(u8), sLengthCompressed - _LengthHeader, sFile) == sLengthCompressed - _LengthHeader,
__FUNCTION__, aFilename.c_str(), "Cannot read compressed data"
)) return NULL; else DynOS_Bin_Compress_Close();
// Allocate memory for uncompressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferUncompressed = (u8 *) calloc(sLengthUncompressed, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for decompression"
)) return NULL;
// Uncompress data
uLongf _LengthUncompressed = (uLongf)sLengthUncompressed;
int uncompressRc = uncompress(sBufferUncompressed, &_LengthUncompressed, sBufferCompressed, sLengthCompressed);
sLengthUncompressed = _LengthUncompressed;
if (!DynOS_Bin_Compress_Check(
uncompressRc == Z_OK,
__FUNCTION__, aFilename.c_str(), "Cannot uncompress data"
)) {
PrintError("ERROR: uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
return NULL;
}
Print("uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
// Return uncompressed data as a BinFile
BinFile *_BinFile = BinFile::OpenB(sBufferUncompressed, sLengthUncompressed);
DynOS_Bin_Compress_Free();
Print(" Done.");
return _BinFile;
}