199 lines
4.3 KiB
C++
199 lines
4.3 KiB
C++
#include <std_include.hpp>
|
|
#include "loader/component_loader.hpp"
|
|
|
|
#include "localized_strings.hpp"
|
|
#include "game_console.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "console.hpp"
|
|
|
|
#include "game/game.hpp"
|
|
|
|
#include <utils/hook.hpp>
|
|
#include <utils/string.hpp>
|
|
#include <utils/concurrency.hpp>
|
|
#include <utils/io.hpp>
|
|
|
|
namespace localized_strings
|
|
{
|
|
namespace
|
|
{
|
|
utils::hook::detour seh_string_ed_get_string_hook;
|
|
|
|
struct localize_entry
|
|
{
|
|
std::string value{};
|
|
bool volatile_{};
|
|
};
|
|
|
|
using localized_map = std::unordered_map<std::string, localize_entry>;
|
|
utils::concurrency::container<localized_map> localized_overrides;
|
|
|
|
const char* seh_string_ed_get_string(const char* reference)
|
|
{
|
|
return localized_overrides.access<const char*>([&](const localized_map& map)
|
|
{
|
|
const auto entry = map.find(reference);
|
|
if (entry != map.end())
|
|
{
|
|
return utils::string::va("%s", entry->second.value.data());
|
|
}
|
|
|
|
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
|
});
|
|
}
|
|
|
|
game::XAssetHeader db_find_localize_entry_stub(game::XAssetType type, const char* name, int allow_create_default)
|
|
{
|
|
const auto value = localized_overrides.access<const char*>([&](const localized_map& map)
|
|
-> const char*
|
|
{
|
|
const auto entry = map.find(name);
|
|
if (entry != map.end())
|
|
{
|
|
return utils::string::va("%s", entry->second.value.data());
|
|
}
|
|
|
|
return nullptr;
|
|
});
|
|
|
|
if (value == nullptr)
|
|
{
|
|
return game::DB_FindXAssetHeader(type, name, allow_create_default);
|
|
}
|
|
|
|
static game::LocalizeEntry entry{};
|
|
entry.value = value;
|
|
entry.name = name;
|
|
|
|
return static_cast<game::XAssetHeader>(&entry);
|
|
}
|
|
|
|
bool parse_localized_strings_file(const std::string& data)
|
|
{
|
|
rapidjson::Document j;
|
|
j.Parse(data.data());
|
|
|
|
if (!j.IsObject())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
localized_overrides.access([&](localized_map& map)
|
|
{
|
|
const auto obj = j.GetObj();
|
|
for (const auto& [key, value] : obj)
|
|
{
|
|
if (!key.IsString() || !value.IsString())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const auto name = key.GetString();
|
|
const auto str = value.GetString();
|
|
|
|
const auto entry = map.find(name);
|
|
if (entry == map.end() || entry->second.volatile_)
|
|
{
|
|
map[name] = {str, true};
|
|
}
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool try_load_file(const std::string& path, const std::string& language)
|
|
{
|
|
const auto file = utils::string::va("%s/localizedstrings/%s.json", path.data(), language.data());
|
|
if (!utils::io::file_exists(file))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
console::info("[Localized strings] Parsing %s\n", file);
|
|
const auto data = utils::io::read_file(file);
|
|
if (!parse_localized_strings_file(data))
|
|
{
|
|
console::error("[Localized strings] Invalid language json file\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void load_localized_strings()
|
|
{
|
|
bool found = false;
|
|
|
|
const auto search_paths = filesystem::get_search_paths_rev();
|
|
const auto language = game::SEH_GetCurrentLanguageName();
|
|
|
|
for (const auto& path : search_paths)
|
|
{
|
|
bool found_in_current_path = false;
|
|
|
|
if (try_load_file(path, "english"))
|
|
{
|
|
found_in_current_path = true;
|
|
found = true;
|
|
}
|
|
|
|
if (language != "english"s && !try_load_file(path, language) && found_in_current_path)
|
|
{
|
|
console::warn("[Localized strings] No valid language file found for '%s' in '%s/localizedstrings/', falling back to 'english'\n",
|
|
language, path.data());
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
console::warn("[Localized strings] No valid language file found!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void override(const std::string& key, const std::string& value, bool volatile_)
|
|
{
|
|
localized_overrides.access([&](localized_map& map)
|
|
{
|
|
map[key] = {value, volatile_};
|
|
});
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
localized_overrides.access([&](localized_map& map)
|
|
{
|
|
for (auto i = map.begin(); i != map.end();)
|
|
{
|
|
if (i->second.volatile_)
|
|
{
|
|
i = map.erase(i);
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
});
|
|
|
|
load_localized_strings();
|
|
}
|
|
|
|
class component final : public component_interface
|
|
{
|
|
public:
|
|
void post_unpack() override
|
|
{
|
|
// Change some localized strings
|
|
seh_string_ed_get_string_hook.create(0x1405E5FD0, &seh_string_ed_get_string);
|
|
utils::hook::call(0x1405E5AB9, db_find_localize_entry_stub);
|
|
}
|
|
};
|
|
}
|
|
|
|
REGISTER_COMPONENT(localized_strings::component)
|