/** * Copyright (c) 2024 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "asset.h" #include "assert/assert.h" #include "util/math.h" #include "util/memory.h" asset_t ASSET; void assetInit() { const char_t *assetFilename = "dusk.tar"; char_t assetPath[FILENAME_MAX]; const char_t* scanLocations[] = { EXECUTABLE_DIRECTORY }; memoryZero(&ASSET, sizeof(asset_t)); // Try and find the asset file. for(int32_t i = 0; i < sizeof(scanLocations) / sizeof(char_t*); i++) { sprintf(assetPath, "%s/%s", scanLocations[i], assetFilename); FILE *file = fopen(assetPath, "rb"); if(file == NULL) continue; // File found. ASSET.file = file; } // Ensure we found it. assertNotNull(ASSET.file, "Failed to find asset file!"); } void assetOpen(const char_t *path) { assertNotNull(path, "Path is not valid!"); assertStrLen(path, FILENAME_MAX, "Path is too long!"); assertStrLenMin(path, 1, "Path is empty!"); // Make sure things are clean assertNull(ASSET.archive, "Archive is not NULL!"); assertNull(ASSET.entry, "Entry is not NULL!"); assertNotNull(ASSET.file, "File is NULL!"); // Store path strcpy(ASSET.path, path); // Prepare data ASSET.archive = archive_read_new(); assertNotNull(ASSET.archive, "Failed to init archive reader"); // Set up the reader // archive_read_support_filter_bzip2(ASSET_ARCHIVE); archive_read_support_format_tar(ASSET.archive); // Set the archive reader callbacks archive_read_set_open_callback(ASSET.archive, &assetArchiveOpen); archive_read_set_read_callback(ASSET.archive, &assetArchiveRead); archive_read_set_seek_callback(ASSET.archive, &assetArchiveSeek); archive_read_set_close_callback(ASSET.archive, &assetArchiveClose); archive_read_set_callback_data(ASSET.archive, ASSET.buffer);// TODO: Not needed? // Open the archive int32_t ret = archive_read_open1(ASSET.archive); assertTrue(ret == ARCHIVE_OK, "Failed to open archive!"); // Iterate over each file. while(archive_read_next_header(ASSET.archive, &ASSET.entry) == ARCHIVE_OK) { // What file is at this position? const char_t *headerFile = (char_t*)archive_entry_pathname(ASSET.entry); // Compare if this is the file we are looking for, if it is just exit the // function if(strcmp(headerFile, ASSET.path) == 0) return; // It is not, skip it. int32_t ret = archive_read_data_skip(ASSET.archive); assertTrue(ret == ARCHIVE_OK, "Failed to skip data!"); } // If we get here we did not find the find in the archive. assertUnreachable("Failed to find file!"); } size_t assetGetSize() { assertNotNull(ASSET.archive, "Archive is NULL!"); assertNotNull(ASSET.entry, "Entry is NULL!"); assertTrue(archive_entry_size_is_set(ASSET.entry), "Entry size is not set!"); return archive_entry_size(ASSET.entry); } size_t assetRead( uint8_t *buffer, const size_t bufferSize ) { assertNotNull(ASSET.archive, "Archive is NULL!"); assertNotNull(ASSET.entry, "Entry is NULL!"); assertNotNull(buffer, "Buffer is NULL!"); assertTrue(bufferSize > 0, "Buffer size must be greater than 0!"); ssize_t read = archive_read_data(ASSET.archive, buffer, bufferSize); if(read == ARCHIVE_FATAL) { assertUnreachable(archive_error_string(ASSET.archive)); return -1; } assertTrue(read != ARCHIVE_RETRY, "Failed to read data (RETRY)!"); assertTrue(read != ARCHIVE_WARN, "Failed to read data (WARN)!"); return read; } size_t assetReadUntil( uint8_t *buffer, const char_t c, const size_t maxLength ) { if(buffer == NULL) { assertTrue( maxLength == -1, "If no buffer is provided, maxLength must be -1." ); uint8_t tBuffer[1]; size_t read = 0; while(assetRead(tBuffer, 1) == 1 && (char_t)tBuffer[0] != c) read++; return read; } size_t read = 0; while(read < maxLength) { // TODO: Read more than 1 char at a time. read += assetRead(buffer + read, 1); if((char_t)buffer[read-1] == c) return read - 1; } return -1; } void assetSkip(const size_t length) { assertNotNull(ASSET.archive, "Archive is NULL!"); assertNotNull(ASSET.entry, "Entry is NULL!"); // Asset archive does not support skipping, so we have to read and discard. uint8_t buffer[ASSET_BUFFER_SIZE]; size_t remaining = length; do { size_t toRead = mathMin(remaining, ASSET_BUFFER_SIZE); size_t read = assetRead(buffer, toRead); assertTrue(read == toRead, "Failed to skip data! (overskip?)"); remaining -= read; } while(remaining > 0); } void assetClose() { if(ASSET.archive == NULL) return; assertNotNull(ASSET.archive, "Archive is NULL!"); assertNotNull(ASSET.entry, "Entry is NULL!"); int32_t ret = archive_read_free(ASSET.archive); assertTrue(ret == ARCHIVE_OK, "Failed to close archive!"); assertNull(ASSET.archive, "Archive is not NULL? Cleanup incorrect."); } void assetDispose() { assertNull(ASSET.archive, "Asset disposing but asset is currently open?"); assertNull(ASSET.entry, "Asset disposing but entry is currently open?"); assertNotNull(ASSET.file, "Asset disposing but file is NULL?"); fclose(ASSET.file); memoryZero(&ASSET, sizeof(asset_t)); } // Libarchive callbacks ssize_t assetArchiveRead( struct archive *archive, void *data, const void **buffer ) { assertNotNull(archive, "Archive is NULL!"); assertNotNull(data, "Data is NULL!"); assertNotNull(buffer, "Buffer is NULL!"); *buffer = data; size_t read = fread(data, 1, ASSET_BUFFER_SIZE, ASSET.file); if(ferror(ASSET.file)) return ARCHIVE_FATAL; return read; } int64_t assetArchiveSeek( struct archive *archive, void *data, int64_t offset, int32_t whence ) { assertNotNull(archive, "Archive is NULL!"); assertNotNull(data, "Data is NULL!"); assertTrue(offset > 0, "Offset must be greater than 0!"); assertNotNull(ASSET.file, "File is NULL!"); int32_t ret = fseek(ASSET.file, offset, whence); assertTrue(ret == 0, "Failed to seek!"); return ftell(ASSET.file); } int32_t assetArchiveOpen(struct archive *a, void *data) { int32_t ret = fseek(ASSET.file, 0, SEEK_SET); assertTrue(ret == 0, "Failed to seek to start of file!"); return ARCHIVE_OK; } int32_t assetArchiveClose(struct archive *a, void *data) { assertNotNull(ASSET.file, "File is NULL!"); ASSET.archive = NULL; ASSET.entry = NULL; return ARCHIVE_OK; }