Skip to content
56 changes: 43 additions & 13 deletions src/data_win.c
Original file line number Diff line number Diff line change
Expand Up @@ -2328,7 +2328,7 @@ static void parseSTRG(BinaryReader* reader, DataWin* dw) {
free(ptrs);
}

static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd) {
static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd, bool loadTextureDataLazily) {
Txtr* t = &dw->txtr;

uint32_t count;
Expand Down Expand Up @@ -2404,9 +2404,36 @@ static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd) {
}

// Load blob data into owned buffers
repeat(count, i) {
if (t->textures[i].blobOffset == 0 || t->textures[i].blobSize == 0) continue;
t->textures[i].blobData = BinaryReader_readBytesAt(reader, t->textures[i].blobOffset, t->textures[i].blobSize);
if (!loadTextureDataLazily) {
repeat(count, i) {
if (t->textures[i].blobOffset == 0 || t->textures[i].blobSize == 0) continue;
t->textures[i].blobData = BinaryReader_readBytesAt(reader, t->textures[i].blobOffset, t->textures[i].blobSize);
}
}
}

void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId) {
Txtr* t = &dw->txtr;
Texture* tex = &t->textures[textureId];

if (tex->blobOffset == 0 || tex->blobSize == 0) return;
if (tex->blobData != nullptr) return;

if (!dw->lazyLoadFile) {
fprintf(stderr, "%s: called without a lazy load file.\n", __func__);
return;
}

tex->blobData = (uint8_t *)safeMalloc(tex->blobSize);

memset(tex->blobData, 0, tex->blobSize);
long old_seek = ftell(dw->lazyLoadFile);
fseek(dw->lazyLoadFile, tex->blobOffset, SEEK_SET);
size_t read = fread(tex->blobData, 1, tex->blobSize, dw->lazyLoadFile);
fseek(dw->lazyLoadFile, old_seek, SEEK_SET);

if (read != tex->blobSize) {
fprintf(stderr, "%s: couldn't read %u bytes to load a texture.\n", __func__, tex->blobSize);
}
}

Expand Down Expand Up @@ -2590,13 +2617,15 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) {
// Bulk-read the chunk data into memory for fast parsing
uint8_t* chunkBuffer = nullptr;
if (shouldParse && chunkLength > 0 && options.loadType != DATAWINLOADTYPE_LOAD_IN_MEMORY_AHEAD_OF_TIME) {
chunkBuffer = (uint8_t *)safeMalloc(chunkLength);
size_t read = fread(chunkBuffer, 1, chunkLength, reader.file);
if (read != chunkLength) {
fprintf(stderr, "DataWin: short read on chunk %.4s (expected %u, got %zu)\n", chunkName, chunkLength, read);
exit(1);
chunkBuffer = (uint8_t *)malloc(chunkLength);
if (chunkBuffer) {
size_t read = fread(chunkBuffer, 1, chunkLength, reader.file);
if (read != chunkLength) {
fprintf(stderr, "DataWin: short read on chunk %.4s (expected %u, got %zu)\n", chunkName, chunkLength, read);
exit(1);
}
BinaryReader_setBuffer(&reader, chunkBuffer, chunkDataStart, chunkLength);
}
BinaryReader_setBuffer(&reader, chunkBuffer, chunkDataStart, chunkLength);
}

if (options.parseGen8 && memcmp(chunkName, "GEN8", 4) == 0) {
Expand Down Expand Up @@ -2661,7 +2690,7 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) {
} else if (options.parseStrg && memcmp(chunkName, "STRG", 4) == 0) {
parseSTRG(&reader, dw);
} else if (options.parseTxtr && memcmp(chunkName, "TXTR", 4) == 0) {
parseTXTR(&reader, dw, chunkEnd);
parseTXTR(&reader, dw, chunkEnd, options.lazyLoadTextures);
} else if (options.parseAudo && memcmp(chunkName, "AUDO", 4) == 0) {
parseAUDO(&reader, dw);
} else {
Expand Down Expand Up @@ -2694,7 +2723,8 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) {

// If lazy-loading rooms, keep the file handle open for DataWin_loadRoomPayload, otherwise close it now
dw->lazyLoadRooms = options.lazyLoadRooms;
if (options.lazyLoadRooms) {
dw->lazyLoadTextures = options.lazyLoadTextures;
if (options.lazyLoadRooms || options.lazyLoadTextures) {
dw->lazyLoadFile = file;
dw->lazyLoadFilePath = safeStrdup(filePath);
dw->fileSize = (size_t) fileSize;
Expand Down Expand Up @@ -2916,7 +2946,7 @@ void DataWin_free(DataWin* dw) {
free(dw->strgBuffer);
free(dw->bytecodeBuffer);

// Close the lazy-load file handle (only open when lazyLoadRooms was enabled)
// Close the lazy-load file handle (only open when lazyLoadRooms/lazyLoadTextures was enabled)
if (dw->lazyLoadFile != nullptr) {
fclose(dw->lazyLoadFile);
dw->lazyLoadFile = nullptr;
Expand Down
4 changes: 4 additions & 0 deletions src/data_win.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ typedef struct {

// If true, Room payloads (backgrounds, views, gameObjects, tiles, layers) are parsed on demand via DataWin_loadRoomPayload during gameplay.
bool lazyLoadRooms;
// If true, TXTR objects will be loaded on demand via DataWin_loadTxtrIfNeeded, and unloaded if memory is tight.
bool lazyLoadTextures;

// When lazyLoadRooms is true, this list indicates which rooms should be loaded during load time instead of demand. They will also not be freed.
StringBooleanEntry* eagerlyLoadedRooms;
Expand Down Expand Up @@ -930,6 +932,7 @@ struct DataWin {
char* lazyLoadFilePath; // owned strdup of the original file path, for diagnostics
size_t fileSize; // cached size of the DataWin, captured at parse time. Used for platforms where fseek(SEEK_END)+ftell is unreliable due to buffering (like the PlayStation 2).
bool lazyLoadRooms; // mirrors the parser option so Runner can branch without re-reading options
bool lazyLoadTextures; // ditto, but with TXTR pages
};

DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options);
Expand All @@ -949,5 +952,6 @@ bool DataWin_isVersionAtLeast(const DataWin* dw, uint32_t major, uint32_t minor,
void DataWin_bumpVersionTo(DataWin* dw, uint32_t major, uint32_t minor, uint32_t release, uint32_t build);
void GamePath_computeInternal(GamePath* path);
PathPositionResult GamePath_getPosition(GamePath* path, float t);
void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId);

#endif /* _BS_DATA_WIN_H_ */
1 change: 1 addition & 0 deletions src/desktop/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ int main(int argc, char* argv[]) {
options.skipLoadingPreciseMasksForNonPreciseSprites = true;
options.loadType = args.loadType;
options.lazyLoadRooms = args.lazyRooms;
options.lazyLoadTextures = args.lazyTextures;
options.eagerlyLoadedRooms = args.eagerRooms;
DataWin* dataWin = DataWin_parse(currentDataWinPath, options);

Expand Down
2 changes: 2 additions & 0 deletions src/gl/gl_renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,8 @@ bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) {
DataWin* dw = gl->base.dataWin;
Texture* txtr = &dw->txtr.textures[pageId];

DataWin_loadTxtrIfNeeded(dw, pageId);

int w, h;
bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0);
uint8_t* pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h);
Expand Down
2 changes: 2 additions & 0 deletions src/gl_legacy/gl_legacy_renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId)
DataWin* dw = gl->base.dataWin;
Texture* txtr = &dw->txtr.textures[pageId];

DataWin_loadTxtrIfNeeded(dw, pageId);

bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0);
uint8_t* pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h);
if (pixels == nullptr) {
Expand Down
Loading