diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f3bd73..6eb6375 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,6 +267,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC "src/sfall_config.h" "src/sfall_global_vars.cc" "src/sfall_global_vars.h" + "src/sfall_ini.cc" + "src/sfall_ini.h" "src/sfall_lists.cc" "src/sfall_lists.h" "src/sfall_opcodes.cc" diff --git a/src/config.cc b/src/config.cc index 966b0d4..2ec1aad 100644 --- a/src/config.cc +++ b/src/config.cc @@ -280,24 +280,28 @@ bool configRead(Config* config, const char* filePath, bool isDb) if (isDb) { File* stream = fileOpen(filePath, "rb"); - if (stream != NULL) { - while (fileReadString(string, sizeof(string), stream) != NULL) { - configParseLine(config, string); - } - fileClose(stream); + + // CE: Return `false` if file does not exists in database. + if (stream == NULL) { + return false; } + + while (fileReadString(string, sizeof(string), stream) != NULL) { + configParseLine(config, string); + } + fileClose(stream); } else { FILE* stream = compat_fopen(filePath, "rt"); - if (stream != NULL) { - while (compat_fgets(string, sizeof(string), stream) != NULL) { - configParseLine(config, string); - } - fclose(stream); + // CE: Return `false` if file does not exists on the file system. + if (stream == NULL) { + return false; } - // FIXME: This function returns `true` even if the file was not actually - // read. I'm pretty sure it's bug. + while (compat_fgets(string, sizeof(string), stream) != NULL) { + configParseLine(config, string); + } + fclose(stream); } return true; diff --git a/src/game.cc b/src/game.cc index b7bfe0a..4412024 100644 --- a/src/game.cc +++ b/src/game.cc @@ -53,6 +53,7 @@ #include "sfall_arrays.h" #include "sfall_config.h" #include "sfall_global_vars.h" +#include "sfall_ini.h" #include "sfall_lists.h" #include "skill.h" #include "skilldex.h" @@ -354,6 +355,10 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4 return -1; } + char* customConfigBasePath; + configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, &customConfigBasePath); + sfall_ini_set_base_path(customConfigBasePath); + messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MISC, &gMiscMessageList); return 0; diff --git a/src/sfall_config.cc b/src/sfall_config.cc index c0f4b6c..c38b825 100644 --- a/src/sfall_config.cc +++ b/src/sfall_config.cc @@ -58,6 +58,8 @@ bool sfallConfigInit(int argc, char** argv) configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER3, 270); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, 360); + configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, ""); + char path[COMPAT_MAX_PATH]; char* executable = argv[0]; char* ch = strrchr(executable, '\\'); diff --git a/src/sfall_config.h b/src/sfall_config.h index 86aa7a7..f165450 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -8,6 +8,7 @@ namespace fallout { #define SFALL_CONFIG_FILE_NAME "ddraw.ini" #define SFALL_CONFIG_MISC_KEY "Misc" +#define SFALL_CONFIG_SCRIPTS_KEY "Scripts" #define SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_MALE_KEY "MaleDefaultModel" #define SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY "FemaleDefaultModel" @@ -68,6 +69,7 @@ namespace fallout { #define SFALL_CONFIG_TOWN_MAP_HOTKEYS_FIX_KEY "TownMapHotkeysFix" #define SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY "ExtraGameMsgFileList" #define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue" +#define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder" #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3 diff --git a/src/sfall_ini.cc b/src/sfall_ini.cc new file mode 100644 index 0000000..ee4aced --- /dev/null +++ b/src/sfall_ini.cc @@ -0,0 +1,146 @@ +#include "sfall_ini.h" + +#include +#include + +#include "config.h" +#include "platform_compat.h" + +namespace fallout { + +/// The max length of `fileName` chunk in the triplet. +static constexpr size_t kFileNameMaxSize = 63; + +/// The max length of `section` chunk in the triplet. +static constexpr size_t kSectionMaxSize = 32; + +/// Special .ini file names which are accessed without adding base path. +static constexpr const char* kSystemConfigFileNames[] = { + "ddraw.ini", + "f2_res.ini", +}; + +static char basePath[COMPAT_MAX_PATH]; + +/// Parses "fileName|section|key" triplet into parts. `fileName` and `section` +/// chunks are copied into appropriate variables. Returns the pointer to `key`, +/// or `nullptr` on any error. +static const char* parse_ini_triplet(const char* triplet, char* fileName, char* section) +{ + const char* fileNameSectionSep = strchr(triplet, '|'); + if (fileNameSectionSep == nullptr) { + return nullptr; + } + + size_t fileNameLength = fileNameSectionSep - triplet; + if (fileNameLength > kFileNameMaxSize) { + return nullptr; + } + + const char* sectionKeySep = strchr(fileNameSectionSep + 1, '|'); + if (sectionKeySep == nullptr) { + return nullptr; + } + + size_t sectionLength = sectionKeySep - fileNameSectionSep - 1; + if (sectionLength > kSectionMaxSize) { + return nullptr; + } + + strncpy(fileName, triplet, fileNameLength); + fileName[fileNameLength] = '\0'; + + strncpy(section, fileNameSectionSep + 1, sectionLength); + section[sectionLength] = '\0'; + + return sectionKeySep + 1; +} + +/// Returns `true` if given `fileName` is a special system .ini file name. +static bool is_system_file_name(const char* fileName) +{ + for (auto& systemFileName : kSystemConfigFileNames) { + if (compat_stricmp(systemFileName, fileName) == 0) { + return true; + } + } + + return false; +} + +void sfall_ini_set_base_path(const char* path) +{ + if (path != nullptr) { + strcpy(basePath, path); + + size_t length = strlen(basePath); + if (length > 0) { + if (basePath[length - 1] == '\\' || basePath[length - 1] == '/') { + basePath[length - 1] = '\0'; + } + } + } else { + basePath[0] = '\0'; + } +} + +bool sfall_ini_get_int(const char* triplet, int* value) +{ + char string[20]; + if (!sfall_ini_get_string(triplet, string, sizeof(string))) { + return false; + } + + *value = atol(string); + + return true; +} + +bool sfall_ini_get_string(const char* triplet, char* value, size_t size) +{ + char fileName[kFileNameMaxSize]; + char section[kSectionMaxSize]; + + const char* key = parse_ini_triplet(triplet, fileName, section); + if (key == nullptr) { + return false; + } + + Config config; + if (!configInit(&config)) { + return false; + } + + char path[COMPAT_MAX_PATH]; + bool loaded = false; + + if (basePath[0] != '\0' && !is_system_file_name(fileName)) { + // Attempt to load requested file in base directory. + snprintf(path, sizeof(path), "%s\\%s", basePath, fileName); + loaded = configRead(&config, path, false); + } + + if (!loaded) { + // There was no base path set, requested file is a system config, or + // non-system config file was not found the base path - attempt to load + // from current working directory. + strcpy(path, fileName); + loaded = configRead(&config, path, false); + } + + bool ok = false; + if (loaded) { + char* stringValue; + if (configGetString(&config, section, key, &stringValue)) { + strncpy(value, stringValue, size - 1); + value[size - 1] = '\0'; + ok = true; + } + } + + configFree(&config); + + return ok; +} + +} // namespace fallout diff --git a/src/sfall_ini.h b/src/sfall_ini.h new file mode 100644 index 0000000..ac4cdd8 --- /dev/null +++ b/src/sfall_ini.h @@ -0,0 +1,19 @@ +#ifndef FALLOUT_SFALL_INI_H_ +#define FALLOUT_SFALL_INI_H_ + +#include + +namespace fallout { + +/// Sets base directory to lookup .ini files. +void sfall_ini_set_base_path(const char* path); + +/// Reads integer key identified by "fileName|section|key" triplet into `value`. +bool sfall_ini_get_int(const char* triplet, int* value); + +/// Reads string key identified by "fileName|section|key" triplet into `value`. +bool sfall_ini_get_string(const char* triplet, char* value, size_t size); + +} // namespace fallout + +#endif /* FALLOUT_SFALL_INI_H_ */ diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index f6f3ede..3b101ce 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -19,6 +19,7 @@ #include "scripts.h" #include "sfall_arrays.h" #include "sfall_global_vars.h" +#include "sfall_ini.h" #include "sfall_lists.h" #include "stat.h" #include "svga.h" @@ -147,6 +148,19 @@ static void opGetGlobalInt(Program* program) programStackPushInteger(program, value); } +// get_ini_setting +static void op_get_ini_setting(Program* program) +{ + const char* string = programStackPopString(program); + + int value; + if (sfall_ini_get_int(string, &value)) { + programStackPushInteger(program, value); + } else { + programStackPushInteger(program, -1); + } +} + // get_game_mode static void opGetGameMode(Program* program) { @@ -181,6 +195,19 @@ static void op_set_bodypart_hit_modifier(Program* program) combat_set_hit_location_penalty(hit_location, penalty); } +// get_ini_string +static void op_get_ini_string(Program* program) +{ + const char* string = programStackPopString(program); + + char value[256]; + if (sfall_ini_get_string(string, value, sizeof(value))) { + programStackPushString(program, value); + } else { + programStackPushInteger(program, -1); + } +} + // sqrt static void op_sqrt(Program* program) { @@ -785,11 +812,13 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x8193, opGetCurrentHand); interpreterRegisterOpcode(0x819D, opSetGlobalVar); interpreterRegisterOpcode(0x819E, opGetGlobalInt); + interpreterRegisterOpcode(0x81AC, op_get_ini_setting); interpreterRegisterOpcode(0x81AF, opGetGameMode); interpreterRegisterOpcode(0x81B3, op_get_uptime); interpreterRegisterOpcode(0x81B6, op_set_car_current_town); interpreterRegisterOpcode(0x81DF, op_get_bodypart_hit_modifier); interpreterRegisterOpcode(0x81E0, op_set_bodypart_hit_modifier); + interpreterRegisterOpcode(0x81EB, op_get_ini_string); interpreterRegisterOpcode(0x81EC, op_sqrt); interpreterRegisterOpcode(0x81ED, op_abs); interpreterRegisterOpcode(0x8204, op_get_proto_data);