summaryrefslogtreecommitdiffstatshomepage
path: root/src/frontend/mame/language.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/mame/language.cpp')
-rw-r--r--src/frontend/mame/language.cpp176
1 files changed, 136 insertions, 40 deletions
diff --git a/src/frontend/mame/language.cpp b/src/frontend/mame/language.cpp
index 016a32f1811..a95a2b78451 100644
--- a/src/frontend/mame/language.cpp
+++ b/src/frontend/mame/language.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Miodrag Milanovic
+// copyright-holders:Vas Crabb
/***************************************************************************
language.cpp
@@ -11,69 +11,165 @@
#include "emu.h"
#include "emuopts.h"
-static std::unordered_map<std::string, std::string> g_translation;
+#include <cstring>
+#include <memory>
+#include <new>
+#include <unordered_map>
-const char *lang_translate(const char *word)
+
+namespace {
+
+constexpr uint32_t MO_MAGIC = 0x950412de;
+constexpr uint32_t MO_MAGIC_REVERSED = 0xde120495;
+
+struct cstr_hash
{
- if (g_translation.find(word) == g_translation.end())
+ size_t operator()(char const *s) const noexcept
{
- return word;
+ // Bernstein string hash
+ size_t result(5381);
+ while (*s)
+ result = ((result << 5) + result) + u8(*s++);
+ return result;
}
- return g_translation[word].c_str();
-}
+};
+
+struct cstr_compare
+{
+ size_t operator()(char const *x, char const *y) const noexcept
+ {
+ return !std::strcmp(x, y);
+ }
+};
+
+std::unique_ptr<u32 []> f_translation_data;
+std::unordered_map<char const *, char const *, cstr_hash, cstr_compare> f_translation_map;
-const uint32_t MO_MAGIC = 0x950412de;
-const uint32_t MO_MAGIC_REVERSED = 0xde120495;
+} // anonymous namespace
-inline uint32_t endianchange(uint32_t value) {
- uint32_t b0 = (value >> 0) & 0xff;
- uint32_t b1 = (value >> 8) & 0xff;
- uint32_t b2 = (value >> 16) & 0xff;
- uint32_t b3 = (value >> 24) & 0xff;
- return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
+char const *lang_translate(char const *word)
+{
+ auto const found = f_translation_map.find(word);
+ return (f_translation_map.end() != found) ? found->second : word;
}
+
void load_translation(emu_options &m_options)
{
- g_translation.clear();
+ f_translation_data.reset();
+ f_translation_map.clear();
+
emu_file file(m_options.language_path(), OPEN_FLAG_READ);
- auto name = std::string(m_options.language());
+ std::string name = m_options.language();
strreplace(name, " ", "_");
strreplace(name, "(", "");
strreplace(name, ")", "");
- if (file.open(name.c_str(), PATH_SEPARATOR "strings.mo") == osd_file::error::NONE)
+ if (file.open(name.c_str(), PATH_SEPARATOR "strings.mo") != osd_file::error::NONE)
+ {
+ osd_printf_error("Error opening translation file %s\n", name);
+ return;
+ }
+
+ u64 const size = file.size();
+ if (20 > size)
{
- uint64_t size = file.size();
- uint32_t *buffer = global_alloc_array(uint32_t, size / 4 + 1);
- file.read(buffer, size);
file.close();
+ osd_printf_error("Error reading translation file %s: %u-byte file is too small to contain translation data\n", name, size);
+ return;
+ }
+
+ f_translation_data.reset(new (std::nothrow) uint32_t [(size + 3) / 4]);
+ if (!f_translation_data)
+ {
+ file.close();
+ osd_printf_error("Failed to allocate %u bytes to load translation data file %s\n", size, name);
+ return;
+ }
+
+ auto const read = file.read(f_translation_data.get(), size);
+ file.close();
+ if (read != size)
+ {
+ osd_printf_error("Error reading translation file %s: requested %u bytes but got %u bytes\n", name, size, read);
+ f_translation_data.reset();
+ return;
+ }
- if (buffer[0] != MO_MAGIC && buffer[0] != MO_MAGIC_REVERSED)
+ if ((f_translation_data[0] != MO_MAGIC) && (f_translation_data[0] != MO_MAGIC_REVERSED))
+ {
+ osd_printf_error("Error reading translation file %s: unrecognized magic number 0x%08X\n", name, f_translation_data[0]);
+ f_translation_data.reset();
+ return;
+ }
+
+ auto fetch_word =
+ [reversed = f_translation_data[0] == MO_MAGIC_REVERSED, words = f_translation_data.get()] (size_t offset)
+ {
+ return reversed ? swapendian_int32(words[offset]) : words[offset];
+ };
+
+ // FIXME: check major/minor version number
+
+ if ((fetch_word(3) % 4) || (fetch_word(4) % 4))
+ {
+ osd_printf_error("Error reading translation file %s: table offsets %u and %u are not word-aligned\n", name, fetch_word(3), fetch_word(4));
+ f_translation_data.reset();
+ return;
+ }
+
+ u32 const number_of_strings = fetch_word(2);
+ u32 const original_table_offset = fetch_word(3) >> 2;
+ u32 const translation_table_offset = fetch_word(4) >> 2;
+ if ((4 * (original_table_offset + (u64(number_of_strings) * 2))) > size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-entry original string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(3), size);
+ f_translation_data.reset();
+ return;
+ }
+ if ((4 * (translation_table_offset + (u64(number_of_strings) * 2))) > size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-entry translated string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(4), size);
+ f_translation_data.reset();
+ return;
+ }
+ osd_printf_verbose("Reading translation file %s: %u strings, original table at word offset %u, translated table at word offset %u\n", name, number_of_strings, original_table_offset, translation_table_offset);
+
+ char const *const data = reinterpret_cast<char const *>(f_translation_data.get());
+ for (u32 i = 1; number_of_strings > i; ++i)
+ {
+ u32 const original_length = fetch_word(original_table_offset + (2 * i));
+ u32 const original_offset = fetch_word(original_table_offset + (2 * i) + 1);
+ if ((original_length + original_offset) >= size)
{
- global_free_array(buffer);
- return;
+ osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u extends past end of %u-byte file\n", name, original_length, i, original_offset, size);
+ continue;
}
- if (buffer[0] == MO_MAGIC_REVERSED)
+ if (data[original_length + original_offset])
{
- for (auto i = 0; i < (size / 4) + 1; ++i)
- {
- buffer[i] = endianchange(buffer[i]);
- }
+ osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u is not correctly NUL-terminated\n", name, original_length, i, original_offset);
+ continue;
}
- uint32_t number_of_strings = buffer[2];
- uint32_t original_table_offset = buffer[3] >> 2;
- uint32_t translation_table_offset = buffer[4] >> 2;
-
- const char *data = reinterpret_cast<const char*>(buffer);
-
- for (auto i = 1; i < number_of_strings; ++i)
+ u32 const translation_length = fetch_word(translation_table_offset + (2 * i));
+ u32 const translation_offset = fetch_word(translation_table_offset + (2 * i) + 1);
+ if ((translation_length + translation_offset) >= size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u extends past end of %u-byte file\n", name, translation_length, i, translation_offset, size);
+ continue;
+ }
+ if (data[translation_length + translation_offset])
{
- std::string original = (const char *)data + buffer[original_table_offset + 2 * i + 1];
- std::string translation = (const char *)data + buffer[translation_table_offset + 2 * i + 1];
- g_translation.emplace(std::move(original), std::move(translation));
+ osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u is not correctly NUL-terminated\n", name, translation_length, i, translation_offset);
+ continue;
}
- global_free_array(buffer);
+
+ char const *const original = &data[original_offset];
+ char const *const translation = &data[translation_offset];
+ auto const ins = f_translation_map.emplace(original, translation);
+ if (!ins.second)
+ osd_printf_warning("Loading translation file %s: translation %u '%s'='%s' conflicts with previous translation '%s'='%s'\n", name, i, original, translation, ins.first->first, ins.first->second);
}
+
+ osd_printf_verbose("Loaded %u translations from file %s\n", f_translation_map.size(), name);
}