diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po index 2b09458..1399498 100644 --- a/assets/locale/en_US.po +++ b/assets/locale/en_US.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"Language: us\n" +"Language: en_US\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/src/asset/asset.c b/src/asset/asset.c index c5804b9..69a7d75 100644 --- a/src/asset/asset.c +++ b/src/asset/asset.c @@ -100,7 +100,7 @@ errorret_t assetLoad(const char_t *filename, void *output) { .zipFile = file, .output = output }; - errorChain(def->custom(&customData)); + errorChain(def->custom(customData)); break; default: diff --git a/src/asset/assettype.h b/src/asset/assettype.h index 5b81029..a61759c 100644 --- a/src/asset/assettype.h +++ b/src/asset/assettype.h @@ -35,7 +35,7 @@ typedef struct { const assetloadstrat_t loadStrategy; union { errorret_t (*entire)(void *data, void *output); - errorret_t (*custom)(assetcustom_t *custom); + errorret_t (*custom)(assetcustom_t custom); }; } assettypedef_t; @@ -61,6 +61,6 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = { [ASSET_TYPE_LANGUAGE] = { .header = "DLF", .loadStrategy = ASSET_LOAD_STRAT_CUSTOM, - .custom = assetLanguageInit + .custom = assetLanguageHandler } }; \ No newline at end of file diff --git a/src/asset/type/assetlanguage.c b/src/asset/type/assetlanguage.c index 6e449cd..40bc51d 100644 --- a/src/asset/type/assetlanguage.c +++ b/src/asset/type/assetlanguage.c @@ -6,9 +6,108 @@ */ #include "asset/asset.h" +#include "assert/assert.h" +#include "locale/localemanager.h" -errorret_t assetLanguageInit(assetcustom_t *custom) { - zip_fclose(custom->zipFile); +errorret_t assetLanguageHandler(assetcustom_t custom) { + assertNotNull(custom.zipFile, "Custom asset zip file cannot be NULL"); + assertNotNull(custom.output, "Custom asset output cannot be NULL"); + + assetlanguage_t *lang = (assetlanguage_t *)custom.output; + errorChain(assetLanguageInit(lang, custom.zipFile)); errorOk(); +} + +errorret_t assetLanguageInit( + assetlanguage_t *lang, + zip_file_t *zipFile +) { + assertNotNull(lang, "Language asset cannot be NULL"); + assertNotNull(zipFile, "Zip file cannot be NULL"); + assertNull(lang->zip, "Language asset zip file must be NULL."); + assertTrue(zip_file_is_seekable(zipFile), "Language file must be seekable."); + + // We now own the zip file handle. + lang->zip = zipFile; + printf("Initialized language asset.\n"); + + // Read in the header. + zip_int64_t bytesRead = zip_fread( + lang->zip, + &lang->header, + sizeof(assetlanguageheader_t) + ); + if(bytesRead != sizeof(assetlanguageheader_t)) { + zip_fclose(lang->zip); + errorThrow("Failed to read language asset header."); + } + + lang->chunksOffset = zip_ftell(lang->zip); + if(lang->chunksOffset <= 0) { + zip_fclose(lang->zip); + errorThrow("Failed to get language asset chunks offset."); + } + + errorOk(); +} + +errorret_t assetLanguageRead( + assetlanguage_t *lang, + const uint32_t key, + char_t *buffer, + const uint32_t bufferSize, + uint32_t *outLength +) { + assertNotNull(lang, "Language asset cannot be NULL"); + assertNotNull(lang->zip, "Language asset zip file cannot be NULL"); + assertTrue(key < LANG_KEY_COUNT, "Language key out of bounds."); + + // Find the string entry + assetlanguagestring_t *str = &lang->header.strings[LANG_MAP_TEST]; + + // If buffer is NULL, return the string length + if(buffer == NULL) { + assertNotNull(outLength, "Output length pointer cannot be NULL."); + *outLength = str->length; + errorOk(); + } + + // Ensure buffer is large enough + assertTrue( + bufferSize >= str->length + 1, + "Provided buffer is too small for language string." + ); + + // Determine the file position + zip_int64_t seekTo = lang->chunksOffset + ( + (str->chunk * ASSET_LANG_CHUNK_CHAR_COUNT) + str->offset + ); + + // Seek + zip_int64_t result = zip_fseek(lang->zip, seekTo, SEEK_SET); + if(result != 0) { + errorThrow("Failed to seek to language string in asset."); + } + + // Read + zip_int64_t readTest = zip_fread(lang->zip, buffer, str->length); + if(readTest != str->length) { + errorThrow("Failed to read test string from language asset."); + } + buffer[str->length] = '\0'; + + // Set str length if requested + if(outLength != NULL) *outLength = str->length; + errorOk(); +} + +void assetLanguageDispose(assetlanguage_t *lang) { + assertNotNull(lang, "Language asset cannot be NULL"); + + if(lang->zip) { + zip_fclose(lang->zip); + } + + printf("Disposed language asset.\n"); } \ No newline at end of file diff --git a/src/asset/type/assetlanguage.h b/src/asset/type/assetlanguage.h index f0c62d2..213148a 100644 --- a/src/asset/type/assetlanguage.h +++ b/src/asset/type/assetlanguage.h @@ -6,10 +6,12 @@ */ #pragma once -#include "error/error.h" #include "locale/language/keys.h" +#include "error/error.h" +#include #define ASSET_LANG_CHUNK_CHAR_COUNT 6 * 1024 // 6 KB per chunk +#define ASSET_LANG_CHUNK_CACHE 4 // Number of chunks to cache in memory #pragma pack(push, 1) typedef char assetlanguagechunk_t[ASSET_LANG_CHUNK_CHAR_COUNT]; @@ -29,12 +31,57 @@ typedef struct { } assetlanguageheader_t; #pragma pack(pop) +typedef struct { + zip_file_t *zip; + assetlanguageheader_t header; + zip_int64_t chunksOffset; + + // Chunk cache + assetlanguagechunk_t chunks[ASSET_LANG_CHUNK_CACHE]; + uint32_t chunkIndices[ASSET_LANG_CHUNK_CACHE]; +} assetlanguage_t; + typedef struct assetcustom_s assetcustom_t; /** - * Receiving function from the asset manager to initialize language assets. + * Receiving function from the asset manager to handle language assets. * - * @param custom Pointer to custom asset loading data. + * @param custom Custom asset loading data. * @return Error code. */ -errorret_t assetLanguageInit(assetcustom_t *custom); \ No newline at end of file +errorret_t assetLanguageHandler(assetcustom_t custom); + +/** + * Initializes a language asset and loads the header data into memory. + * + * @param lang Language asset to initialize. + * @param zipFile Zip file handle for the language asset. + * @return Error code. + */ +errorret_t assetLanguageInit(assetlanguage_t *lang, zip_file_t *zipFile); + +/** + * Reads a string from the language asset into the provided buffer. + * + * @param lang Language asset to read from. + * @param key Language key to read. + * @param buffer Buffer to read the string into. + * @param bufferSize Size of the provided buffer. + * @param outLength Pointer to store the length of the string read. + * @return Error code. + */ +errorret_t assetLanguageRead( + assetlanguage_t *lang, + const uint32_t key, + char_t *buffer, + const uint32_t bufferSize, + uint32_t *outLength +); + +/** + * Disposes of language asset resources. + * + * @param custom Custom asset loading data. + * @return Error code. + */ +void assetLanguageDispose(assetlanguage_t *lang); \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c index c90ddf6..6df9bf2 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -54,6 +54,7 @@ void engineExit(void) { } errorret_t engineDispose(void) { + localeManagerDispose(); sceneManagerDispose(); rpgDispose(); uiDispose(); diff --git a/src/locale/localemanager.c b/src/locale/localemanager.c index 9a9fa85..295aba0 100644 --- a/src/locale/localemanager.c +++ b/src/locale/localemanager.c @@ -8,12 +8,29 @@ #include "localemanager.h" #include "util/memory.h" #include "asset/asset.h" +#include "assert/assert.h" localemanager_t LOCALE; errorret_t localeManagerInit() { memoryZero(&LOCALE, sizeof(localemanager_t)); - - errorChain(assetLoad("language/us.dlf", &LOCALE)); + errorChain(localeManagerSetLocale(DUSK_LOCALE_EN_US)); errorOk(); +} + +errorret_t localeManagerSetLocale(const dusklocale_t locale) { + assertTrue(locale < DUSK_LOCALE_COUNT, "Invalid locale."); + LOCALE.locale = locale; + char_t languageFile[FILENAME_MAX]; + snprintf( + languageFile, FILENAME_MAX, "language/%s.dlf", LOCALE_INFOS[locale].file + ); + assetLanguageDispose(&LOCALE.language); + memoryZero(&LOCALE.language, sizeof(assetlanguage_t)); + errorChain(assetLoad(languageFile, &LOCALE.language)); + errorOk(); +} + +void localeManagerDispose() { + assetLanguageDispose(&LOCALE.language); } \ No newline at end of file diff --git a/src/locale/localemanager.h b/src/locale/localemanager.h index 4969ec1..bc5b303 100644 --- a/src/locale/localemanager.h +++ b/src/locale/localemanager.h @@ -6,12 +6,30 @@ */ #pragma once -#include "error/error.h" +#include "asset/asset.h" + +typedef enum { + DUSK_LOCALE_EN_US, + + DUSK_LOCALE_COUNT +} dusklocale_t; typedef struct { - void *nothing; + dusklocale_t locale; + assetlanguage_t language; } localemanager_t; + +typedef struct { + const char_t *file; +} localeinfo_t; + +static const localeinfo_t LOCALE_INFOS[DUSK_LOCALE_COUNT] = { + [DUSK_LOCALE_EN_US] = { + .file = "en_US" + } +}; + extern localemanager_t LOCALE; /** @@ -19,4 +37,17 @@ extern localemanager_t LOCALE; * * @return An error code if a failure occurs. */ -errorret_t localeManagerInit(); \ No newline at end of file +errorret_t localeManagerInit(); + +/** + * Set the current locale. + * + * @param locale The locale to set. + * @return An error code if a failure occurs. + */ +errorret_t localeManagerSetLocale(const dusklocale_t locale); + +/** + * Dispose of the locale system. + */ +void localeManagerDispose(); \ No newline at end of file diff --git a/tools/assetstool/processlanguage.py b/tools/assetstool/processlanguage.py index e322de8..98c6f92 100644 --- a/tools/assetstool/processlanguage.py +++ b/tools/assetstool/processlanguage.py @@ -60,6 +60,8 @@ def processLanguageList(): # Now we can generate the language string chunks. nextChunkIndex = max(desiredChunkGroups.values()) + 1 + files = [] + for lang in LANGUAGE_DATA: langData = LANGUAGE_DATA[lang] @@ -102,7 +104,6 @@ def processLanguageList(): # We have now chunked all the keys for this language! langBuffer = b"" - files = [] # Write header info langBuffer += b'DLF' # Dusk Language File @@ -127,6 +128,12 @@ def processLanguageList(): strData = langData[key].encode('utf-8') langBuffer += strData + # Now pad the chunk to full size + curLen = chunkInfo['len'] + if curLen < LANGUAGE_CHUNK_CHAR_COUNT: + padSize = LANGUAGE_CHUNK_CHAR_COUNT - curLen + langBuffer += b'\0' * padSize + # Write out the language data file outputFile = os.path.join(args.output_assets, "language", f"{lang}.dlf") files.append(outputFile)