Files
dusk/src/asset/asset.cpp
Dominic Masters 2e0f5f302b
Some checks failed
Build Dusk / build-linux (push) Failing after 2m7s
Build Dusk / build-psp (push) Failing after 2m19s
Nuke error.hpp
2025-12-22 14:19:07 +10:00

300 lines
7.8 KiB
C++

/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "asset.hpp"
#include "util/memory.hpp"
#include "util/string.hpp"
#include "assert/assert.hpp"
#include "asset/assettype.h"
#include "engine/Engine.hpp"
#include "debug/debug.hpp"
using namespace Dusk;
void assetInit(void) {
memoryZero(&ASSET, sizeof(asset_t));
// Engine may have been provided the launch path
if(Engine::ENGINE.argc > 0) {
// This first arg is the executable, so on most platforms it is say
// "/path/file" or "C:\Path\file.exe". On PSP this would be something
// like "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or if we are debugging it is
// "host0:/Dusk.prx"
// Get the directory of the executable
char_t buffer[FILENAME_MAX];
stringCopy(buffer, Engine::ENGINE.argv[0], FILENAME_MAX);
size_t len = strlen(buffer);
// Normalize slashes
for(size_t i = 0; i < FILENAME_MAX; i++) {
if(buffer[i] == '\0') break;
if(buffer[i] == '\\') buffer[i] = '/';
}
// Now find the last slash
char_t *end = buffer + len - 1;
do {
end--;
if(*end == '/') {
*end = '\0';
break;
}
} while(end != buffer);
// Did we find a slash?
if(end != buffer) {
// We found the directory, set as system path
stringCopy(ASSET.systemPath, buffer, FILENAME_MAX);
}
}
// Default system path, intended to be overridden by the platform
stringCopy(ASSET.systemPath, ".", FILENAME_MAX);
// PSP specific time.
#if PSP
assertTrue(ENGINE.argc >= 1, "PSP requires launch argument.");
// PSP is given either host0:/Dusk.prx (debugging) OR the PBP file.
if(
stringEndsWithCaseInsensitive(ENGINE.argv[0], ".pbp") ||
ASSET_PBP_READ_PBP_FROM_HOST
) {
const char_t *pbpPath = (
ASSET_PBP_READ_PBP_FROM_HOST ? "./EBOOT.PBP" : ENGINE.argv[0]
);
ASSET.pbpFile = fopen(pbpPath, "rb");
if(ASSET.pbpFile == NULL) {
throw std::runtime_error(
"Failed to open PBP file: " + std::string(pbpPath)
);
}
// Get size of PBP file.
if(fseek(ASSET.pbpFile, 0, SEEK_END) != 0) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to seek to end of PBP file : " + std::string(pbpPath)
);
}
size_t pbpSize = ftell(ASSET.pbpFile);
// Rewind to start
if(fseek(ASSET.pbpFile, 0, SEEK_SET) != 0) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to seek to start of PBP file : " + std::string(pbpPath)
);
}
// Read the PBP header
size_t read = fread(
&ASSET.pbpHeader,
1,
sizeof(assetpbp_t),
ASSET.pbpFile
);
if(read != sizeof(assetpbp_t)) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to read PBP header: " + std::string(pbpPath)
);
}
if(memoryCompare(
ASSET.pbpHeader.signature,
ASSET_PBP_SIGNATURE,
sizeof(ASSET_PBP_SIGNATURE)
) != 0) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Invalid PBP signature in file: " + std::string(pbpPath)
);
}
// If we seek to the PSAR offset, we can read the WAD file from there
if(fseek(ASSET.pbpFile, ASSET.pbpHeader.psarOffset, SEEK_SET) != 0) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to seek to PSAR offset in PBP file: " + std::string(pbpPath)
);
}
zip_uint64_t zipPsarOffset = (zip_uint64_t)ASSET.pbpHeader.psarOffset;
zip_int64_t zipPsarSize = (zip_int64_t)(
pbpSize - ASSET.pbpHeader.psarOffset
);
zip_source_t *psarSource = zip_source_filep_create(
ASSET.pbpFile,
zipPsarOffset,
zipPsarSize,
NULL
);
if(psarSource == NULL) {
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to create zip source in PBP file: " + std::string(pbpPath)
);
}
ASSET.zip = zip_open_from_source(
psarSource,
ZIP_RDONLY,
NULL
);
if(ASSET.zip == NULL) {
zip_source_free(psarSource);
fclose(ASSET.pbpFile);
throw std::runtime_error(
"Failed to open zip from PBP file: " + std::string(pbpPath)
);
}
errorOk();
}
#endif
// Open zip file
char_t searchPath[FILENAME_MAX];
const char_t **path = ASSET_SEARCH_PATHS;
do {
sprintf(
searchPath,
*path,
ASSET.systemPath,
ASSET_FILE
);
// Try open
ASSET.zip = zip_open(searchPath, ZIP_RDONLY, NULL);
if(ASSET.zip == NULL) continue;
break;// Found!
} while(*(++path) != NULL);
// Did we open the asset?
if(ASSET.zip == NULL) throw std::runtime_error("Failed to open asset file.");
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
return true;
}
void assetLoad(const char_t *filename, void *output) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
// Try to open the file
zip_file_t *file = zip_fopen(ASSET.zip, filename, 0);
if(file == NULL) {
throw std::runtime_error(
"Failed to open asset file: " + std::string(filename)
);
}
// Read the header.
assetheader_t header;
memoryZero(&header, sizeof(assetheader_t));
zip_int64_t bytesRead = zip_fread(file, &header, sizeof(assetheader_t));
if(bytesRead != sizeof(assetheader_t)) {
zip_fclose(file);
throw std::runtime_error(
"Failed to read asset header for: " + std::string(filename)
);
}
// Find the asset type based on the header
const assettypedef_t *def = NULL;
for(uint_fast8_t i = 0; i < ASSET_TYPE_COUNT; i++) {
const assettypedef_t *cmp = &ASSET_TYPE_DEFINITIONS[i];
if(cmp->header == NULL) continue;
// strcmp didn't work because it's a fixed char_t[3] I think, or maybe
// because of the packed struct?
bool_t match = true;
for(size_t h = 0; h < ASSET_HEADER_SIZE; h++) {
if(header.header[h] == cmp->header[h]) continue;
match = false;
break;
}
if(!match) continue;
def = cmp;
break;
}
if(def == NULL) {
zip_fclose(file);
throw std::runtime_error(
"Unknown asset type for file: " + std::string(filename)
);
}
// We found the asset type, now load the asset data
switch(def->loadStrategy) {
case ASSET_LOAD_STRAT_ENTIRE: {
assertNotNull(def->entire, "Asset load function cannot be NULL.");
void *data = memoryAllocate(def->dataSize);
bytesRead = zip_fread(file, data, def->dataSize);
if(bytesRead == 0 || bytesRead > def->dataSize) {
memoryFree(data);
zip_fclose(file);
throw std::runtime_error(
"Failed to read entire asset data for file: " +
std::string(filename)
);
}
// Close the file now we have the data
zip_fclose(file);
// Pass to the asset type loader
try {
def->entire(data, output);
} catch(...) {
memoryFree(data);
throw;
}
memoryFree(data);
break;
}
case ASSET_LOAD_STRAT_CUSTOM: {
assertNotNull(def->custom, "Asset load function cannot be NULL.");
assetcustom_t customData = {
.zipFile = file,
.output = output
};
def->custom(customData);
break;
}
default:
assertUnreachable("Unknown asset load strategy.");
}
}
void assetDispose(void) {
if(ASSET.zip != NULL) {
zip_close(ASSET.zip);
ASSET.zip = NULL;
}
#if PSP
if(ASSET.pbpFile != NULL) {
fclose(ASSET.pbpFile);
ASSET.pbpFile = NULL;
}
#endif
}