536 lines
10 KiB
C++
536 lines
10 KiB
C++
#pragma once
|
|
|
|
#include "app_language.h"
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
#define PATH_SEPERATOR "\\"
|
|
#define THIS_MODULE_NAME "lang.dll"
|
|
#else
|
|
#define PATH_SEPERATOR "/"
|
|
#define THIS_MODULE_NAME "lang.so"
|
|
#endif
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <locale.h>
|
|
|
|
// *.pak ...
|
|
//typedef struct _lang_pak
|
|
//{
|
|
// uint32_t len;
|
|
// uint32_t crc; // crc check ...
|
|
// uint32_t ver;
|
|
// uint32_t* cps[]; // supported code-pages table offset, until '-1'
|
|
// struct
|
|
// {
|
|
// uint32_t name_id;
|
|
// char name[0];
|
|
// }*name[]; // my name on my code-page
|
|
// ENDING address align 16 before
|
|
//
|
|
// struct
|
|
// {
|
|
// uint32_t id;
|
|
// uint32_t offset; // offset to the first structure
|
|
// }str_map[]; // Offset of corresponding ID string, until id == -1
|
|
//};
|
|
struct _cp_locale
|
|
{
|
|
int cp;
|
|
const char* locale;
|
|
}g_known_locale[] =
|
|
{ {936, "zh_CN"}
|
|
, {950, "zh_HK"}
|
|
, {950, "zh_TW"}
|
|
, {950, "zh_SG"}
|
|
, {20127, "en_"}
|
|
, {932, "ja_JP"}
|
|
, {863, "fr_"}
|
|
, {1144, "it_CH"}
|
|
, {1141, "de_"}
|
|
, {855, "ru_RU"}
|
|
};
|
|
|
|
class lang_mgr
|
|
{
|
|
typedef struct _map_str
|
|
{
|
|
uint32_t name_id; // global unique
|
|
std::vector<int> code_pages;
|
|
std::string name;
|
|
std::string file;
|
|
|
|
bool operator==(int cp)
|
|
{
|
|
return std::find(code_pages.begin(), code_pages.end(), cp) != code_pages.end();
|
|
}
|
|
}MAPSTR;
|
|
|
|
std::vector<MAPSTR> code_pages_;
|
|
LANATTR **all_;
|
|
uint32_t os_cp_;
|
|
uint32_t cur_cp_;
|
|
std::string cur_lang_;
|
|
std::map<uint32_t, std::string> map_off_;
|
|
|
|
static lang_mgr* inst_;
|
|
|
|
void clear(void)
|
|
{
|
|
map_off_.clear();
|
|
cur_lang_.clear();
|
|
cur_cp_ = -1;
|
|
}
|
|
uint32_t calculate_crc32(uint8_t* data, uint32_t len, uint32_t prev = 0)
|
|
{
|
|
return 0;
|
|
}
|
|
uint32_t get_cur_code_page_id(void)
|
|
{
|
|
return os_cp_;
|
|
}
|
|
bool parse_pak_digest(uint8_t* data, MAPSTR& ms)
|
|
{
|
|
uint32_t ver = *(uint32_t*)data,
|
|
val = 0,
|
|
*cps = (uint32_t*)(data + sizeof(uint32_t));
|
|
|
|
while (*cps != -1)
|
|
{
|
|
ms.code_pages.push_back(*cps++);
|
|
}
|
|
if (ms.code_pages.empty())
|
|
return false;
|
|
|
|
cps++;
|
|
ms.name_id = *cps++;
|
|
val = (uint8_t*)cps - data;
|
|
ms.name = (char*)data + val;
|
|
|
|
return ms.name.length() ? true : false;
|
|
}
|
|
bool parse_pak(uint8_t* data, uint32_t* bgn, uint32_t* id, std::map<uint32_t, std::string>& off)
|
|
{
|
|
uint32_t ver = *(uint32_t*)data,
|
|
val = 0,
|
|
*cps = (uint32_t*)(data + sizeof(uint32_t));
|
|
|
|
if (id)
|
|
*id = cps[0];
|
|
while (*cps++ != -1);
|
|
cps++;
|
|
val = (uint8_t*)cps - data;
|
|
val += strlen((char*)data + val) + 1;
|
|
val += 15;
|
|
val /= 16;
|
|
val *= 16;
|
|
if (bgn)
|
|
*bgn = val;
|
|
|
|
struct
|
|
{
|
|
uint32_t id;
|
|
uint32_t off;
|
|
}*go = nullptr;
|
|
*((void**)&go) = (void*)(data + val);
|
|
while (go->id != -1)
|
|
{
|
|
off[go->id] = (char*)data + go->off + val;
|
|
go++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool load_language_pak(const char* file, bool at_init)
|
|
{
|
|
std::string cont(""), name("");
|
|
uint32_t bgn = 0, id = 0, cur_id = -1;
|
|
bool ret = false;
|
|
FILE* src = fopen(file, "rb");
|
|
std::map<uint32_t, std::string> off;
|
|
|
|
if (src)
|
|
{
|
|
int len = 0;
|
|
uint8_t* buf = nullptr;
|
|
|
|
fseek(src, 0, SEEK_END);
|
|
len = ftell(src);
|
|
fseek(src, 0, SEEK_SET);
|
|
if (len)
|
|
{
|
|
buf = new uint8_t[len + 4];
|
|
if (buf)
|
|
{
|
|
memset(buf, 0, len + 4);
|
|
len = fread(buf, 1, len, src);
|
|
if (len == *(uint32_t*)buf && calculate_crc32(buf + sizeof(uint32_t)* 2, len - sizeof(uint32_t) * 2) == ((uint32_t*)buf)[1])
|
|
{
|
|
cont = std::string((char*)buf, len);
|
|
}
|
|
delete[] buf;
|
|
}
|
|
}
|
|
fclose(src);
|
|
}
|
|
if (cont.length())
|
|
{
|
|
if (at_init)
|
|
{
|
|
MAPSTR ms;
|
|
|
|
ms.file = file;
|
|
cur_id = get_cur_code_page_id();
|
|
if (parse_pak_digest((uint8_t*)&cont[0], ms))
|
|
{
|
|
code_pages_.push_back(std::move(ms));
|
|
if (cur_id == id && map_off_.size() == 0)
|
|
{
|
|
ret = parse_pak((uint8_t*)&cont[0], &bgn, &id, off);
|
|
if (ret)
|
|
name = ms.name;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ret = parse_pak((uint8_t*)&cont[0], &bgn, &id, off);
|
|
}
|
|
|
|
if (ret && ((at_init && cur_id == id) || !at_init))
|
|
{
|
|
clear();
|
|
cur_cp_ = id;
|
|
cur_lang_ = std::move(name);
|
|
map_off_ = std::move(off);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
static std::string u2a(const wchar_t* u, UINT cp = CP_ACP)
|
|
{
|
|
std::string a("");
|
|
|
|
if (u)
|
|
{
|
|
char stack[256] = { 0 }, * ansi = NULL;
|
|
int len = 0;
|
|
|
|
len = WideCharToMultiByte(cp, 0, u, lstrlenW(u), NULL, 0, NULL, NULL);
|
|
ansi = new char[len + 2];
|
|
len = WideCharToMultiByte(cp, 0, u, lstrlenW(u), ansi, len, NULL, NULL);
|
|
ansi[len--] = 0;
|
|
a = ansi;
|
|
delete[] ansi;
|
|
}
|
|
|
|
return a;
|
|
}
|
|
static std::wstring a2u(const char* a, UINT cp = CP_ACP)
|
|
{
|
|
std::wstring u(L"");
|
|
|
|
if (a)
|
|
{
|
|
wchar_t *unic = NULL;
|
|
int len = 0;
|
|
|
|
len = MultiByteToWideChar(cp, 0, a, lstrlenA(a), NULL, 0);
|
|
unic = new wchar_t[len + 2];
|
|
len = MultiByteToWideChar(cp, 0, a, lstrlenA(a), unic, len + 1);
|
|
unic[len--] = 0;
|
|
u = unic;
|
|
delete[] unic;
|
|
}
|
|
|
|
return u;
|
|
}
|
|
#else
|
|
static std::string link_file(const char* lnk)
|
|
{
|
|
char path[512] = { 0 };
|
|
int len = readlink(lnk, path, sizeof(path) - 1);
|
|
|
|
return path;
|
|
}
|
|
static bool found_module(const char* file, void* param)
|
|
{
|
|
std::string* str = (std::string*)param;
|
|
const char* n = strrchr(file, PATH_SEPERATOR[0]);
|
|
|
|
if (n++ == nullptr)
|
|
n = file;
|
|
if (strcasecmp(str->c_str(), n) == 0)
|
|
{
|
|
*str = file;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
int enum_files(const char* dir, bool(*on_found)(const char*, void*), void* param, bool recursive = true)
|
|
{
|
|
int ret = 0;
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
std::wstring root(a2u(dir, CP_UTF8) + L"\\");
|
|
WIN32_FIND_DATAW fd = { 0 };
|
|
HANDLE h = FindFirstFileW((a2u(dir, CP_UTF8) + L"\\*").c_str(), &fd);
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return GetLastError();
|
|
do
|
|
{
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
if (recursive && wcscmp(fd.cFileName, L".") && wcscmp(fd.cFileName, L".."))
|
|
{
|
|
ret = enum_files(u2a((root + fd.cFileName).c_str(), CP_UTF8).c_str(), on_found, param, recursive);
|
|
if (ret == ERROR_CANCELLED)
|
|
break;
|
|
}
|
|
}
|
|
else if (!on_found(u2a((root + fd.cFileName).c_str(), CP_UTF8).c_str(), param))
|
|
{
|
|
ret = ERROR_CANCELLED;
|
|
break;
|
|
}
|
|
} while (FindNextFileW(h, &fd));
|
|
FindClose(h);
|
|
#else
|
|
DIR* pdir = nullptr;
|
|
struct dirent* ent = nullptr;
|
|
|
|
pdir = opendir(dir);
|
|
if (!pdir)
|
|
return errno;
|
|
|
|
while ((ent = readdir(pdir)))
|
|
{
|
|
if (ent->d_type & DT_DIR)
|
|
{
|
|
if (recursive)
|
|
{
|
|
if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
|
|
{
|
|
std::string sub(dir);
|
|
sub += "/";
|
|
sub += ent->d_name;
|
|
ret = enum_files(sub.c_str(), found_file, param, recursive);
|
|
if (ret == 0x5e17)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string file(dir);
|
|
|
|
file += "/";
|
|
file += ent->d_name;
|
|
if (!found_file(link_file(file.c_str()).c_str(), param))
|
|
{
|
|
ret = 0x5e17;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
static bool found_language_pak(const char* file, void* obj)
|
|
{
|
|
((lang_mgr*)obj)->load_language_pak(file, true);
|
|
|
|
return true;
|
|
}
|
|
std::string get_module_path(void)
|
|
{
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
wchar_t path[MAX_PATH] = { 0 };
|
|
|
|
GetModuleFileNameW(GetModuleHandleA(THIS_MODULE_NAME), path, _countof(path) - 1);
|
|
|
|
return u2a(path, CP_UTF8);
|
|
#else
|
|
char path[256] = { 0 };
|
|
std::string ret(THIS_MODULE_NAME);
|
|
|
|
sprintf(path, "/proc/%u/map_files", getpid());
|
|
enum_files(path, &lang_mgr::found_module, &ret);
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
lang_mgr() : os_cp_(lang_mgr::get_os_code_page()), cur_cp_(-1), cur_lang_(""), all_(nullptr)
|
|
{
|
|
std::string path(get_module_path());
|
|
size_t pos = path.rfind(PATH_SEPERATOR[0]);
|
|
char *str = nullptr;
|
|
LANATTR *st = nullptr;
|
|
int ind = 0;
|
|
|
|
if (pos++ == std::string::npos)
|
|
pos = 0;
|
|
path.erase(pos);
|
|
path += "lang";
|
|
enum_files(path.c_str(), &lang_mgr::found_language_pak, this, false);
|
|
|
|
size_t len = sizeof(LANATTR*) * (code_pages_.size() + 1) + sizeof(LANATTR) * code_pages_.size();
|
|
|
|
pos = len;
|
|
for (const auto& v: code_pages_)
|
|
{
|
|
len += v.name.length() + 4;
|
|
}
|
|
|
|
all_ = (LANATTR**)new char[len];
|
|
memset(all_, 0, len);
|
|
str = (char*)all_ + pos;
|
|
st = (LANATTR*)((char*)all_ + sizeof(LANATTR*) * (code_pages_.size() + 1));
|
|
for (const auto& v : code_pages_)
|
|
{
|
|
all_[ind++] = st;
|
|
st->cp = v.code_pages[0];
|
|
st->name = str;
|
|
strcpy(str, v.name.c_str());
|
|
str += v.name.length() + 2;
|
|
st++;
|
|
}
|
|
}
|
|
~lang_mgr()
|
|
{
|
|
delete[](char*)all_;
|
|
}
|
|
|
|
public:
|
|
static int get_os_code_page(void)
|
|
{
|
|
char* locale = setlocale(LC_ALL, "");
|
|
int cp = 936;
|
|
|
|
if (locale)
|
|
{
|
|
char* end = nullptr;
|
|
bool found = false;
|
|
|
|
if (strstr(locale, "LC_CTYPE="))
|
|
locale = strstr(locale, "LC_CTYPE=") + strlen("LC_CTYPE");
|
|
end = strstr(locale, ";");
|
|
if (end)
|
|
*end = 0;
|
|
for (int i = 0; i < sizeof(g_known_locale) / sizeof(g_known_locale[0]); ++i)
|
|
{
|
|
if (strstr(locale, g_known_locale[i].locale))
|
|
{
|
|
cp = g_known_locale[i].cp;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found && strrchr(locale, '.'))
|
|
{
|
|
// windows: Chinese (Simplified)_China.936
|
|
locale = strrchr(locale, '.') + 1;
|
|
if (*locale >= '0' && *locale <= '9')
|
|
cp = atoi(locale);
|
|
}
|
|
*end = ';';
|
|
}
|
|
|
|
return cp;
|
|
}
|
|
|
|
public:
|
|
static lang_mgr* instance(void)
|
|
{
|
|
if (!lang_mgr::inst_)
|
|
lang_mgr::inst_ = new lang_mgr();
|
|
|
|
return lang_mgr::inst_;
|
|
}
|
|
int get_cur_cp(void)
|
|
{
|
|
return cur_cp_;
|
|
}
|
|
std::string get_cur_name(void)
|
|
{
|
|
return cur_lang_;
|
|
}
|
|
LANATTR** get_all(void)
|
|
{
|
|
return all_;
|
|
}
|
|
int set_code_page(int cp)
|
|
{
|
|
if (cp == 0)
|
|
cp = lang_mgr::get_os_code_page();
|
|
if (cp == cur_cp_)
|
|
return 0;
|
|
|
|
std::vector<MAPSTR>::iterator it = std::find(code_pages_.begin(), code_pages_.end(), cp);
|
|
if (it == code_pages_.end())
|
|
return ENOENT;
|
|
|
|
load_language_pak(it->file.c_str(), false);
|
|
|
|
return 0;
|
|
}
|
|
const char* get_string(uint32_t id, int* err)
|
|
{
|
|
if (map_off_[id].size())
|
|
{
|
|
if (err)
|
|
*err = 0;
|
|
|
|
return map_off_[id].c_str();
|
|
}
|
|
else
|
|
{
|
|
if (err)
|
|
*err = ENOENT;
|
|
|
|
return "";
|
|
}
|
|
}
|
|
int supported_cps(void)
|
|
{
|
|
return code_pages_.size();
|
|
}
|
|
};
|
|
lang_mgr* lang_mgr::inst_ = nullptr;
|
|
|
|
extern "C"
|
|
{
|
|
int lang_initialize(void* param)
|
|
{
|
|
if (lang_mgr::instance()->supported_cps() > 0)
|
|
return lang_mgr::instance()->get_cur_cp();
|
|
else
|
|
return -1;
|
|
}
|
|
LANATTR** lang_get_supported_languages(void)
|
|
{
|
|
return lang_mgr::instance()->get_all();
|
|
}
|
|
int lang_get_cur_code_page(void)
|
|
{
|
|
return lang_mgr::instance()->get_cur_cp();
|
|
}
|
|
int lang_set_code_page(int cp)
|
|
{
|
|
return lang_mgr::instance()->set_code_page(cp);
|
|
}
|
|
const char* lang_load_string(uint32_t id, int* err)
|
|
{
|
|
return lang_mgr::instance()->get_string(id, err);
|
|
}
|
|
}
|