From 8bf129f5e1939733495cf7349092f5fdf2b767b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20T=C3=B6rnblom?= Date: Thu, 22 Apr 2021 21:34:19 +0200 Subject: [PATCH] add initial language support using a simple MO parser --- CMakeLists.txt | 1 + Source/DiabloUI/mainmenu.cpp | 1 + Source/diablo.cpp | 8 +++ Source/language.cpp | 125 +++++++++++++++++++++++++++++++++++ Source/language.h | 7 ++ Source/options.h | 6 ++ Source/utils/paths.cpp | 20 ++++++ Source/utils/paths.h | 4 +- 8 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 Source/language.cpp create mode 100644 Source/language.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed8919ba40e..c4a65a0d13d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,6 +241,7 @@ set(devilutionx_SRCS Source/inv.cpp Source/itemdat.cpp Source/items.cpp + Source/language.cpp Source/lighting.cpp Source/loadsave.cpp Source/main.cpp diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index 8b99b86d3b1..6000134a009 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -1,5 +1,6 @@ #include "control.h" +#include "language.h" #include "DiabloUI/diabloui.h" #include "DiabloUI/selok.h" diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 02c0bde9cc0..600e9bd9c64 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -26,6 +26,7 @@ #include "gmenu.h" #include "help.h" #include "init.h" +#include "language.h" #include "lighting.h" #include "loadsave.h" #include "mainmenu.h" @@ -188,6 +189,8 @@ static void diablo_parse_flags(int argc, char **argv) SetPrefPath(argv[++i]); } else if (strcasecmp("--config-dir", argv[i]) == 0) { SetConfigPath(argv[++i]); + } else if (strcasecmp("--lang-dir", argv[i]) == 0) { + SetLangPath(argv[++i]); } else if (strcasecmp("--ttf-dir", argv[i]) == 0) { SetTtfPath(argv[++i]); } else if (strcasecmp("--ttf-name", argv[i]) == 0) { @@ -517,6 +520,8 @@ static void SaveOptions() setIniInt("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch); #endif + setIniValue("Language", "Code", sgOptions.Language.szCode); + SaveIni(); } @@ -591,6 +596,8 @@ static void LoadOptions() sgOptions.Controller.bRearTouch = getIniBool("Controller", "Enable Rear Touchpad", true); #endif + getIniValue("Language", "Code", sgOptions.Language.szCode, sizeof(sgOptions.Language.szCode), "en"); + sbWasOptionsLoaded = true; } @@ -625,6 +632,7 @@ static void diablo_init() gbIsHellfireSaveGame = gbIsHellfire; + LanguageInitialize(); UiInitialize(); UiSetSpawned(gbIsSpawn); was_ui_init = true; diff --git a/Source/language.cpp b/Source/language.cpp new file mode 100644 index 00000000000..4690346fabd --- /dev/null +++ b/Source/language.cpp @@ -0,0 +1,125 @@ +#include "options.h" +#include "utils/paths.h" +#include + + +using namespace devilution; +#define MO_MAGIC 0x950412de + + +namespace { + + struct cstring_cmp { + bool operator()(const char *s1, const char *s2) const { + return strcmp(s1, s2) < 0; + } + }; + + std::map map; + + + struct mo_head { + uint32_t magic; + struct { + uint16_t major; + uint16_t minor; + } revision; + + uint32_t nb_mappings; + uint32_t src_offset; + uint32_t dst_offset; + }; + + + struct mo_entry { + uint32_t length; + uint32_t offset; + }; + + + void* read_entry(FILE *fp, mo_entry* e) { + void *data; + + if(fseek(fp, e->offset, SEEK_SET)) { + return NULL; + } + + if(!(data = calloc(e->length, sizeof(char)))) { + return NULL; + } + + if(fread(data, sizeof(char), e->length, fp) != e->length) { + free(data); + return NULL; + } + + return data; + } +} + + +const char* LanguageTranslate(const char* key) { + auto it = map.find(key); + if(it == map.end()) { + return key; + } + + return it->second; +} + + +void LanguageInitialize() { + std::string path = GetLangPath() + "./" + sgOptions.Language.szCode + ".mo"; + mo_head head; + FILE *fp; + + if(!(fp = fopen(path.c_str(), "rb"))) { + perror(path.c_str()); + return; + } + // Read header and do sanity checks + if(fread(&head, sizeof(mo_head), 1, fp) != 1) { + return; + } + + if(head.magic != MO_MAGIC) { + return; // not a MO file + } + + if(head.revision.major > 1 || head.revision.minor > 1) { + return; // unsupported revision + } + + // Read entries of source strings + mo_entry src[head.nb_mappings]; + if(fseek(fp, head.src_offset, SEEK_SET)) { + return; + } + if(fread(src, sizeof(mo_entry), head.nb_mappings, fp) != head.nb_mappings) { + return; + } + + // Read entries of target strings + mo_entry dst[head.nb_mappings]; + if(fseek(fp, head.dst_offset, SEEK_SET)) { + return; + } + + if(fread(dst, sizeof(mo_entry), head.nb_mappings, fp) != head.nb_mappings) { + return; + } + + // Read strings described by entries + for(uint32_t i=0; i(key)] = static_cast(val); + } else { + free(key); + } + } + } +} + + diff --git a/Source/language.h b/Source/language.h new file mode 100644 index 00000000000..6c6c65e68e1 --- /dev/null +++ b/Source/language.h @@ -0,0 +1,7 @@ +#pragma once + +#define _(x) LanguageTranslate(x) + +void LanguageInitialize(); +const char* LanguageTranslate(const char* key); + diff --git a/Source/options.h b/Source/options.h index 97faf8a9bd8..7030f8772e4 100644 --- a/Source/options.h +++ b/Source/options.h @@ -124,6 +124,11 @@ struct ChatOptions { char szHotKeyMsgs[4][MAX_SEND_STR_LEN]; }; +struct LanguageOptions { + /** @brief Language code (ISO 639-1) for text. */ + char szCode[2]; +}; + struct Options { DiabloOptions Diablo; HellfireOptions Hellfire; @@ -133,6 +138,7 @@ struct Options { ControllerOptions Controller; NetworkOptions Network; ChatOptions Chat; + LanguageOptions Language; }; extern Options sgOptions; diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index ab178540d01..2505f215ee2 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -14,6 +14,10 @@ #define TTF_FONT_NAME "CharisSILB.ttf" #endif +#ifndef MO_LANG_DIR +#define MO_LANG_DIR "" +#endif + namespace devilution { namespace { @@ -21,6 +25,7 @@ namespace { std::string *basePath = NULL; std::string *prefPath = NULL; std::string *configPath = NULL; +std::string *langPath = NULL; std::string *ttfPath = NULL; std::string *ttfName = NULL; @@ -82,6 +87,13 @@ const std::string &GetTtfPath() return *ttfPath; } +const std::string &GetLangPath() +{ + if (langPath == NULL) + langPath = new std::string(MO_LANG_DIR); + return *langPath; +} + const std::string &GetTtfName() { if (ttfName == NULL) @@ -113,6 +125,14 @@ void SetConfigPath(const char *path) AddTrailingSlash(configPath); } +void SetLangPath(const char *path) +{ + if (langPath == NULL) + langPath = new std::string; + *langPath = path; + AddTrailingSlash(langPath); +} + void SetTtfPath(const char *path) { if (ttfPath == NULL) diff --git a/Source/utils/paths.h b/Source/utils/paths.h index b09658327e2..7c286bbc47c 100644 --- a/Source/utils/paths.h +++ b/Source/utils/paths.h @@ -7,12 +7,14 @@ namespace devilution { const std::string &GetBasePath(); const std::string &GetPrefPath(); const std::string &GetConfigPath(); +const std::string &GetLangPath(); const std::string &GetTtfPath(); const std::string &GetTtfName(); - + void SetBasePath(const char *path); void SetPrefPath(const char *path); void SetConfigPath(const char *path); +void SetLangPath(const char *path); void SetTtfPath(const char *path); void SetTtfName(const char *path);