Added language path for loading fonts (#368)

Some code fixes to support the language path.
This commit is contained in:
NovaRain
2021-04-24 10:25:51 +08:00
parent 99c95b2154
commit b72b26d292
14 changed files with 154 additions and 89 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
UseCommandLine=0
;Uncomment and point to a file to get alternate translations for some sfall messages
;This file can be placed in data\text\<language>\ for sfall to get the translations depending on the game language
;This file can be placed in text\<language>\ for sfall to get the translations depending on the game language
;TranslationsINI=sfall\Translations.ini
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+50 -7
View File
@@ -30,18 +30,52 @@ namespace fo
static fo::MessageNode messageBuf;
const char* GetMessageStr(const fo::MessageList* fileAddr, long messageId) {
return fo::func::getmsg(fileAddr, &messageBuf, messageId);
const char* GetMessageStr(const fo::MessageList* file, long messageId) {
return fo::func::getmsg(file, &messageBuf, messageId);
}
const char* MessageSearch(const fo::MessageList* fileAddr, long messageId) {
const char* MessageSearch(const fo::MessageList* file, long messageId) {
messageBuf.number = messageId;
if (fo::func::message_search(fileAddr, &messageBuf) == 1) {
if (fo::func::message_search(file, &messageBuf) == 1) {
return messageBuf.message;
}
return nullptr;
}
fo::MessageNode* GetMsgNode(fo::MessageList* msgList, int msgNum) {
if (msgList != nullptr && msgList->numMsgs > 0) {
fo::MessageNode *msgNode = msgList->nodes;
long last = msgList->numMsgs - 1;
long first = 0;
long mid;
// Use Binary Search to find msg
while (first <= last) {
mid = (first + last) / 2;
if (msgNum > msgNode[mid].number)
first = mid + 1;
else if (msgNum < msgNode[mid].number)
last = mid - 1;
else
return &msgNode[mid];
}
}
return nullptr;
}
// Alternative version of getmsg_ function
char* GetMsg(fo::MessageList* msgList, int msgNum, int msgType) {
fo::MessageNode *msgNode = GetMsgNode(msgList, msgNum);
if (msgNode) {
if (msgType == 2) {
return msgNode->message;
} else if (msgType == 1) {
return msgNode->audio;
}
}
return nullptr;
}
fo::Queue* QueueFind(fo::GameObject* object, long type) {
if (fo::var::queue) {
fo::Queue* queue = fo::var::queue;
@@ -582,14 +616,23 @@ static bool LoadFrmFrame(fo::UnlistedFrm::Frame *frame, fo::DbFile* frmStream) {
fo::UnlistedFrm *LoadUnlistedFrm(char *frmName, unsigned int folderRef) {
if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr;
char *artfolder = fo::var::art[folderRef].path; // address of art type name
const char *artfolder = fo::var::art[folderRef].path; // address of art type name
char frmPath[MAX_PATH];
sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s", artfolder, frmName);
if (fo::var::use_language) {
sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s\\%s", (const char*)&fo::var::language, artfolder, frmName);
} else {
sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s", artfolder, frmName);
}
fo::UnlistedFrm *frm = new fo::UnlistedFrm;
auto frmStream = fo::func::xfopen(frmPath, "rb");
auto frmStream = fo::func::db_fopen(frmPath, "rb");
if (!frmStream && fo::var::use_language) {
sprintf_s(frmPath, MAX_PATH, "art\\%s\\%s", artfolder, frmName);
frmStream = fo::func::db_fopen(frmPath, "rb");
}
if (frmStream != nullptr) {
if (!LoadFrmHeader(frm, frmStream)) {
+6 -2
View File
@@ -43,10 +43,14 @@ __forceinline void sf_rect_free(fo::RectList* rect) {
}
// returns message string from given file or "Error" when not found
const char* GetMessageStr(const fo::MessageList* fileAddr, long messageId);
const char* GetMessageStr(const fo::MessageList* file, long messageId);
// similar to GetMessageStr, but returns nullptr when no message is found
const char* MessageSearch(const fo::MessageList* fileAddr, long messageId);
const char* MessageSearch(const fo::MessageList* file, long messageId);
fo::MessageNode* GetMsgNode(fo::MessageList* msgList, int msgNum);
char* GetMsg(fo::MessageList* msgList, int msgNum, int msgType);
fo::Queue* QueueFind(fo::GameObject* object, long type);
+2
View File
@@ -136,6 +136,7 @@
#define FO_VAR_itemButtonItems 0x5970F8
#define FO_VAR_itemCurrentItem 0x518F78
#define FO_VAR_kb_lock_flags 0x51E2EA
#define FO_VAR_language 0x56C970 // aka _darn_foreign_sub_path
#define FO_VAR_last_buttons 0x51E2AC
#define FO_VAR_last_button_winID 0x51E404
#define FO_VAR_last_level 0x5707B4
@@ -280,6 +281,7 @@
#define FO_VAR_title_color 0x56D750
#define FO_VAR_title_font 0x56D748
#define FO_VAR_trait_data 0x51DB84
#define FO_VAR_use_language 0x510898 // aka _darn_foreigners
#define FO_VAR_view_page 0x664520
#define FO_VAR_wd_obj 0x59E98C
#define FO_VAR_window 0x6ADE58
+2
View File
@@ -98,6 +98,7 @@ VAR_(inventry_message_file, fo::MessageList)
VARA(itemButtonItems, fo::ItemButtonItem, 2) // 0 - left, 1 - right
VAR_(itemCurrentItem, long) // 0 - left, 1 - right
VAR_(kb_lock_flags, DWORD)
VARA(language, char, 32)
VAR_(last_buttons, DWORD)
VAR_(last_button_winID, DWORD)
VAR_(last_level, DWORD)
@@ -223,6 +224,7 @@ VAR_(tile, DWORD)
VAR_(title_color, DWORD)
VAR_(title_font, DWORD)
VARA(trait_data, fo::TraitInfo, fo::TRAIT_count)
VAR_(use_language, DWORD)
VAR_(view_page, DWORD)
VAR_(wd_obj, DWORD)
VARA(window, fo::Window*, 50)
+6 -8
View File
@@ -18,13 +18,11 @@
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
#include "..\FalloutEngine\EngineUtils.h"
#include "..\Translate.h"
#include "Inventory.h"
#include "LoadGameHook.h"
#include "LoadOrder.h"
#include "Message.h"
#include "PartyControl.h"
#include "ScriptExtender.h"
@@ -578,8 +576,8 @@ static void DrawCharNote(bool style, int winRef, DWORD xPosWin, DWORD yPosWin, B
char *MsgFileName = (style) ? "game\\AppStyle.msg" : "game\\AppRace.msg";
if (fo::func::message_load(&MsgList, MsgFileName) == 1) {
TitleMsg = GetMsg(&MsgList, 100, 2);
InfoMsg = GetMsg(&MsgList, 101, 2);
TitleMsg = fo::GetMsg(&MsgList, 100, 2);
InfoMsg = fo::GetMsg(&MsgList, 101, 2);
}
fo::Window *winInfo = fo::func::GNW_find(winRef);
@@ -704,8 +702,8 @@ void __stdcall HeroSelectWindow(int raceStyleFlag) {
fo::MessageList MsgList;
if (fo::func::message_load(&MsgList, "game\\AppIface.msg") == 1) {
RaceStyleBtn = GetMsg(&MsgList, (isStyle) ? 101 : 100, 2);
DoneBtn = GetMsg(&MsgList, 102, 2);
RaceStyleBtn = fo::GetMsg(&MsgList, (isStyle) ? 101 : 100, 2);
DoneBtn = fo::GetMsg(&MsgList, 102, 2);
} else {
// Get alternate text from ini if available (TODO: remove this in the future)
char titleText[16];
@@ -1230,8 +1228,8 @@ static void __declspec(naked) FixCharScrnBack() {
fo::MessageList MsgList;
if (fo::func::message_load(&MsgList, "game\\AppIface.msg") == 1) {
RaceBtn = GetMsg(&MsgList, 100, 2);
StyleBtn = GetMsg(&MsgList, 101, 2);
RaceBtn = fo::GetMsg(&MsgList, 100, 2);
StyleBtn = fo::GetMsg(&MsgList, 101, 2);
} else {
// Get alternate text from ini if available (TODO: remove this in the future)
char RaceText[8], StyleText[8];
+32
View File
@@ -435,6 +435,32 @@ artNotExist:
}
}
static fo::DbFile* __fastcall LoadFont(const char* font, const char* mode) {
char file[128];
const char* lang;
if (fo::func::get_game_config_string(&lang, "system", "language") && _stricmp(lang, "english") != 0) {
std::sprintf(file, "%s\\%s", lang, font);
return fo::func::db_fopen(file, mode);
}
return nullptr;
}
void __declspec(naked) load_font_hook() {
__asm {
mov ebp, edx;
mov ebx, eax;
mov ecx, eax;
call LoadFont;
test eax, eax;
jz default;
retn;
default:
mov edx, ebp;
mov eax, ebx;
jmp fo::funcoffs::db_fopen_;
}
}
static void SfallResourceFile() {
const char* sfallRes = "sfall.dat";
@@ -501,6 +527,12 @@ void LoadOrder::init() {
MakeCall(0x47FBBF, SlotMap2Game_hack_attr, 1);
dlogr(" Done", DL_INIT);
// Load fonts depending on the game language
HookCalls(load_font_hook, {
0x4D5621, // load_font_
0x441D58 // FMLoadFont_
});
LoadGameHook::OnAfterGameInit() += RemoveSavFiles;
LoadGameHook::OnGameReset() += []() {
savPrototypes.clear();
+16 -46
View File
@@ -30,7 +30,7 @@ namespace sfall
#define CASTMSG(adr) reinterpret_cast<fo::MessageList*>(adr)
const fo::MessageList* gameMsgFiles[] = {
const fo::MessageList* Message::gameMsgFiles[] = {
CASTMSG(MSG_FILE_COMBAT),
CASTMSG(MSG_FILE_AI),
CASTMSG(MSG_FILE_SCRNAME),
@@ -54,13 +54,13 @@ const fo::MessageList* gameMsgFiles[] = {
};
#undef CASTMSG
static char gameLanguage[41]; // max length of language string is 40
static char gameLanguage[32]; // (max length of language string is 40)
const char* Message::GameLanguage() {
return &gameLanguage[0];
}
ExtraGameMessageListsMap gExtraGameMsgLists;
ExtraGameMessageListsMap Message::gExtraGameMsgLists;
static std::vector<std::string> msgFileList;
static long msgNumCounter = 0x3000;
@@ -70,15 +70,17 @@ static long heroIsFemale = -1;
// Searches the special character in the text and removes the text depending on the player's gender
// example: <MaleText^FemaleText>
static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, DWORD* msgFile) {
if (!InDialog() || msgData->flags & MSG_GENDER_CHECK_FLG) return 1;
if (heroIsFemale < 0) heroIsFemale = fo::HeroIsFemale();
unsigned char* _pos = (u_char*)msgData->message;
unsigned char* _pos = (unsigned char*)msgData->message;
unsigned char* pos;
while ((pos = (u_char*)std::strchr((char*)_pos, '^')) != 0) { // pos - pointer to the character position
while ((pos = (unsigned char*)std::strchr((char*)_pos, '^')) != 0) { // pos - pointer to the character position
_pos = pos; // next find position
for (u_char* n = pos - 1; ; n--) {
if (n < (u_char*)msgData->message) {
for (unsigned char* n = pos - 1; ; n--) {
if (n < (unsigned char*)msgData->message) {
_pos++; // error, open char not found
break;
} else if (*n == '<') {
@@ -111,6 +113,7 @@ static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, DWORD* msgFil
}
if (_pos > pos) break;
}
// set flag
unsigned long outValue;
fo::func::message_find(msgFile, msgData->number, &outValue);
@@ -157,39 +160,6 @@ noFile:
}
}
fo::MessageNode* GetMsgNode(fo::MessageList* msgList, int msgRef) {
if (msgList != nullptr && msgList->numMsgs > 0) {
fo::MessageNode *msgNode = msgList->nodes;
long last = msgList->numMsgs - 1;
long first = 0;
long mid;
// Use Binary Search to find msg
while (first <= last) {
mid = (first + last) / 2;
if (msgRef > msgNode[mid].number)
first = mid + 1;
else if (msgRef < msgNode[mid].number)
last = mid - 1;
else
return &msgNode[mid];
}
}
return nullptr;
}
char* GetMsg(fo::MessageList* msgList, int msgRef, int msgNum) {
fo::MessageNode *msgNode = GetMsgNode(msgList, msgRef);
if (msgNode) {
if (msgNum == 2) {
return msgNode->message;
} else if (msgNum == 1) {
return msgNode->audio;
}
}
return nullptr;
}
static void ReadExtraGameMsgFiles() {
if (!msgFileList.empty()) {
int number = 0;
@@ -205,7 +175,7 @@ static void ReadExtraGameMsgFiles() {
path += ".msg";
fo::MessageList* list = new fo::MessageList();
if (fo::func::message_load(list, path.c_str()) == 1) {
gExtraGameMsgLists.insert(std::make_pair(0x2000 + number, list));
Message::gExtraGameMsgLists.insert(std::make_pair(0x2000 + number, list));
} else {
delete list;
}
@@ -218,7 +188,7 @@ static void ReadExtraGameMsgFiles() {
long Message::AddExtraMsgFile(const char* msgName, long msgNumber) {
if (msgNumber) {
if (msgNumber < 0x2000 || msgNumber > 0x2FFF) return -1;
if (gExtraGameMsgLists.count(msgNumber)) return 0; // file has already been added
if (Message::gExtraGameMsgLists.count(msgNumber)) return 0; // file has already been added
} else if (msgNumCounter > 0x3FFF) return -3;
std::string path("game\\");
@@ -233,15 +203,15 @@ long Message::AddExtraMsgFile(const char* msgName, long msgNumber) {
//}
}
if (msgNumber == 0) msgNumber = msgNumCounter++;
gExtraGameMsgLists.emplace(msgNumber, list);
Message::gExtraGameMsgLists.emplace(msgNumber, list);
return msgNumber;
}
static void ClearScriptAddedExtraGameMsg() { // C++11
for (auto it = gExtraGameMsgLists.begin(); it != gExtraGameMsgLists.end();) {
for (auto it = Message::gExtraGameMsgLists.begin(); it != Message::gExtraGameMsgLists.end();) {
if (it->first >= 0x3000 && it->first <= 0x3FFF) {
fo::func::message_exit(it->second.get());
it = gExtraGameMsgLists.erase(it);
it = Message::gExtraGameMsgLists.erase(it);
} else {
++it;
}
@@ -259,7 +229,7 @@ static void FallbackEnglishLoadMsgFiles() {
}
static void ClearReadExtraGameMsgFiles() {
for (auto it = gExtraGameMsgLists.begin(); it != gExtraGameMsgLists.end(); ++it) {
for (auto it = Message::gExtraGameMsgLists.begin(); it != Message::gExtraGameMsgLists.end(); ++it) {
fo::func::message_exit(it->second.get());
}
}
+5 -6
View File
@@ -18,10 +18,11 @@
#pragma once
#include <memory>
#include <unordered_map>
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
//#include "..\FalloutEngine\Fallout2.h"
#include "Module.h"
@@ -50,8 +51,6 @@ namespace sfall
#define MSG_FILE_WORLDMAP (0x672FB0)
typedef std::unordered_map<int, std::unique_ptr<fo::MessageList>> ExtraGameMessageListsMap;
extern ExtraGameMessageListsMap gExtraGameMsgLists;
extern const fo::MessageList* gameMsgFiles[];
class Message : public Module {
public:
@@ -61,10 +60,10 @@ public:
static const char* GameLanguage();
static ExtraGameMessageListsMap gExtraGameMsgLists;
static const fo::MessageList* gameMsgFiles[];
static long AddExtraMsgFile(const char* msgName, long msgNumber);
};
fo::MessageNode* GetMsgNode(fo::MessageList* msgList, int msgRef);
char* GetMsg(fo::MessageList* msgList, int msgRef, int msgNum);
}
+2 -2
View File
@@ -29,7 +29,7 @@
namespace sfall
{
static char mapName[17] = {};
static char mapName[16] = {};
static char patchName[65] = {};
static char versionString[65] = {};
@@ -761,7 +761,7 @@ static void EngineOptimizationPatches() {
void MiscPatches::init() {
EngineOptimizationPatches();
if (IniReader::GetConfigString("Misc", "StartingMap", "", mapName, 17)) {
if (IniReader::GetConfigString("Misc", "StartingMap", "", mapName, 16)) {
dlog("Applying starting map patch.", DL_INIT);
SafeWrite32(0x480AAA, (DWORD)&mapName);
dlogr(" Done", DL_INIT);
+4 -4
View File
@@ -26,19 +26,19 @@
namespace sfall
{
static char startMaleModelName[65] = {};
static char startMaleModelName[33] = {};
char defaultMaleModelName[65] = {};
static char startFemaleModelName[65] = {};
static char startFemaleModelName[33] = {};
char defaultFemaleModelName[65] = {};
void PlayerModel::init() {
if (IniReader::GetConfigString("Misc", "MaleStartModel", "", startMaleModelName, 65)) {
if (IniReader::GetConfigString("Misc", "MaleStartModel", "", startMaleModelName, 33)) {
dlog("Applying male start model patch.", DL_INIT);
SafeWrite32(0x418B88, (DWORD)&startMaleModelName);
dlogr(" Done", DL_INIT);
}
if (IniReader::GetConfigString("Misc", "FemaleStartModel", "", startFemaleModelName, 65)) {
if (IniReader::GetConfigString("Misc", "FemaleStartModel", "", startFemaleModelName, 33)) {
dlog("Applying female start model patch.", DL_INIT);
SafeWrite32(0x418BAB, (DWORD)&startFemaleModelName);
dlogr(" Done", DL_INIT);
+2 -2
View File
@@ -28,14 +28,14 @@ namespace sfall
static fo::PremadeChar* premade;
static const char* __fastcall GetLangPremadePath(const char* premadePath) {
static char premadeLangPath[65]; // premade\<language>\combat.bio
static char premadeLangPath[56]; // premade\<language>\combat.bio
static bool isDefault = false;
static long len = 0;
if (isDefault) return nullptr;
if (len == 0) {
len = std::strlen(Message::GameLanguage());
if (len == 0 || len > 40) {
if (len == 0 || len >= 32) {
isDefault = true;
return nullptr;
}
+22 -6
View File
@@ -506,16 +506,27 @@ static fo::FrmFile* LoadArtFile(const char* file, long frame, long direction, fo
return frmPtr;
}
static long GetArtFIDFile(long fid, const char* &file) {
static long GetArtFIDFile(long fid, char* outFilePath) {
long direction = 0;
long _fid = fid & 0xFFFFFFF;
file = fo::func::art_get_name(_fid); // .frm
const char* artPathName = fo::func::art_get_name(_fid); // <cd>\art\type\file.frm
if (_fid >> 24 == fo::OBJ_TYPE_CRITTER) {
direction = (fid >> 28);
if (direction > 0 && !fo::func::db_access(file)) {
file = fo::func::art_get_name(fid); // .fr#
if (direction > 0 && !fo::func::db_access(artPathName)) {
artPathName = fo::func::art_get_name(fid); // .fr#
}
} else {
if (fo::var::use_language) {
const char* _artPathName = std::strchr(artPathName, '\\');
if (_artPathName && *(++_artPathName)) {
sprintf_s(outFilePath, MAX_PATH, "art\\%s\\%s", (const char*)fo::var::language, _artPathName);
if (fo::func::db_access(outFilePath)) return direction;
}
}
}
std::strcpy(outFilePath, artPathName);
return direction;
}
@@ -532,7 +543,9 @@ static long DrawImage(OpcodeContext& ctx, bool isScaled) {
long fid = ctx.arg(0).rawValue();
if (fid == -1) return -1;
direction = GetArtFIDFile(fid, file);
char fileBuf[MAX_PATH];
direction = GetArtFIDFile(fid, fileBuf);
file = fileBuf;
} else {
file = ctx.arg(0).strValue(); // path to frm/pcx file
}
@@ -602,7 +615,10 @@ static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* ifaceWin) {
if (fid == -1) return -1;
useShift = (((fid & 0xF000000) >> 24) == fo::OBJ_TYPE_CRITTER);
direction = GetArtFIDFile(fid, file);
char fileBuf[MAX_PATH];
direction = GetArtFIDFile(fid, fileBuf);
file = fileBuf;
} else {
file = ctx.arg(1).strValue(); // path to frm/pcx file
}
+4 -5
View File
@@ -17,7 +17,6 @@
*/
#include "..\..\..\FalloutEngine\Fallout2.h"
#include "..\..\..\FalloutEngine\EngineUtils.h"
#include "..\..\ScriptExtender.h"
#include "..\..\Message.h"
#include "..\Arrays.h"
@@ -350,13 +349,13 @@ void op_message_str_game(OpcodeContext& ctx) {
if (fileId >= 0) {
int msgId = ctx.arg(1).rawValue();
if (fileId < 20) { // main msg files
msg = fo::GetMessageStr(gameMsgFiles[fileId], msgId);
msg = fo::GetMessageStr(Message::gameMsgFiles[fileId], msgId);
} else if (fileId >= 0x1000 && fileId <= 0x1005) { // proto msg files
msg = fo::GetMessageStr(&fo::var::proto_msg_files[fileId - 0x1000], msgId);
} else if (fileId >= 0x2000) { // Extra game message files.
ExtraGameMessageListsMap::iterator it = gExtraGameMsgLists.find(fileId);
if (it != gExtraGameMsgLists.end()) {
msg = GetMsg(it->second.get(), msgId, 2);
ExtraGameMessageListsMap::iterator it = Message::gExtraGameMsgLists.find(fileId);
if (it != Message::gExtraGameMsgLists.end()) {
msg = fo::GetMsg(it->second.get(), msgId, 2);
}
}
}