#include "Render/FontCache.h" #include #include #include // clang-format off #include // clang-format on #include #include namespace { // STATIC: 0x01311F00 / 0x01311ED0 const char* const primary[] = { "\xB1\xBC\xB8\xB2", "Arial", "\x82l\x82r \x82o\x83S\x83V\x83" "b\x83N", "\xCB\xCE\xCC\xE5", "\xB2\xD3\xA9\xFA\xC5\xE9", "Tahoma", "Arial", "Verdana", "Verdana", "Arial", "Arial", "Tahoma" }; const char* const fallback[] = {"Gulim", "Arial", "MS Gothic", "SimSun", "MingLiU", "Tahoma", "Arial", "Verdana", "Verdana", "Arial", "Arial", "Tahoma"}; // OFFSET: 0x0054F2C0 // FUNCTION: fn_01956 int CALLBACK MatchFont(const LOGFONTA* font, const TEXTMETRICA*, DWORD, LPARAM name) { return _stricmp(reinterpret_cast(name), font->lfFaceName); } const char* PrivatePath(unsigned char bold) { return bold == 1 ? "system\\font\\SCDream6.otf" : "system\\font\\SCDream4.otf"; } } // OFFSET: 0x0054D530 // FUNCTION: fn_01935 FontCache::FontCache() { Gdiplus::GdiplusStartupInput input; Gdiplus::GdiplusStartup(&this->m_gdiplus, &input, nullptr); } // OFFSET: 0x0100F120 // FUNCTION: fn_29519 FontCache::~FontCache() { for (const FontEntry& entry : this->m_fonts) { DeleteObject(entry.font); // Private-font cleanup. if (entry.privateResources > 0 && entry.family == 29) { RemoveFontResourceExA(PrivatePath(entry.bold), FR_PRIVATE, nullptr); } } this->m_fonts.clear(); Gdiplus::GdiplusShutdown(this->m_gdiplus); } // OFFSET: 0x0054DC90 // FUNCTION: fn_01947 void FontCache::CreateSystemFont(FontEntry& entry) { LOGFONTA description = {}; description.lfCharSet = static_cast(entry.charset); const char* face = primary[entry.family]; // Original does not release either screen DC acquired for enumeration. if (EnumFontFamiliesExA(GetDC(nullptr), &description, MatchFont, reinterpret_cast(face), 0) != 0) { face = fallback[entry.family]; if (EnumFontFamiliesExA(GetDC(nullptr), &description, MatchFont, reinterpret_cast(face), 0) != 0) { face = nullptr; } } entry.font = CreateFontA( entry.height, 0, 0, 0, entry.bold ? 700 : 400, entry.italic != 0, 0, 0, entry.charset, 0, 0, 3, 0, face ); } // OFFSET: 0x0054DEE0 // FUNCTION: fn_01949 // OFFSET: 0x0054F8A0 // FUNCTION: fn_01960 bool FontCache::CreatePrivateFont(FontEntry& entry) { const char* path = entry.family == 29 ? PrivatePath(entry.bold) : ""; entry.privateResources = AddFontResourceExA(path, FR_PRIVATE, nullptr); if (entry.privateResources != 0) { LOGFONTA description = {}; description.lfHeight = -MulDiv(entry.height, 72, 72); description.lfWeight = entry.bold ? 700 : 400; description.lfItalic = entry.italic != 0; description.lfCharSet = static_cast(entry.charset); // Original byte-to-wide conversion sign-extends each char, not ACP decode. std::wstring wide; for (const char* byte = path; *byte; ++byte) { wide.push_back(static_cast(*byte)); } Gdiplus::PrivateFontCollection collection; collection.AddFontFile(wide.c_str()); Gdiplus::FontFamily family; int found = 0; collection.GetFamilies(1, &family, &found); WCHAR name[32] = {}; family.GetFamilyName(name, 0); char narrow[128] = {}; WideCharToMultiByte(CP_THREAD_ACP, 0, name, -1, narrow, sizeof(narrow), nullptr, nullptr); strncpy_s(description.lfFaceName, narrow, _TRUNCATE); entry.font = CreateFontIndirectA(&description); } return entry.privateResources > 0; } // OFFSET: 0x0054F360 // FUNCTION: fn_01958 HFONT FontCache::Get(int family, int height, unsigned charset, unsigned char bold, unsigned char italic) { for (const FontEntry& entry : this->m_fonts) { if ( entry.family == family && entry.height == height && entry.charset == charset && entry.bold == bold && entry.italic == italic ) { return entry.font; } } FontEntry entry{family, height, charset, bold, italic, nullptr, 0}; if (family >= 20 && family <= 28) { static const char* const faces[] = {"RixSquirrel_10.eot", "RixLoveangel_10.eot", "RixNHCgogo_10.eot", "RixDiary_10.eot", "RixMiniHeart_10.eot", "RixFreshman_10.eot", "RixKid_10.eot", "RixMagic_10.eot", "RixJJangu_10.eot"}; LOGFONTA description = {}; description.lfHeight = -MulDiv(13, 72, 72); description.lfWeight = 400; description.lfCharSet = 1; description.lfOutPrecision = 6; description.lfQuality = 2; strcpy_s(description.lfFaceName, faces[family - 20]); entry.font = CreateFontIndirectA(&description); } else if (family != 29 || !CreatePrivateFont(entry)) { CreateSystemFont(entry); } this->m_fonts.push_back(entry); // Original comparator deliberately ignores style flags when ordering. std::sort(this->m_fonts.begin(), this->m_fonts.end(), [](const FontEntry& a, const FontEntry& b) { return std::tie(a.family, a.height, a.charset) < std::tie(b.family, b.height, b.charset); }); return entry.font; }