diff --git a/CMakeLists.txt b/CMakeLists.txt index 87859f18f0..69c6e6f115 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,11 @@ add_library(${PROJECT_NAME} OBJECT src/dynrpg_textplugin.h src/enemyai.cpp src/enemyai.h + src/exe_buildinfo.h + src/exe_constants.h + src/exe_patches.h + src/exe_shared.h + src/exe_strings.h src/exe_reader.cpp src/exe_reader.h src/exfont.h diff --git a/Makefile.am b/Makefile.am index 02992d0f91..e95b677527 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,6 +89,11 @@ libeasyrpg_player_a_SOURCES = \ src/dynrpg_textplugin.cpp \ src/enemyai.cpp \ src/enemyai.h \ + src/exe_buildinfo.h \ + src/exe_constants.h \ + src/exe_patches.h \ + src/exe_shared.h \ + src/exe_strings.h \ src/exe_reader.cpp \ src/exe_reader.h \ src/exfont.h \ diff --git a/src/battle_message.cpp b/src/battle_message.cpp index 1b89b53f85..0994cbcbac 100644 --- a/src/battle_message.cpp +++ b/src/battle_message.cpp @@ -150,6 +150,15 @@ static std::string GetHpSpRecoveredMessage(const Game_Battler& target, int value Utils::MakeSvArray(target.GetName(), std::to_string(value), points) ); } + + StringView template_text; + if (Player::Constants::HasEmbeddedTemplateString(EXE::Shared::EmbeddedStringTypes::Battle_HpSpRecovery, template_text)) { + return Utils::ReplacePlaceholders( + template_text, + Utils::MakeArray('S', 'U', 'V', 'T'), + Utils::MakeSvArray(target.GetName(), points, std::to_string(value), lcf::Data::terms.hp_recovery) + ); + } std::stringstream ss; std::string particle, particle2, space = ""; @@ -188,6 +197,20 @@ std::string GetParameterAbsorbedMessage(const Game_Battler& source, const Game_B Utils::MakeSvArray(source.GetName(), target.GetName(), std::to_string(value), points) ); } + + auto embedded_str_type = target_is_ally + ? EXE::Shared::EmbeddedStringTypes::Battle_AbsorbAlly + : EXE::Shared::EmbeddedStringTypes::Battle_AbsorbEnemy; + + StringView template_text; + if (Player::Constants::HasEmbeddedTemplateString(embedded_str_type, template_text)) { + return Utils::ReplacePlaceholders( + template_text, + Utils::MakeArray('O', 'U', 'V', 'T'), + Utils::MakeSvArray(target.GetName(), points, std::to_string(value), message) + ); + } + std::stringstream ss; std::string particle, particle2, space = ""; @@ -243,6 +266,20 @@ std::string GetDamagedMessage(const Game_Battler& target, int value) { Utils::MakeSvArray(target.GetName(), std::to_string(value), lcf::Data::terms.health_points) ); } + + auto embedded_str_type = target_is_ally + ? EXE::Shared::EmbeddedStringTypes::Battle_DamageToAlly + : EXE::Shared::EmbeddedStringTypes::Battle_DamageToEnemy; + + StringView template_text; + if (Player::Constants::HasEmbeddedTemplateString(embedded_str_type, template_text)) { + return Utils::ReplacePlaceholders( + template_text, + Utils::MakeArray('S', 'V', 'T'), + Utils::MakeSvArray(target.GetName(), std::to_string(value), message) + ); + } + std::stringstream ss; std::string particle, space = ""; ss << target.GetName(); @@ -276,6 +313,20 @@ std::string GetParameterChangeMessage(const Game_Battler& target, int value, Str Utils::MakeSvArray(target.GetName(), std::to_string(value), points) ); } + + auto embedded_str_type = is_positive + ? EXE::Shared::EmbeddedStringTypes::Battle_StatIncrease + : EXE::Shared::EmbeddedStringTypes::Battle_StatDecrease; + + StringView template_text; + if (Player::Constants::HasEmbeddedTemplateString(embedded_str_type, template_text)) { + return Utils::ReplacePlaceholders( + template_text, + Utils::MakeArray('S', 'U', 'V', 'T'), + Utils::MakeSvArray(target.GetName(), points, std::to_string(value), message) + ); + } + std::stringstream ss; std::string particle, particle2, space = ""; ss << target.GetName(); @@ -437,6 +488,16 @@ std::string GetItemStartMessage2k(const Game_Battler& source, const lcf::rpg::It Utils::MakeSvArray(source.GetName(), item.name) ); } + + //StringView template_text; + //if (Player::Constants::HasEmbeddedTemplateString(EXE::Shared::EmbeddedStringTypes::Battle_UseItem, template_text)) { + // return Utils::ReplacePlaceholders( + // template_text, + // Utils::MakeArray('S', 'O', 'T'), + // Utils::MakeSvArray(source.GetName(), item.name, ToString(lcf::Data::terms.use_item)) + // ); + //} + std::string particle; if (Player::IsCP932()) particle = "は"; diff --git a/src/exe_buildinfo.h b/src/exe_buildinfo.h new file mode 100644 index 0000000000..fc6aa932f3 --- /dev/null +++ b/src/exe_buildinfo.h @@ -0,0 +1,242 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_EXE_BUILDINFO_H +#define EP_EXE_BUILDINFO_H + +#include +#include +#include "utils.h" + +namespace EXE::BuildInfo { + + enum class EngineType { + UnknownEngine = 0, + RPG2000, + RPG2003, + RM2k3_MP, + EasyRPG + }; + + static constexpr auto kEngineTypes = lcf::makeEnumTags( + "Unknown", + "RPG2000", + "RPG2003", + "RM2k3_MP", + "EasyRPG" + ); + + enum KnownEngineBuildVersions { + UnknownBuild = 0, + + RM2K_20000306, + RM2K_2000XXXX_UNK, + RM2K_20000507, + RM2K_20000619, + RM2K_20000711, + RM2K_20001113, + RM2K_20001115, + RM2K_20001227, + RM2K_20010505, + RM2K_20030327, + RM2K_20030625, + RM2KE_160, + RM2KE_161, + RM2KE_162, + + RM2K3_100, + RM2K3_UNK_1, + RM2K3_UNK_2, + RM2K3_1021_1021, + RM2K3_1030_1030_1, + RM2K3_1030_1030_2, + RM2K3_1030_1040, + RM2K3_1050_1050_1, + RM2K3_1050_1050_2, + RM2K3_1060_1060, + RM2K3_1070_1070, + RM2K3_1080_1080, + RM2K3_1091_1091, + + LAST_RM2K = RM2KE_162, + LAST_RM2K3 = RM2K3_1091_1091, + LAST + }; + + static constexpr auto kKnownEngineBuildDescriptions = lcf::makeEnumTags( + "UnknownBuild", + + "1.00", + "2000-Unk", + "2000-05-07", + "2000-06-19", + "2000-07-11", + "2000-11-13", + "2000-11-15", + "1.07", + "1.10", + "1.50 (VALUE!)", + "1.51 (VALUE!)", + "1.60 (English)", + "1.61 (English)", + "1.62 (English)", + + "1.0.0", + "1.0.1_UNK1", + "1.0.1_UNK2", + "1.0.2.1", + "1.0.3.1", + "1.0.3.2", + "1.0.4.0", + "1.0.5.1", + "1.0.5.2", + "1.0.6", + "1.0.7", + "1.0.8", + "1.0.9.1" + ); + + static_assert(kKnownEngineBuildDescriptions.size() == static_cast(KnownEngineBuildVersions::LAST)); + + constexpr size_t count_known_engine_builds = KnownEngineBuildVersions::LAST; + constexpr size_t count_known_rm2k_builds = 1 + KnownEngineBuildVersions::LAST_RM2K - KnownEngineBuildVersions::RM2K_20000306; + constexpr size_t count_known_rm2k3_builds = 1 + KnownEngineBuildVersions::LAST_RM2K3 - KnownEngineBuildVersions::RM2K3_100; + + struct EngineBuildInfo { + EngineType engine_type; + int32_t code_size; + int32_t entrypoint; + + constexpr EngineBuildInfo() : + engine_type(EngineType::UnknownEngine), code_size(0), entrypoint(0) { + } + + constexpr EngineBuildInfo(EngineType engine_type, int32_t code_size, int32_t entrypoint) : + engine_type(engine_type), code_size(code_size), entrypoint(entrypoint) { + } + }; + + using engine_buildinfo_map = std::array, count_known_engine_builds>; + + constexpr engine_buildinfo_map known_engine_builds = {{ + { UnknownBuild, { EngineType::UnknownEngine, 0, 0 } }, + + { RM2K_20000306, { EngineType::RPG2000, 0x96400, 0x097220 } }, + { RM2K_2000XXXX_UNK, { EngineType::RPG2000, 0x96600, 0x0972C4 } }, + { RM2K_20000507, { EngineType::RPG2000, 0x96600, 0x0972D8 } }, + { RM2K_20000619, { EngineType::RPG2000, 0x96C00, 0x097940 } }, + { RM2K_20000711, { EngineType::RPG2000, 0x96E00, 0x097B50 } }, + { RM2K_20001113, { EngineType::RPG2000, 0x96800, 0x0975E4 } }, + { RM2K_20001115, { EngineType::RPG2000, 0x96800, 0x0975EC } }, + { RM2K_20001227, { EngineType::RPG2000, 0x96A00, 0x097744 } }, + { RM2K_20010505, { EngineType::RPG2000, 0x96A00, 0x097710 } }, + { RM2K_20030327, { EngineType::RPG2000, 0x9BC00, 0x09CA80 } }, + { RM2K_20030625, { EngineType::RPG2000, 0x9BE00, 0x09CC30 } }, + { RM2KE_160, { EngineType::RPG2000, 0x9C000, 0x09CC78 } }, + { RM2KE_161, { EngineType::RPG2000, 0x9CA00, 0x09D708 } }, + { RM2KE_162, { EngineType::RPG2000, 0x9CC00, 0x09D930 } }, + + { RM2K3_100, { EngineType::RPG2003, 0xBD600, 0x0BE3F4 } }, + { RM2K3_UNK_1, { EngineType::RPG2003, 0xBF200, 0x0BFF50 } }, + { RM2K3_UNK_2, { EngineType::RPG2003, 0xBE800, 0x0BF6A8 } }, + { RM2K3_1021_1021, { EngineType::RPG2003, 0xBF400, 0x0C00CC } }, + { RM2K3_1030_1030_1, { EngineType::RPG2003, 0xBFE00, 0x0C0B90 } }, + { RM2K3_1030_1030_2, { EngineType::RPG2003, 0xC0A00, 0x0C1878 } }, + { RM2K3_1030_1040, { EngineType::RPG2003, 0xC0800, 0x0C14D8 } }, + { RM2K3_1050_1050_1, { EngineType::RPG2003, 0xC7400, 0x0C81C4 } }, + { RM2K3_1050_1050_2, { EngineType::RPG2003, 0xC7400, 0x0C81F0 } }, + { RM2K3_1060_1060, { EngineType::RPG2003, 0xC8A00, 0x0C9804 } }, + { RM2K3_1070_1070, { EngineType::RPG2003, 0xC8C00, 0x0C9984 } }, + { RM2K3_1080_1080, { EngineType::RPG2003, 0xC8E00, 0x0C9C1C } }, + { RM2K3_1091_1091, { EngineType::RPG2003, 0xC9000, 0x0C9D24 } } + }}; + + template + class fixed_size_byte_array { + public: + template + constexpr fixed_size_byte_array(Args... args) + : _size(sizeof...(args)), _data(init_fixedsize_array({ args... })) { + } + constexpr size_t size() const { return _size; } + + constexpr const uint8_t operator[](const size_t off) const noexcept { + return _data[off]; + } + private: + template + static constexpr std::array init_fixedsize_array(const std::array input) { + std::array result{}; + for (size_t i = 0; i < N; ++i) { + result[i] = input[i]; + } + return result; + } + + size_t _size; + std::array _data; + }; + + constexpr size_t MAX_SIZE_CHK_PRE = 4; + + using validate_const_data = fixed_size_byte_array; + + class CodeAddressInfo { + public: + int32_t default_val; + uint8_t size_val; + size_t code_offset; + validate_const_data pre_data; + + template + constexpr CodeAddressInfo(int32_t default_val, uint8_t size_val, size_t code_offset, Args... args) : + default_val(default_val), size_val(size_val), code_offset(code_offset), pre_data(validate_const_data{ static_cast(args)... }) { + } + }; + + class CodeAddressStringInfo { + public: + size_t code_offset; + uint32_t crc_jp, crc_en; + validate_const_data pre_data; + + template + constexpr CodeAddressStringInfo(size_t code_offset, uint32_t crc_jp, uint32_t crc_en, Args... args) : + code_offset(code_offset), crc_jp(crc_jp), crc_en(crc_en), pre_data(validate_const_data{ static_cast(args)... }) { + } + }; + + constexpr size_t MAX_SIZE_CHK_PATCH_SEGMENT = 8; + + using patch_segment_data = fixed_size_byte_array; + + class PatchDetectionInfo { + public: + size_t chk_segment_offset; + patch_segment_data chk_segment_data; + size_t extract_var_offset; + + constexpr PatchDetectionInfo(size_t chk_segment_offset, patch_segment_data chk_segment_data) : + chk_segment_offset(chk_segment_offset), chk_segment_data(chk_segment_data), extract_var_offset(0) { + } + constexpr PatchDetectionInfo(size_t chk_segment_offset, patch_segment_data chk_segment_data, size_t extract_var_offset) : + chk_segment_offset(chk_segment_offset), chk_segment_data(chk_segment_data), extract_var_offset(extract_var_offset) { + } + }; +} + +#endif diff --git a/src/exe_constants.h b/src/exe_constants.h new file mode 100644 index 0000000000..53f110325c --- /dev/null +++ b/src/exe_constants.h @@ -0,0 +1,436 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_EXE_CONSTANTS_H +#define EP_EXE_CONSTANTS_H + +#include "exe_buildinfo.h" +#include "exe_shared.h" + +namespace EXE::Constants { + using namespace BuildInfo; + using GameConstantType = EXE::Shared::GameConstantType; + + enum class KnownPatchConfigurations { + Rm2k3_Italian_WD_108, // Italian "WhiteDragon" patch + QP_StatDelimiter, + LAST + }; + + static constexpr auto kKnownPatchConfigurations = lcf::makeEnumTags( + "Rm2k3 Italian 1.08", + "QuickPatch StatDelimiter" + ); + + static_assert(kKnownPatchConfigurations.size() == static_cast(KnownPatchConfigurations::LAST)); + + using patch_config = std::map; + using T = GameConstantType; + + const std::map known_patch_configurations = { + { + KnownPatchConfigurations::Rm2k3_Italian_WD_108, { + { T::MinVarLimit, -999999999 }, + { T::MaxVarLimit, 999999999 }, + { T::MaxEnemyHP, 999999999 }, + { T::MaxEnemySP, 999999999 }, + { T::MaxActorHP, 99999 }, + { T::MaxActorSP, 9999 }, + { T::MaxAtkBaseValue, 9999 }, + { T::MaxDefBaseValue, 9999 }, + { T::MaxSpiBaseValue, 9999 }, + { T::MaxAgiBaseValue, 9999 }, + { T::MaxDamageValue, 99999 }, + { T::MaxGoldValue, 9999999 } + }},{ + KnownPatchConfigurations::QP_StatDelimiter, { + { T::MaxActorHP, 9999999 }, + { T::MaxActorSP, 9999999 }, + { T::MaxAtkBaseValue, 999999 }, + { T::MaxDefBaseValue, 999999 }, + { T::MaxSpiBaseValue, 999999 }, + { T::MaxAgiBaseValue, 999999 }, + { T::MaxAtkBattleValue, 999999 }, + { T::MaxDefBattleValue, 999999 }, + { T::MaxSpiBattleValue, 999999 }, + { T::MaxAgiBattleValue, 999999 } + }} + }; + + using code_address = std::pair; + using code_address_map = std::array(GameConstantType::LAST)>; + +#define ADD_EAX_ESI 0x03, 0xC6 +#define ADD_EDX_ESI 0x03, 0xD6 +#define MOV_EAX 0xB8 +#define MOV_ECX 0xB9 +#define MOV_EDX 0xBA +#define SUB_EDX_EBX 0x2B, 0xD3 +#define CMP_DWORD_ESP 0x81, 0x7C, 0x24 +#define CMP_ESI 0x81, 0xFE +#define CMP_EAX_BYTE 0x83, 0xF8 +#define CMP_EBX_BYTE 0x83, 0xFB + +//#define DEPENDS_ON_PREVIOUS { 0xFF, 0xFF, 0x00, 0xFF } +// +// constexpr auto magic_prev = std::array(DEPENDS_ON_PREVIOUS); + + template + constexpr code_address map(T default_val, size_t code_offset, Args&&... args) { + return { C, CodeAddressInfo(default_val, sizeof(T), code_offset, std::forward(args)...) }; + } + + template + constexpr code_address not_def() { + return { C, CodeAddressInfo(0, 0, 0) }; + } + + using engine_code_adresses_rm2k = std::array, static_cast(count_known_rm2k_builds)>; + using engine_code_adresses_rm2k3 = std::array, static_cast(count_known_rm2k3_builds)>; + + constexpr code_address_map empty_code_map = {{ + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }}; + + constexpr engine_code_adresses_rm2k known_engine_builds_rm2k = {{ + { + RM2K_20000306, + empty_code_map + }, { + RM2K_2000XXXX_UNK, + {{ + map ( -999999, 0x085A0C, CMP_DWORD_ESP, 0x10), + map ( 999999, 0x085A36, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x06D039, MOV_EDX), + map ( 148, 0x06D040, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x06D05B, MOV_EDX), + map ( 88, 0x06D062, SUB_EDX_EBX, MOV_ECX), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }} + }, { + RM2K_20000507, + empty_code_map + },{ + RM2K_20000619, + empty_code_map + },{ + RM2K_20000711, + {{ + map ( -999999, 0x0846A8, CMP_DWORD_ESP, 0x10), + map ( 999999, 0x0846D2, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x06E491, MOV_EDX), + map ( 148, 0x06E498, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x06E4B3, MOV_EDX), + map ( 88, 0x06E4BA, SUB_EDX_EBX, MOV_ECX), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }} + },{ + RM2K_20001113, + empty_code_map + },{ + RM2K_20001115, + empty_code_map + },{ + RM2K_20001227, + {{ + map ( -999999, 0x085D78, CMP_DWORD_ESP, 0x10), + map ( 999999, 0x085DA2, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x06D5B9, MOV_EDX), + map ( 148, 0x06D5C0, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x06D5DB, MOV_EDX), + map ( 88, 0x06D5E2, SUB_EDX_EBX, MOV_ECX), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }} + },{ + RM2K_20010505, + empty_code_map + },{ + RM2K_20030327, + empty_code_map + },{ + RM2K_20030625, + empty_code_map + },{ + RM2KE_160, + empty_code_map + },{ + RM2KE_161, + empty_code_map + },{ + RM2KE_162, + empty_code_map + } + }}; + + constexpr engine_code_adresses_rm2k3 known_engine_builds_rm2k3 = {{ + { + RM2K3_100, + empty_code_map + }, { + RM2K3_UNK_1, + empty_code_map + },{ + RM2K3_UNK_2, + empty_code_map + }, { + RM2K3_1021_1021, + empty_code_map + },{ + RM2K3_1030_1030_1, + empty_code_map + }, { + RM2K3_1030_1030_2, + empty_code_map + },{ + RM2K3_1030_1040, + {{ + map (-9999999, 0x0A60B3, CMP_DWORD_ESP, 0x10), + map ( 9999999, 0x0A60DD, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x08AC49, MOV_EDX), + map ( 148, 0x08AC50, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x08AC6B, MOV_EDX), + map ( 88, 0x08AC72, SUB_EDX_EBX, MOV_ECX), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }} + }, { + RM2K3_1050_1050_1, + empty_code_map + },{ + RM2K3_1050_1050_2, + empty_code_map + }, { + RM2K3_1060_1060, + {{ + map (-9999999, 0x0AC4F7, CMP_DWORD_ESP, 0x10), + map ( 9999999, 0x0AC521, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x08FB6D, MOV_EDX), + map ( 148, 0x08FB74, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x08FB8F, MOV_EDX), + map ( 88, 0x08FB96, SUB_EDX_EBX, MOV_ECX), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + + not_def(), + not_def(), + not_def(), + not_def(), + not_def(), + not_def() + }} + },{ + RM2K3_1070_1070, + empty_code_map + },{ + RM2K3_1080_1080, + {{ + map (-9999999, 0x0AC76B, CMP_DWORD_ESP, 0x10), + map ( 9999999, 0x0AC795, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x08FC21, MOV_EDX), + map ( 148, 0x08FC28, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x08FC43, MOV_EDX), + map ( 88, 0x08FC4A, SUB_EDX_EBX, MOV_ECX), + + map ( 9999, 0x0B652B, MOV_ECX), /* 0x0B8590 - 0x0B858B */ + map ( 999, 0x0B659D, MOV_ECX), /* 0x0B85B2 - 0x0B85AD */ + not_def(), + not_def(), + + map ( 999, 0x0B6636, MOV_ECX), /* 0x0B85D1 - 0xB85CC */ + map ( 999, 0x0B689C, MOV_ECX), /* 0x0B85F0 - 0xB85EB */ + map ( 999, 0x0B694C, MOV_ECX), /* 0x0B860F - 0xB860A */ + map ( 999, 0x0B69F2, MOV_ECX), /* 0x0B862E - 0xB8629 */ + + map ( 9999, 0x0BEF3C, MOV_ECX), + map ( 9999, 0x0BF008, MOV_ECX), + map ( 9999, 0x0BF0D1, MOV_ECX), + map ( 9999, 0x0BF16D, MOV_ECX), + + map ( 9999, 0x09C43C, MOV_EAX), + map ( 9999999, 0x0B60C3, CMP_ESI), + map ( 999999, 0x0A5B54, ADD_EDX_ESI, MOV_EAX), + map ( 99, 0x092399, CMP_EAX_BYTE), + map ( 16, 0x08FB34, CMP_EBX_BYTE), + not_def(), + }} + },{ + RM2K3_1091_1091, + {{ + map (-9999999, 0x0B5103, CMP_DWORD_ESP, 0x10), + map ( 9999999, 0x0B512D, CMP_DWORD_ESP, 0x10), + + map ( 160, 0x08EE15, MOV_EDX), + map ( 148, 0x08EE1C, SUB_EDX_EBX, MOV_ECX), + map ( 160, 0x08EE37, MOV_EDX), + map ( 88, 0x08EE3E, SUB_EDX_EBX, MOV_ECX), + + map ( 9999, 0x0AD543, MOV_ECX), + map ( 999, 0x0AD5B5, MOV_ECX), + not_def(), + not_def(), + + map ( 999, 0x0AD64E, MOV_ECX), + map ( 999, 0x0AD8B4, MOV_ECX), + map ( 999, 0x0AD964, MOV_ECX), + map ( 999, 0x0ADA0A, MOV_ECX), + + map ( 9999, 0x0A92B8, MOV_ECX), + map ( 9999, 0x0A9384, MOV_ECX), + map ( 9999, 0x0A944D, MOV_ECX), + map ( 9999, 0x0A94E9, MOV_ECX), + + map ( 9999, 0x09B770, MOV_EAX), + map ( 9999999, 0x0AD0DB, CMP_ESI), + map ( 999999, 0x0A3EDC, ADD_EDX_ESI, MOV_EAX), + map ( 99, 0x09158D, CMP_EAX_BYTE), + map ( 16, 0x08ED28, CMP_EBX_BYTE), + not_def(), + }} + } + }}; +} + +#endif diff --git a/src/exe_patches.h b/src/exe_patches.h new file mode 100644 index 0000000000..551d01f833 --- /dev/null +++ b/src/exe_patches.h @@ -0,0 +1,104 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_EXE_PATCHES_H +#define EP_EXE_PATCHES_H + +#include "exe_buildinfo.h" +#include "exe_shared.h" + +namespace EXE::Patches { + using namespace BuildInfo; + using KnownPatches = EXE::Shared::KnownPatches; + + template + constexpr patch_segment_data patch_segment(Args... args) { + return patch_segment_data{ static_cast(args)... }; + } + + using patch_detection = std::pair; + template + using patch_detection_map = std::array; + + constexpr patch_detection_map<8> patches_RM2K_107 = {{ + { KnownPatches::UnlockPics, { 0x082B4D, patch_segment(0x90, 0x90, 0x90, 0x90, 0x90) } }, + { KnownPatches::CommonThisEvent, { 0x084F4C, patch_segment(0xE9, 0xAF, 0xBC, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08A62A, patch_segment(0xE8, 0x85, 0xE8, 0xFF, 0xFF) } }, + { KnownPatches::AutoEnterPatch, { 0x06D420, patch_segment(0x75, 0x75) } }, + { KnownPatches::BetterAEP, { 0x06D75F, patch_segment(0xEB, 0x07), 0x096CA4 } }, + { KnownPatches::PicPointer, { 0x087BE6, patch_segment(0xE8, 0xE9, 0xBF, 0xF9, 0xFF) } }, + { KnownPatches::PicPointer_R, { 0x087BE6, patch_segment(0xE8, 0x51, 0xC0, 0xF9, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x078FC8, patch_segment(0xE9, 0x13, 0x92, 0xFB, 0xFF), 0x0321B5 } } + }}; + + constexpr patch_detection_map<4> patches_RM2K_110 = {{ + { KnownPatches::CommonThisEvent, { 0x084E5C, patch_segment(0xE9, 0x33, 0xBD, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08A73A, patch_segment(0xE8, 0x85, 0xE8, 0xFF, 0xFF) } }, + { KnownPatches::PicPointer_R, { 0x023B28, patch_segment(0xE8, 0xD5, 0xC0, 0xF9, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x078ED8, patch_segment(0xE9, 0x93, 0x92, 0xFB, 0xFF), 0x032145 } } + }}; + + constexpr patch_detection_map<3> patches_RM2K_150 = {{ + { KnownPatches::CommonThisEvent, { 0x089EAC, patch_segment(0xE9, 0xDF, 0x6D, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08F72A, patch_segment(0xE8, 0x8D, 0xE8, 0xFF, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x07DE30, patch_segment(0xE9, 0x37, 0x44, 0xFB, 0xFF), 0x032241 } } + }}; + + constexpr patch_detection_map<4> patches_RM2K_151 = {{ + { KnownPatches::CommonThisEvent, { 0x089FC8, patch_segment(0xE9, 0x07, 0x6D, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08F846, patch_segment(0xE88DE8FFFF) } }, + { KnownPatches::PicPointer_R, { 0x08CD82, patch_segment(0xE8, 0x89, 0x6F, 0xF9, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x07DF4C, patch_segment(0xE9, 0x5F, 0x43, 0xFB, 0xFF), 0x032285 } } + }}; + + constexpr patch_detection_map<2> patches_RM2K_160 = {{ + { KnownPatches::CommonThisEvent, { 0x088838, patch_segment(0xE9, 0xEB, 0x85, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08E098, patch_segment(0xE8, 0xAF, 0xE8, 0xFF, 0xFF) } } + }}; + + constexpr patch_detection_map<2> patches_RM2K_161 = {{ + { KnownPatches::CommonThisEvent, { 0x089208, patch_segment(0xE9, 0x2B, 0x80, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x08EAA4, patch_segment(0xE8, 0xAF, 0xE8, 0xFE, 0xFF) } } + }}; + + constexpr patch_detection_map<4> patches_RM2K_162 = {{ + { KnownPatches::CommonThisEvent, { 0x08B8D0, patch_segment(0xE9, 0x63, 0x59, 0xFA, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x09116C, patch_segment(0xE8, 0xAF, 0xE8, 0xFF, 0xFF) } }, + { KnownPatches::PicPointer_R, { 0x08E6CA, patch_segment(0xE8, 0xA5, 0x5B, 0xF9, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x07F848, patch_segment(0xE9, 0xC7, 0x2F, 0xFB, 0xFF), 0x0327E9 } } + }}; + + constexpr patch_detection_map<8> patches_RM2K3_1080 = {{ + { KnownPatches::UnlockPics, { 0x0B12FA, patch_segment(0x90, 0x90, 0x90, 0x90, 0x90) } }, + { KnownPatches::CommonThisEvent, { 0x0AB670, patch_segment(0xE9, 0x8B, 0x76, 0xF9, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x0B1E4B, patch_segment(0xE8, 0x04, 0xE2, 0xFF, 0xFF) } }, + { KnownPatches::AutoEnterPatch, { 0x08FAC0, patch_segment(0x75, 0x3D) } }, + { KnownPatches::BetterAEP, { 0x08FDA7, patch_segment(0xEB, 0x07), 0x0C91A4 } }, + { KnownPatches::PicPointer, { 0x0AEB1E, patch_segment(0xE8, 0x01, 0x5F, 0xF8, 0xFF) } }, + { KnownPatches::PicPointer_R, { 0x0AEB1E, patch_segment(0xE8, 0x65, 0x5F, 0xF8, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x0A0422, patch_segment(0xE9, 0xE2, 0x5E, 0xFA, 0xFF), 0x00462DE } } + }}; + + constexpr patch_detection_map<4> patches_RM2K3_1091 = {{ + { KnownPatches::CommonThisEvent, { 0x0B4008, patch_segment(0xE9, 0xF3, 0xEC, 0xF8, 0xFF) } }, + { KnownPatches::BreakLoopFix, { 0x0BA7E3, patch_segment(0xE8, 0x04, 0xE2, 0xFF, 0xFF) } }, + { KnownPatches::PicPointer_R, { 0x0B74B6, patch_segment(0xE8, 0xCD, 0xD5, 0xF7, 0xFF) } }, + { KnownPatches::DirectMenu, { 0x09F756, patch_segment(0xE9, 0xAE, 0x6B, 0xFA, 0xFF), 0x00462DE } } + }}; +} + +#endif diff --git a/src/exe_reader.cpp b/src/exe_reader.cpp index 4785f021a8..ae8a9921ae 100644 --- a/src/exe_reader.cpp +++ b/src/exe_reader.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace { // hashes of known RPG_RT startup logos @@ -74,6 +75,8 @@ EXEReader::EXEReader(Filesystem_Stream::InputStream core) : corefile(std::move(c uint32_t sectionsOfs = optional_header + GetU16(ofs + 0x14); // skip opt header uint32_t data_directory_ofs = (format_pe32 ? 0x60 : 0x70); resource_rva = GetU32(optional_header + data_directory_ofs + 16); + file_info.entrypoint = format_pe32 ? GetU32(optional_header + 0x10) : 0; + if (!resource_rva) { // Is some kind of encrypted EXE -> Give up return; @@ -90,6 +93,7 @@ EXEReader::EXEReader(Filesystem_Stream::InputStream core) : corefile(std::move(c if (secName == 0x45444F43) { // CODE file_info.code_size = sectVs; + file_info.code_ofs = GetU32(sectionsOfs + 0x14); } else if (secName == 0x52454843) { // CHER(RY) file_info.cherry_size = sectVs; } else if (secName == 0x50454547) { // GEEP @@ -406,7 +410,7 @@ bool EXEReader::ResNameCheck(uint32_t i, const char* p) { } void EXEReader::FileInfo::Print() const { - Output::Debug("RPG_RT information: version={} logos={} code={:#x} cherry={:#x} geep={:#x} arch={} easyrpg={}", version_str, logos, code_size, cherry_size, geep_size, kMachineTypes[machine_type], is_easyrpg_player); + Output::Debug("RPG_RT information: version={} logos={} code={:#x} entrypoint={:#x} cherry={:#x} geep={:#x} arch={} easyrpg={}", version_str, logos, code_size, entrypoint, cherry_size, geep_size, kMachineTypes[machine_type], is_easyrpg_player); } int EXEReader::FileInfo::GetEngineType(bool& is_maniac_patch) const { @@ -489,4 +493,325 @@ int EXEReader::FileInfo::GetEngineType(bool& is_maniac_patch) const { return Player::EngineNone; } +std::map EXEReader::GetOverridenGameConstants() { + constexpr bool debug_const_extraction = true; + + std::map game_constants; + int code_offset = file_info.code_ofs - 0x400; + + auto match_surrounding_data = [&](const EXE::BuildInfo::CodeAddressInfo& info, const uint32_t const_ofs) { + for (int i = 0; i < info.pre_data.size(); i++) { + if (info.pre_data[i] != GetU8(const_ofs - info.pre_data.size() + i)) + return false; + } + /*for (int i = 0; i < info.post_data.size(); i++) { + if (info.post_data[i] != GetU8(const_ofs + sizeof(uint32_t) + i)) + return false; + }*/ + //Is a hit -> constant value can be extracted + return true; + }; + + auto check_address_map = [&](const EXE::Constants::code_address_map& map) { + uint32_t const_ofs; + bool extract_success = false; + + for (auto it = map.begin(); it != map.end();++it) { + auto const_type = it->first; + auto& addr_info = it->second; + + if (addr_info.code_offset == 0) { + // constant is not defined in this map + continue; + } + + const_ofs = code_offset + addr_info.code_offset; + + bool extract_constant = false; + /*if (addr_info.pre_data == ExeConstants::magic_prev && extract_success) { + extract_constant = true; + } else*/ + if (match_surrounding_data(addr_info, const_ofs)) { + extract_constant = true; + } + + if (extract_constant) { + int32_t value; + switch (addr_info.size_val) { + case 4: + value = GetU32(const_ofs); + break; + case 2: + value = GetU16(const_ofs); + break; + case 1: + value = GetU8(const_ofs); + break; + default: + continue; + } + + auto it = game_constants.find(const_type); + if (it != game_constants.end() && it->second == value) { + // Constant override has already been applied through some other means + continue; + } + + if (value != addr_info.default_val || it != game_constants.end()) { + game_constants[const_type] = value; + Output::Debug("Read constant '{}': {} (default: {})", EXE::Shared::kGameConstantType.tag(const_type), value, addr_info.default_val); + } else if (debug_const_extraction) { + Output::Debug("Constant '{}' unchanged: {}", EXE::Shared::kGameConstantType.tag(const_type), value); + } + extract_success = true; + } else { + Output::Debug("Could not read constant '{}'", EXE::Shared::kGameConstantType.tag(const_type)); + extract_success = false; + } + } + }; + + auto apply_known_config = [&](EXE::Constants::KnownPatchConfigurations conf) { + Output::Debug("Assuming known patch config '{}'", EXE::Constants::kKnownPatchConfigurations.tag(static_cast(conf))); + auto it_conf = EXE::Constants::known_patch_configurations.find(conf); + assert(it_conf != EXE::Constants::known_patch_configurations.end()); + + for (auto it = it_conf->second.begin(); it != it_conf->second.end(); ++it) { + game_constants[it->first] = it->second; + } + }; + + for (auto it = EXE::BuildInfo::known_engine_builds.begin(); it != EXE::BuildInfo::known_engine_builds.end(); ++it) { + auto& curr_build_info = std::get(*it); + + if (file_info.code_size != curr_build_info.code_size || file_info.entrypoint != curr_build_info.entrypoint) { + continue; + } + build_version = std::get(*it); + build_info = curr_build_info; + break; + } + + if (build_version != EXE::BuildInfo::KnownEngineBuildVersions::UnknownBuild) { + Output::Debug("Assuming {} build '{}' for constant extraction", + EXE::BuildInfo::kEngineTypes.tag(build_info.engine_type), + EXE::BuildInfo::kKnownEngineBuildDescriptions.tag(build_version)); + + EXE::Constants::code_address_map const* constant_addresses = nullptr; + + switch (build_info.engine_type) { + case EXE::BuildInfo::EngineType::RPG2000: + { + auto& builds = EXE::Constants::known_engine_builds_rm2k; + auto it = std::find_if(builds.begin(), builds.end(), [&](const auto& pair) { + return pair.first == build_version; + }); + if (it != builds.end()) { + constant_addresses = &it->second; + } + } + break; + case EXE::BuildInfo::EngineType::RPG2003: + { + auto& builds = EXE::Constants::known_engine_builds_rm2k3; + auto it = std::find_if(builds.begin(), builds.end(), [&](const auto& pair) { + return pair.first == build_version; + }); + if (it != builds.end()) { + constant_addresses = &it->second; + } + } + break; + } + + switch (build_version) { + case EXE::BuildInfo::RM2KE_162: + if (CheckForString(0x07DEA6, "XXX" /* 3x "POP EAX" */)) { + apply_known_config(EXE::Constants::KnownPatchConfigurations::QP_StatDelimiter); + } + break; + case EXE::BuildInfo::RM2K3_1080_1080: + if (CheckForString(0x08EFE0, "NoTitolo")) { + apply_known_config(EXE::Constants::KnownPatchConfigurations::Rm2k3_Italian_WD_108); + } + if (CheckForString(0x09D679, "XXX" /* 3x "POP EAX" */)) { + apply_known_config(EXE::Constants::KnownPatchConfigurations::QP_StatDelimiter); + } + break; + case EXE::BuildInfo::RM2K3_1091_1091: + if (CheckForString(0x09C9AD, "XXX" /* 3x "POP EAX" */)) { + apply_known_config(EXE::Constants::KnownPatchConfigurations::QP_StatDelimiter); + } + break; + default: + break; + } + + if (constant_addresses) { + check_address_map(*constant_addresses); + } + } else { + Output::Debug("Unknown build"); + } + return game_constants; +} + +std::map EXEReader::GetEmbeddedStrings(std::string encoding) { + constexpr int max_string_size = 32; + constexpr bool debug_string_extraction = true; + + std::map embedded_strings; + int code_offset = file_info.code_ofs - 0x400; + std::array str_data; + + auto match_surrounding_data = [&](const EXE::BuildInfo::CodeAddressStringInfo& info, const uint32_t const_ofs) { + for (int i = 0; i < info.pre_data.size(); i++) { + if (info.pre_data[i] != GetU8(const_ofs - info.pre_data.size() + i)) + return false; + } + return true; + }; + + auto check_string_address_map = [&](const EXE::Strings::string_address_map& map) { + uint32_t const_ofs; + bool extract_success = false; + + for (auto it = map.begin(); it != map.end(); ++it) { + auto const_type = it->first; + auto& addr_info = it->second; + + if (addr_info.code_offset == 0) { + // string is not defined in this map + continue; + } + + const_ofs = code_offset + addr_info.code_offset; + + bool extract_string = false; + if (match_surrounding_data(addr_info, const_ofs)) { + extract_string = true; + } + + if (extract_string) { + int32_t size_str = GetU32(const_ofs); + if (size_str > max_string_size) { + Output::Debug("Unexpected length for embedded string: {} ({})", EXE::Shared::kEmbeddedStringTypes.tag(const_type), size_str); + continue; + } + const_ofs += 4; + for (int i = 0; i < size_str; ++i) { + str_data[i] = GetU8(const_ofs + i); + } + auto crc = static_cast(crc32(0, reinterpret_cast(str_data.data()), size_str)); + + if ((crc != addr_info.crc_jp && crc != addr_info.crc_en) || debug_string_extraction) { + auto extracted_string = lcf::ReaderUtil::Recode(ToString(lcf::DBString(str_data.data(), static_cast(size_str))), encoding); + + if (debug_string_extraction && crc == addr_info.crc_jp) { + Output::Debug("Embedded string for '{}' matches JP -> '{}'", EXE::Shared::kEmbeddedStringTypes.tag(const_type), extracted_string); + } else if (debug_string_extraction && crc == addr_info.crc_en) { + Output::Debug("Embedded string for '{}' matches EN -> '{}'", EXE::Shared::kEmbeddedStringTypes.tag(const_type), extracted_string); + } else { + Output::Debug("Read embedded string '{}' -> '{}'", EXE::Shared::kEmbeddedStringTypes.tag(const_type), extracted_string); + + //TODO: add to map + } + } + + extract_success = true; + } else { + Output::Debug("Could not read embedded string '{}'", EXE::Shared::kEmbeddedStringTypes.tag(const_type)); + extract_success = false; + } + } + }; + + switch (build_version) { + case EXE::BuildInfo::RM2K_20030625: + check_string_address_map(EXE::Strings::string_addresses_rm2k_151); + break; + default: + break; + } + + return embedded_strings; +} + +std::vector EXEReader::CheckForPatches() { + std::vector patches; + + int code_offset = file_info.code_ofs - 0x400; + + auto check_for_patch_segment = [&](const EXE::BuildInfo::PatchDetectionInfo& patch_info) { + for (int i = 0; i < patch_info.chk_segment_data.size(); i++) { + if (patch_info.chk_segment_data[i] != GetU8(code_offset + patch_info.chk_segment_offset + i)) + return false; + } + }; + + auto apply_patches = [&](const auto& patch_detection_map) { + for (auto it = patch_detection_map.begin(); it < patch_detection_map.end(); ++it) { + auto patch_type = it->first; + auto& patch_info = it->second; + + if (!check_for_patch_segment(patch_info)) { + continue; + } + + if (patch_info.extract_var_offset == 0) { + Output::Debug("Detected Patch: '{}'", EXE::Shared::kKnownPatches.tag(static_cast(patch_type))); + patches.emplace_back(EXE::Shared::PatchSetupInfo { patch_type, 0 }); + } else { + int patch_var = GetU32(code_offset + patch_info.extract_var_offset); + if (patch_var > 0) { + Output::Debug("Detected Patch: '{}' (VarId: {})", EXE::Shared::kKnownPatches.tag(static_cast(patch_type)), patch_var); + } + patches.emplace_back(EXE::Shared::PatchSetupInfo{ patch_type, patch_var }); + } + } + }; + + switch (build_version) { + case EXE::BuildInfo::RM2K_20001227: + apply_patches(EXE::Patches::patches_RM2K_107); + break; + case EXE::BuildInfo::RM2K_20010505: + apply_patches(EXE::Patches::patches_RM2K_110); + break; + case EXE::BuildInfo::RM2K_20030327: + apply_patches(EXE::Patches::patches_RM2K_150); + break; + case EXE::BuildInfo::RM2K_20030625: + apply_patches(EXE::Patches::patches_RM2K_151); + break; + case EXE::BuildInfo::RM2KE_160: + apply_patches(EXE::Patches::patches_RM2K_160); + break; + case EXE::BuildInfo::RM2KE_161: + apply_patches(EXE::Patches::patches_RM2K_161); + break; + case EXE::BuildInfo::RM2KE_162: + apply_patches(EXE::Patches::patches_RM2K_162); + break; + + case EXE::BuildInfo::RM2K3_1080_1080: + apply_patches(EXE::Patches::patches_RM2K3_1080); + break; + case EXE::BuildInfo::RM2K3_1091_1091: + apply_patches(EXE::Patches::patches_RM2K3_1091); + break; + default: + break; + } + + return patches; +} + +bool EXEReader::CheckForString(uint32_t offset, const char* p) { + while (*p) { + if (GetU8(file_info.code_ofs - 0x400 + offset++) != *p++) + return false; + } + return true; +} #endif diff --git a/src/exe_reader.h b/src/exe_reader.h index 15abb5ded8..a9ec4ff5b4 100644 --- a/src/exe_reader.h +++ b/src/exe_reader.h @@ -23,6 +23,10 @@ #include #include #include "bitmap.h" +#include "exe_buildinfo.h" +#include "exe_constants.h" +#include "exe_patches.h" +#include "exe_strings.h" #include "player.h" /** @@ -64,6 +68,8 @@ class EXEReader { uint32_t geep_size = 0; MachineType machine_type = MachineType::Unknown; bool is_easyrpg_player = false; + uint32_t code_ofs = 0; + uint32_t entrypoint = 0; int GetEngineType(bool& is_maniac_patch) const; void Print() const; @@ -71,6 +77,12 @@ class EXEReader { const FileInfo& GetFileInfo(); + std::map GetOverridenGameConstants(); + + std::map GetEmbeddedStrings(std::string encoding); + + std::vector CheckForPatches(); + private: // Bounds-checked unaligned reader primitives. // In case of out-of-bounds, returns 0 - this will usually result in a harmless error at some other level, @@ -83,12 +95,18 @@ class EXEReader { uint32_t GetLogoCount(); bool ResNameCheck(uint32_t namepoint, const char* name); + bool CheckForString(uint32_t offset, const char* p); + // 0 if resource section was unfindable. uint32_t resource_ofs = 0; uint32_t resource_rva = 0; FileInfo file_info; Filesystem_Stream::InputStream corefile; + + EXE::BuildInfo::KnownEngineBuildVersions build_version = EXE::BuildInfo::KnownEngineBuildVersions::UnknownBuild; + EXE::BuildInfo::EngineBuildInfo build_info; + }; #endif diff --git a/src/exe_shared.h b/src/exe_shared.h new file mode 100644 index 0000000000..481c08f814 --- /dev/null +++ b/src/exe_shared.h @@ -0,0 +1,157 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_EXE_SHARED_H +#define EP_EXE_SHARED_H + +#include + +namespace EXE::Shared { + + enum class GameConstantType { + MinVarLimit, + MaxVarLimit, + MaxActorHP, + MaxActorSP, + MaxEnemyHP, + MaxEnemySP, + + MaxAtkBaseValue, + MaxDefBaseValue, + MaxSpiBaseValue, + MaxAgiBaseValue, + + MaxAtkBattleValue, + MaxDefBattleValue, + MaxSpiBattleValue, + MaxAgiBattleValue, + + MaxDamageValue, + MaxExpValue, + MaxLevel, + MaxGoldValue, + MaxItemCount, + MaxSaveFiles, + + /** X-coordinate of the title scene command window (HAlign: Center) */ + TitleX, + /** Y-coordinate of the title scene command window (VAlign: Top) */ + TitleY, + /** X-coordinate of the title scene command window when the title graphic is hidden (HAlign: Center) */ + TitleHiddenX, + /** Y-coordinate of the title scene command window when the title graphic is hidden (VAlign: Top) */ + TitleHiddenY, + + LAST + }; + + static constexpr auto kGameConstantType = lcf::makeEnumTags( + "MinVarLimit", + "MaxVarLimit", + "MaxActorHP", + "MaxActorSP", + "MaxEnemyHP", + "MaxEnemySP", + + "MaxAtkBaseValue", + "MaxDefBaseValue", + "MaxSpiBaseValue", + "MaxAgiBaseValue", + + "MaxAtkBattleValue", + "MaxDefBattleValue", + "MaxSpiBattleValue", + "MaxAgiBattleValue", + + "MaxDamageValue", + "MaxExpValue", + "MaxLevel", + "MaxGoldValue", + "MaxItemCount", + "MaxSaveFiles", + "TitleCmdWnd_X", + "TitleCmdWnd_Y", + "TitleHiddenCmdWnd_X", + "TitleHiddenCmdWnd_Y" + ); + + static_assert(kGameConstantType.size() == static_cast(GameConstantType::LAST)); + + enum class EmbeddedStringTypes { + Battle_DamageToEnemy, + Battle_DamageToAlly, + Battle_HpSpRecovery, + Battle_StatDecrease, + Battle_StatIncrease, + Battle_AbsorbEnemy, + Battle_AbsorbAlly, + Battle_UseItem, + Msg_LevelUp, + Menu_ExpMaxedOut, + Menu_2k3ActorStatus_ExpMaxedOut, + LAST + }; + + static constexpr auto kEmbeddedStringTypes = lcf::makeEnumTags( + "Battle_DamageToEnemy", + "Battle_DamageToAlly", + "Battle_HpSpRecovery", + "Battle_StatDecrease", + "Battle_StatIncrease", + "Battle_AbsorbEnemy", + "Battle_AbsorbAlly", + "Battle_UseItem", + "Msg_LevelUp", + "Menu_ExpMaxedOut", + "Menu_2k3ActorStatus_ExpMaxedOut" + ); + + static_assert(kEmbeddedStringTypes.size() == static_cast(EmbeddedStringTypes::LAST)); + + enum class KnownPatches { + UnlockPics, + CommonThisEvent, + BreakLoopFix, + AutoEnterPatch, + BetterAEP, + PicPointer, + PicPointer_R, + DirectMenu, + + LAST + }; + + static constexpr auto kKnownPatches = lcf::makeEnumTags( + "UnlockPics", + "CommonThisEvent", + "BreakLoopFix", + "AutoEnterPatch", + "BetterAEP", + "PicPointer 2.5b", + "PicPointer Restruct", + "DirectMenu" + ); + + static_assert(kKnownPatches.size() == static_cast(KnownPatches::LAST)); + + struct PatchSetupInfo { + KnownPatches patch_type; + int32_t custom_var_1; + }; +} + +#endif diff --git a/src/exe_strings.h b/src/exe_strings.h new file mode 100644 index 0000000000..96398acf1d --- /dev/null +++ b/src/exe_strings.h @@ -0,0 +1,61 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_EXE_STRINGS_H +#define EP_EXE_STRINGS_H + +#include "exe_buildinfo.h" +#include "exe_shared.h" + +namespace EXE::Strings { + using namespace BuildInfo; + using EmbeddedStringTypes = EXE::Shared::EmbeddedStringTypes; + + using string_address = std::pair; + using string_address_map = std::array(EmbeddedStringTypes::LAST)>; + + template + constexpr string_address map(size_t code_offset, uint32_t crc_jp, uint32_t crc_en, Args&&... args) { + return { C, CodeAddressStringInfo(code_offset, crc_jp, crc_en, std::forward(args)...) }; + } + + template + constexpr string_address not_def() { + return { C, CodeAddressStringInfo(0, 0, 0, 0) }; + } + + using Str = EmbeddedStringTypes; + +#define FFFFFFFF 0xFF, 0xFF, 0xFF, 0xFF + + //TODO: offsets: 0x07B848, 0x07B860 (are these strings used anywhere?) + constexpr string_address_map string_addresses_rm2k_151 = {{ + map(0x07AEA4, 0x9D4F8EDE, 0xB55355BF, FFFFFFFF), + map(0x07AEB8, 0x6900AACD, 0xB55355BF, FFFFFFFF), + map(0x07AFF0, 0x7C968020, 0x5299B765, FFFFFFFF), + map(0x07B1E0, 0x7C968020, 0x5299B765, FFFFFFFF), + map(0x07B43C, 0x7C968020, 0x5299B765, FFFFFFFF), + map(0x07B65C, 0xFB3336D9, 0x5299B765, FFFFFFFF), + map(0x07B674, 0x3ABDE919, 0x5299B765, FFFFFFFF), + not_def(), + map(0x0882CC, 0x0DED4C01, 0x5299B765, FFFFFFFF), + map(0x07C678, 0x2770FF3E, 0x2770FF3E, FFFFFFFF), //TODO + not_def() + }}; +} + +#endif diff --git a/src/filefinder.cpp b/src/filefinder.cpp index ccc2b841e5..971fe763b5 100644 --- a/src/filefinder.cpp +++ b/src/filefinder.cpp @@ -399,7 +399,7 @@ bool FileFinder::HasSavegame() { int FileFinder::GetSavegames() { auto fs = Save(); - for (int i = 1; i <= 15; i++) { + for (int i = 1; i <= Player::Constants::MaxSaveFiles(); i++) { std::stringstream ss; ss << "Save" << (i <= 9 ? "0" : "") << i << ".lsd"; std::string filename = fs.FindFile(ss.str()); diff --git a/src/game_actor.cpp b/src/game_actor.cpp index 4543a181c9..e46bddff60 100644 --- a/src/game_actor.cpp +++ b/src/game_actor.cpp @@ -35,47 +35,16 @@ #include "rand.h" #include "algo.h" -constexpr int max_level_2k = 50; -constexpr int max_level_2k3 = 99; - int Game_Actor::MaxHpValue() const { - auto& val = lcf::Data::system.easyrpg_max_actor_hp; - if (val == -1) { - return Player::IsRPG2k() ? 999 : 9999; - } - return val; + return Player::Constants::MaxActorHpValue(); } int Game_Actor::MaxSpValue() const { - auto& val = lcf::Data::system.easyrpg_max_actor_sp; - if (val == -1) { - return 999; - } - return val; -} - -int Game_Actor::MaxStatBattleValue() const { - auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; - if (val == -1) { - return 9999; - } - return val; -} - -int Game_Actor::MaxStatBaseValue() const { - auto& val = lcf::Data::system.easyrpg_max_stat_base_value; - if (val == -1) { - return 999; - } - return val; + return Player::Constants::MaxActorSpValue(); } int Game_Actor::MaxExpValue() const { - auto& val = lcf::Data::system.easyrpg_max_exp; - if (val == -1) { - return Player::IsRPG2k() ? 999999 : 9999999; - } - return val; + return Player::Constants::MaxExpValue(); } Game_Actor::Game_Actor(int actor_id) { @@ -511,7 +480,7 @@ int Game_Actor::GetBaseAtk(Weapon weapon, bool mod, bool equip) const { ForEachEquipment(GetWholeEquipment(), [&](auto& item) { n += item.atk_points1; }, weapon); } - return Utils::Clamp(n, 1, MaxStatBaseValue()); + return Utils::Clamp(n, 1, Player::Constants::MaxAtkBaseValue()); } int Game_Actor::GetBaseAtk(Weapon weapon) const { @@ -534,7 +503,7 @@ int Game_Actor::GetBaseDef(Weapon weapon, bool mod, bool equip) const { ForEachEquipment(GetWholeEquipment(), [&](auto& item) { n += item.def_points1; }, weapon); } - return Utils::Clamp(n, 1, MaxStatBaseValue()); + return Utils::Clamp(n, 1, Player::Constants::MaxDefBaseValue()); } int Game_Actor::GetBaseDef(Weapon weapon) const { @@ -557,7 +526,7 @@ int Game_Actor::GetBaseSpi(Weapon weapon, bool mod, bool equip) const { ForEachEquipment(GetWholeEquipment(), [&](auto& item) { n += item.spi_points1; }, weapon); } - return Utils::Clamp(n, 1, MaxStatBaseValue()); + return Utils::Clamp(n, 1, Player::Constants::MaxSpiBaseValue()); } int Game_Actor::GetBaseSpi(Weapon weapon) const { @@ -580,7 +549,7 @@ int Game_Actor::GetBaseAgi(Weapon weapon, bool mod, bool equip) const { ForEachEquipment(GetWholeEquipment(), [&](auto& item) { n += item.agi_points1; }, weapon); } - return Utils::Clamp(n, 1, MaxStatBaseValue()); + return Utils::Clamp(n, 1, Player::Constants::MaxAgiBaseValue()); } int Game_Actor::GetBaseAgi(Weapon weapon) const { @@ -744,11 +713,7 @@ int Game_Actor::GetAccessoryId() const { } int Game_Actor::GetMaxLevel() const { - int max_level = Player::IsRPG2k() ? max_level_2k : max_level_2k3; - if (lcf::Data::system.easyrpg_max_level > -1) { - max_level = lcf::Data::system.easyrpg_max_level; - } - return Utils::Clamp(max_level, 1, dbActor->final_level); + return Utils::Clamp(Player::Constants::MaxLevel(), 1, dbActor->final_level); } void Game_Actor::SetExp(int _exp) { @@ -805,6 +770,15 @@ std::string Game_Actor::GetLevelUpMessage(int new_level) const { Utils::MakeSvArray(GetName(), ss.str(), lcf::Data::terms.level) ); } else { + StringView template_text; + if (Player::Constants::HasEmbeddedTemplateString(EXE::Shared::EmbeddedStringTypes::Msg_LevelUp, template_text)) { + return Utils::ReplacePlaceholders( + template_text, + Utils::MakeArray('S', 'L', 'V', 'T'), + Utils::MakeSvArray(GetName(), ToString(lcf::Data::terms.use_item), std::to_string(new_level), ToString(lcf::Data::terms.level_up)) + ); + } + std::string particle, space = ""; if (Player::IsCP932()) { particle = "は"; @@ -1161,11 +1135,6 @@ static int ClampMaxSpMod(int sp, const Game_Actor* actor) { return Utils::Clamp(sp, -limit, limit); } -static int ClampStatMod(int value, const Game_Actor* actor) { - auto limit = actor->MaxStatBaseValue(); - return Utils::Clamp(value, -limit, limit); -} - void Game_Actor::SetBaseMaxHp(int maxhp) { int new_hp_mod = data.hp_mod + (maxhp - GetBaseMaxHp()); data.hp_mod = ClampMaxHpMod(new_hp_mod, this); @@ -1192,22 +1161,22 @@ int Game_Actor::SetSp(int sp) { void Game_Actor::SetBaseAtk(int atk) { int new_attack_mod = data.attack_mod + (atk - GetBaseAtk()); - data.attack_mod = ClampStatMod(new_attack_mod, this); + data.attack_mod = Utils::Clamp(new_attack_mod, -Player::Constants::MaxAtkBaseValue(), Player::Constants::MaxAtkBaseValue()); } void Game_Actor::SetBaseDef(int def) { int new_defense_mod = data.defense_mod + (def - GetBaseDef()); - data.defense_mod = ClampStatMod(new_defense_mod, this); + data.defense_mod = Utils::Clamp(new_defense_mod, -Player::Constants::MaxDefBaseValue(), Player::Constants::MaxDefBaseValue()); } void Game_Actor::SetBaseSpi(int spi) { int new_spirit_mod = data.spirit_mod + (spi - GetBaseSpi()); - data.spirit_mod = ClampStatMod(new_spirit_mod, this); + data.spirit_mod = Utils::Clamp(new_spirit_mod, -Player::Constants::MaxSpiBaseValue(), Player::Constants::MaxSpiBaseValue()); } void Game_Actor::SetBaseAgi(int agi) { int new_agility_mod = data.agility_mod + (agi - GetBaseAgi()); - data.agility_mod = ClampStatMod(new_agility_mod, this); + data.agility_mod = Utils::Clamp(new_agility_mod, -Player::Constants::MaxAgiBaseValue(), Player::Constants::MaxAgiBaseValue()); } Game_Actor::RowType Game_Actor::GetBattleRow() const { diff --git a/src/game_actor.h b/src/game_actor.h index c3b34e63d6..479a73ff2e 100644 --- a/src/game_actor.h +++ b/src/game_actor.h @@ -55,10 +55,6 @@ class Game_Actor final : public Game_Battler { int MaxSpValue() const override; - int MaxStatBattleValue() const override; - - int MaxStatBaseValue() const override; - int MaxExpValue() const; virtual PermanentStates GetPermanentStates() const override; @@ -425,7 +421,7 @@ class Game_Actor final : public Game_Battler { /** * Sets exp of actor. - * The value is adjusted to the boundary 0 up 999999. + * The value is adjusted to the boundary 0 up to a maximum (dependent on engine type & patch). * Other actor attributes are not altered. Use ChangeExp to do a proper * experience change. * diff --git a/src/game_battlealgorithm.cpp b/src/game_battlealgorithm.cpp index 6204d32f2c..ce9cc5a22d 100644 --- a/src/game_battlealgorithm.cpp +++ b/src/game_battlealgorithm.cpp @@ -52,7 +52,7 @@ #include "feature.h" static inline int MaxDamageValue() { - return lcf::Data::system.easyrpg_max_damage == -1 ? (Player::IsRPG2k() ? 999 : 9999) : lcf::Data::system.easyrpg_max_damage; + return Player::Constants::MaxDamageValue(); } Game_BattleAlgorithm::AlgorithmBase::AlgorithmBase(Type ty, Game_Battler* source, Game_Battler* target) : diff --git a/src/game_battler.cpp b/src/game_battler.cpp index d6dee13a2e..52d43d7192 100644 --- a/src/game_battler.cpp +++ b/src/game_battler.cpp @@ -544,35 +544,35 @@ static int AdjustParam(int base, int mod, int maxval, Span states } int Game_Battler::CalcValueAfterAtkStates(int value) const { - return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack); + return AdjustParam(value, 0, Player::Constants::MaxAtkBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack); } int Game_Battler::CalcValueAfterDefStates(int value) const { - return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense); + return AdjustParam(value, 0, Player::Constants::MaxDefBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense); } int Game_Battler::CalcValueAfterSpiStates(int value) const { - return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit); + return AdjustParam(value, 0, Player::Constants::MaxSpiBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit); } int Game_Battler::CalcValueAfterAgiStates(int value) const { - return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility); + return AdjustParam(value, 0, Player::Constants::MaxAgiBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility); } int Game_Battler::GetAtk(Weapon weapon) const { - return AdjustParam(GetBaseAtk(weapon), atk_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack); + return AdjustParam(GetBaseAtk(weapon), atk_modifier, Player::Constants::MaxAtkBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack); } int Game_Battler::GetDef(Weapon weapon) const { - return AdjustParam(GetBaseDef(weapon), def_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense); + return AdjustParam(GetBaseDef(weapon), def_modifier, Player::Constants::MaxDefBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense); } int Game_Battler::GetSpi(Weapon weapon) const { - return AdjustParam(GetBaseSpi(weapon), spi_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit); + return AdjustParam(GetBaseSpi(weapon), spi_modifier, Player::Constants::MaxSpiBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit); } int Game_Battler::GetAgi(Weapon weapon) const { - return AdjustParam(GetBaseAgi(weapon), agi_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility); + return AdjustParam(GetBaseAgi(weapon), agi_modifier, Player::Constants::MaxAgiBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility); } int Game_Battler::GetDisplayX() const { diff --git a/src/game_battler.h b/src/game_battler.h index 79b4acad7c..35526ef5c3 100644 --- a/src/game_battler.h +++ b/src/game_battler.h @@ -77,10 +77,6 @@ class Game_Battler { virtual int MaxSpValue() const = 0; - virtual int MaxStatBattleValue() const = 0; - - virtual int MaxStatBaseValue() const = 0; - /** * Gets if battler has a state. * diff --git a/src/game_config_game.cpp b/src/game_config_game.cpp index 4a719b5556..519782bf08 100644 --- a/src/game_config_game.cpp +++ b/src/game_config_game.cpp @@ -78,6 +78,16 @@ void Game_ConfigGame::LoadFromArgs(CmdlineParser& cp) { } continue; } + if (cp.ParseNext(arg, 1, "--engine-path")) { + if (arg.NumValues() > 0) { + std::string path = arg.Value(0); + path = FileFinder::MakeCanonical(path, 0); + if (!path.empty()) { + engine_path.Set(path); + } + } + continue; + } if (cp.ParseNext(arg, 0, "--no-patch")) { patch_support.Set(false); patch_dynrpg.Lock(false); @@ -192,6 +202,7 @@ void Game_ConfigGame::LoadFromStream(Filesystem_Stream::InputStream& is) { new_game.FromIni(ini); engine_str.FromIni(ini); + engine_path.FromIni(ini); fake_resolution.FromIni(ini); if (patch_easyrpg.FromIni(ini)) { diff --git a/src/game_config_game.h b/src/game_config_game.h index fe9e3ec0c0..dff3ac33d2 100644 --- a/src/game_config_game.h +++ b/src/game_config_game.h @@ -37,6 +37,7 @@ struct Game_ConfigGame { BoolConfigParam new_game{ "Start new game", "Skips the title screen and starts a new game directly", "Game", "NewGame", false }; StringConfigParam engine_str{ "Engine", "", "Game", "Engine", std::string() }; + StringConfigParam engine_path{ "Engine Path", "Sets the executable to be used by the engine auto-detection", "Game", "EnginePath", std::string() }; BoolConfigParam fake_resolution{ "Fake Metrics", "Makes games run on higher resolutions (with some success)", "Game", "FakeResolution", false }; BoolConfigParam patch_easyrpg{ "EasyRPG", "EasyRPG Engine Extensions", "Patch", "EasyRPG", false }; BoolConfigParam patch_destiny{ "Destiny Patch", "", "Patch", "Destiny", false }; diff --git a/src/game_enemy.cpp b/src/game_enemy.cpp index 3da6b4a7fd..d73066a631 100644 --- a/src/game_enemy.cpp +++ b/src/game_enemy.cpp @@ -48,35 +48,11 @@ Game_Enemy::Game_Enemy(const lcf::rpg::TroopMember* member) } int Game_Enemy::MaxHpValue() const { - auto& val = lcf::Data::system.easyrpg_max_enemy_hp; - if (val == -1) { - return Player::IsRPG2k() ? 9999 : 99999; - } - return val; + return Player::Constants::MaxEnemyHpValue(); } int Game_Enemy::MaxSpValue() const { - auto& val = lcf::Data::system.easyrpg_max_enemy_sp; - if (val == -1) { - return 9999; - } - return val; -} - -int Game_Enemy::MaxStatBattleValue() const { - auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; - if (val == -1) { - return 9999; - } - return val; -} - -int Game_Enemy::MaxStatBaseValue() const { - auto& val = lcf::Data::system.easyrpg_max_stat_base_value; - if (val == -1) { - return 999; - } - return val; + return Player::Constants::MaxEnemySpValue(); } int Game_Enemy::GetStateProbability(int state_id) const { diff --git a/src/game_enemy.h b/src/game_enemy.h index 0821c2fed9..f243c2fa8e 100644 --- a/src/game_enemy.h +++ b/src/game_enemy.h @@ -41,10 +41,6 @@ class Game_Enemy final : public Game_Battler int MaxSpValue() const override; - int MaxStatBattleValue() const override; - - int MaxStatBaseValue() const override; - Point GetOriginalPosition() const override; void ResetBattle() override; diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 5a5b6dbed7..80e7f35e86 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -3761,7 +3761,7 @@ bool Game_Interpreter::CommandBreakLoop(lcf::rpg::EventCommand const& /* com */) // BreakLoop will jump to the end of the event if there is no loop. - bool has_bug = !Player::IsPatchManiac(); + bool has_bug = !Player::HasBreakLoopFix(); if (!has_bug) { SkipToNextConditional({ Cmd::EndLoop }, list[index].indent - 1); ++index; diff --git a/src/game_party.cpp b/src/game_party.cpp index bfd8e9dd8e..dcfcd2ccce 100644 --- a/src/game_party.cpp +++ b/src/game_party.cpp @@ -161,7 +161,7 @@ int Game_Party::GetItemTotalCount(int item_id) const { int Game_Party::GetMaxItemCount(int item_id) const { const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id); if (!item || item->easyrpg_max_count == -1) { - return (lcf::Data::system.easyrpg_max_item_count == -1 ? 99 : lcf::Data::system.easyrpg_max_item_count); + return Player::Constants::MaxItemCount(); } else { return item->easyrpg_max_count; } @@ -169,12 +169,12 @@ int Game_Party::GetMaxItemCount(int item_id) const { void Game_Party::GainGold(int n) { data.gold = data.gold + n; - data.gold = std::min(std::max(data.gold, 0), 999999); + data.gold = std::min(std::max(data.gold, 0), Player::Constants::MaxGoldValue()); } void Game_Party::LoseGold(int n) { data.gold = data.gold - n; - data.gold = std::min(std::max(data.gold, 0), 999999); + data.gold = std::min(std::max(data.gold, 0), Player::Constants::MaxGoldValue()); } void Game_Party::AddItem(int item_id, int amount) { diff --git a/src/game_variables.h b/src/game_variables.h index 0d6e9382e2..15ac1fa81d 100644 --- a/src/game_variables.h +++ b/src/game_variables.h @@ -34,10 +34,10 @@ class Game_Variables { using Variables_t = std::vector; static constexpr int max_warnings = 10; - static constexpr Var_t min_2k = -999999; - static constexpr Var_t max_2k = 999999; - static constexpr Var_t min_2k3 = -9999999; - static constexpr Var_t max_2k3 = 9999999; + static constexpr Var_t min_2k = -999'999; + static constexpr Var_t max_2k = 999'999; + static constexpr Var_t min_2k3 = -9'999'999; + static constexpr Var_t max_2k3 = 9'999'999; Game_Variables(Var_t minval, Var_t maxval); diff --git a/src/meta.cpp b/src/meta.cpp index bc51999476..3987fa9c46 100644 --- a/src/meta.cpp +++ b/src/meta.cpp @@ -173,7 +173,7 @@ std::vector Meta::BuildImportCandidateList(const FilesystemView& if (is_match) { // Scan over every possible save file and see if any match. - for (int saveId = 0; saveId < 15; saveId++) { + for (int saveId = 0; saveId < Player::Constants::MaxSaveFiles(); saveId++) { std::stringstream ss; ss << "Save" << (saveId <= 8 ? "0" : "") << (saveId + 1) << ".lsd"; diff --git a/src/platform/sdl/main.cpp b/src/platform/sdl/main.cpp index aeffa5b805..97114567af 100644 --- a/src/platform/sdl/main.cpp +++ b/src/platform/sdl/main.cpp @@ -79,8 +79,15 @@ extern "C" int main(int argc, char* argv[]) { EpAndroid::env = (JNIEnv*)SDL_AndroidGetJNIEnv(); #endif + bool detect_engine = args.end() != std::find(args.begin(), args.end(), "--detect-engine"); + Player::Init(std::move(args)); - Player::Run(); + + if (detect_engine) { + Player::PrintEngineInfo(); + } else { + Player::Run(); + } // Close return Player::exit_code; diff --git a/src/player.cpp b/src/player.cpp index eedb575968..1a9bf9498d 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -135,6 +135,7 @@ namespace Player { int rng_seed = -1; Game_ConfigPlayer player_config; Game_ConfigGame game_config; + bool break_loop_fix; #ifdef EMSCRIPTEN std::string emscripten_game_name; #endif @@ -759,14 +760,48 @@ void Player::CreateGameObjects() { if (!exfont_stream) { // Backwards compatible with older Player versions exfont_stream = FileFinder::OpenImage(".", "ExFont"); + } else { + Output::Debug("Using custom ExFont: {}", FileFinder::GetPathInsideGamePath(exfont_stream.GetName())); + Cache::exfont_custom = Utils::ReadStream(exfont_stream); + } + + DetectEngine(false); + + Main_Data::filefinder_rtp = std::make_unique(no_rtp_flag, no_rtp_warning_flag, rtp_path); + + game_config.PrintActivePatches(); + + Constants::ResetOverrides(); + Constants::PrintActiveOverrides(); + + ResetGameObjects(); + + LoadFonts(); + + if (Player::IsPatchKeyPatch()) { + Main_Data::game_ineluki->ExecuteScriptList(FileFinder::Game().FindFile("autorun.script")); + } + + if (Player::IsPatchDestiny()) { + Main_Data::game_destiny->Load(); } +} +void Player::DetectEngine(bool ignore_patch_override) { int& engine = game_config.engine; + std::map game_constant_overrides; + std::vector patches; #ifndef EMSCRIPTEN // Attempt reading ExFont and version information from RPG_RT.exe (not supported on Emscripten) std::unique_ptr exe_reader; - auto exeis = FileFinder::Game().OpenFile(EXE_NAME); + const auto exe_file = game_config.engine_path.Get().empty() ? EXE_NAME : game_config.engine_path.Get(); + + if (!game_config.engine_path.Get().empty()) { + Output::Debug("Using specified .EXE '{}' for engine detection", exe_file); + } + + auto exeis = FileFinder::Game().OpenFile(exe_file); if (exeis) { exe_reader.reset(new EXEReader(std::move(exeis))); @@ -777,11 +812,19 @@ void Player::CreateGameObjects() { version_info.Print(); bool is_patch_maniac; engine = version_info.GetEngineType(is_patch_maniac); - if (!game_config.patch_override) { + if (!game_config.patch_override || ignore_patch_override) { game_config.patch_maniac.Set(is_patch_maniac); } } + game_constant_overrides = exe_reader->GetOverridenGameConstants(); + + if (!game_config.patch_override || ignore_patch_override) { + patches = exe_reader->CheckForPatches(); + } + + exe_reader->GetEmbeddedStrings(encoding); + if (engine == EngineNone) { Output::Debug("Unable to detect version from exe"); } @@ -790,11 +833,6 @@ void Player::CreateGameObjects() { } #endif - if (exfont_stream) { - Output::Debug("Using custom ExFont: {}", FileFinder::GetPathInsideGamePath(exfont_stream.GetName())); - Cache::exfont_custom = Utils::ReadStream(exfont_stream); - } - if (engine == EngineNone) { if (lcf::Data::system.ldb_id == 2003) { engine = EngineRpg2k3; @@ -816,9 +854,7 @@ void Player::CreateGameObjects() { Output::Debug("Engine configured as: 2k={} 2k3={} MajorUpdated={} Eng={}", Player::IsRPG2k(), Player::IsRPG2k3(), Player::IsMajorUpdatedVersion(), Player::IsEnglish()); - Main_Data::filefinder_rtp = std::make_unique(no_rtp_flag, no_rtp_warning_flag, rtp_path); - - if (!game_config.patch_override) { + if (!game_config.patch_override || ignore_patch_override) { if (!FileFinder::Game().FindFile("harmony.dll").empty()) { game_config.patch_key_patch.Set(true); } @@ -835,21 +871,73 @@ void Player::CreateGameObjects() { if (!FileFinder::Game().FindFile(DESTINY_DLL).empty()) { game_config.patch_destiny.Set(true); } - } - game_config.PrintActivePatches(); + using Patch = EXE::Patches::KnownPatches; - ResetGameObjects(); + if (patches.size() > 0) { + for (auto it = patches.begin(); it != patches.end(); ++it) { + auto& patch = *it; - LoadFonts(); + switch (static_cast(patch.patch_type)) { + case Patch::UnlockPics: + if (!game_config.patch_unlock_pics.Get()) { + game_config.patch_unlock_pics.Set(true); + } + break; + case Patch::CommonThisEvent: + if (!game_config.patch_common_this_event.Get()) { + game_config.patch_common_this_event.Set(true); + } + break; + case Patch::BreakLoopFix: + break_loop_fix = true; + break; + case Patch::AutoEnterPatch: + game_config.new_game.Set(true); + break; + case Patch::BetterAEP: + game_config.new_game.Set(true); + // FIXME: implement BetterAEP�s extended patch functionality + // -> patch_var (default: 3350) + break; + case Patch::PicPointer: + case Patch::PicPointer_R: + //TODO + break; + case Patch::DirectMenu: + if (!game_config.patch_direct_menu.Get()) { + game_config.patch_direct_menu.Set(patch.custom_var_1); + } + break; + } + } + } + } - if (Player::IsPatchKeyPatch()) { - Main_Data::game_ineluki->ExecuteScriptList(FileFinder::Game().FindFile("autorun.script")); + if (game_constant_overrides.size() > 0) { + for (auto it = game_constant_overrides.begin(); it != game_constant_overrides.end(); ++it) { + Constants::OverrideGameConstant(it->first, it->second); + } } +} - if (Player::IsPatchDestiny()) { - Main_Data::game_destiny->Load(); +void Player::PrintEngineInfo() { + auto fs = FileFinder::Game(); + + if (!fs) { + fs = FileFinder::Root().Create(Main_Data::GetDefaultProjectPath()); + if (!fs) { + Output::Error("{} is not a valid path", Main_Data::GetDefaultProjectPath()); + } + FileFinder::SetGameFilesystem(fs); } + // Parse game specific settings + CmdlineParser cp(arguments); + game_config = Game_ConfigGame::Create(cp); + + DetectEngine(true); + + // TODO } bool Player::ChangeResolution(int width, int height) { @@ -892,22 +980,9 @@ void Player::ResetGameObjects() { Main_Data::game_switches = std::make_unique(); Main_Data::game_switches->SetLowerLimit(lcf::Data::switches.size()); - auto min_var = lcf::Data::system.easyrpg_variable_min_value; - if (min_var == 0) { - if ((Player::game_config.patch_maniac.Get() & 1) == 1) { - min_var = std::numeric_limits::min(); - } else { - min_var = Player::IsRPG2k3() ? Game_Variables::min_2k3 : Game_Variables::min_2k; - } - } - auto max_var = lcf::Data::system.easyrpg_variable_max_value; - if (max_var == 0) { - if ((Player::game_config.patch_maniac.Get() & 1) == 1) { - max_var = std::numeric_limits::max(); - } else { - max_var = Player::IsRPG2k3() ? Game_Variables::max_2k3 : Game_Variables::max_2k; - } - } + int32_t min_var, max_var; + Player::Constants::GetVariableLimits(min_var, max_var); + Main_Data::game_variables = std::make_unique(min_var, max_var); Main_Data::game_variables->SetLowerLimit(lcf::Data::variables.size()); @@ -1416,6 +1491,8 @@ Engine options: rpg2k3 - RPG Maker 2003 (v1.00 - v1.04) rpg2k3v105 - RPG Maker 2003 (v1.05 - v1.09a) rpg2k3e - RPG Maker 2003 (English release, v1.12) + --engine-path EXE Set a custom path for the executable which is to be used + by the automatic engine detection. --font1 FILE Font to use for the first font. The system graphic of the game determines whether font 1 or 2 is used. --font1-size PX Size of font 1 in pixel. The default is 12. @@ -1597,3 +1674,298 @@ std::string Player::GetEngineVersion() { if (EngineVersion() > 0) return std::to_string(EngineVersion()); return std::string(); } + +namespace Player::Constants { + std::map constant_overrides; +} + +void Player::Constants::GetVariableLimits(Var_t& min_var, Var_t& max_var) { + + min_var = lcf::Data::system.easyrpg_variable_min_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MinVarLimit, min_var); + if (min_var == 0) { + if (Player::IsPatchManiac()) { + min_var = std::numeric_limits::min(); + } else { + min_var = Player::IsRPG2k3() ? Game_Variables::min_2k3 : Game_Variables::min_2k; + } + } + max_var = lcf::Data::system.easyrpg_variable_max_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxVarLimit, max_var); + if (max_var == 0) { + if (Player::IsPatchManiac()) { + max_var = std::numeric_limits::max(); + } else { + max_var = Player::IsRPG2k3() ? Game_Variables::max_2k3 : Game_Variables::max_2k; + } + } +} + +int32_t Player::Constants::MaxActorHpValue() { + auto& val = lcf::Data::system.easyrpg_max_actor_hp; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxActorHP, val); + if (val == -1) { + return Player::IsRPG2k() ? 999 : 9'999; + } + return val; +} + +int32_t Player::Constants::MaxActorSpValue() { + auto& val = lcf::Data::system.easyrpg_max_actor_sp; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxActorSP, val); + if (val == -1) { + return 999; + } + return val; +} + +int32_t Player::Constants::MaxEnemyHpValue() { + auto& val = lcf::Data::system.easyrpg_max_enemy_hp; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxEnemyHP, val); + if (val == -1) { + return Player::IsRPG2k() ? 9'999 : 99'999; + } + return val; +} + +int32_t Player::Constants::MaxEnemySpValue() { + auto& val = lcf::Data::system.easyrpg_max_enemy_sp; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxEnemySP, val); + if (val == -1) { + return 9'999; + } + return val; +} + +int32_t Player::Constants::MaxAtkBaseValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_base_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxAtkBaseValue, val); + if (val == -1) { + return max_stat_base_value; + } + return val; +} + +int32_t Player::Constants::MaxDefBaseValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_base_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxDefBaseValue, val); + if (val == -1) { + return max_stat_base_value; + } + return val; +} + +int32_t Player::Constants::MaxSpiBaseValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_base_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxSpiBaseValue, val); + if (val == -1) { + return max_stat_base_value; + } + return val; +} + +int32_t Player::Constants::MaxAgiBaseValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_base_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxAgiBaseValue, val); + if (val == -1) { + return max_stat_base_value; + } + return val; +} + +int32_t Player::Constants::MaxAtkBattleValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxAtkBattleValue, val); + if (val == -1) { + return max_stat_battle_value; + } + return val; +} + +int32_t Player::Constants::MaxDefBattleValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxDefBattleValue, val); + if (val == -1) { + return max_stat_battle_value; + } + return val; +} + +int32_t Player::Constants::MaxSpiBattleValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxSpiBattleValue, val); + if (val == -1) { + return max_stat_battle_value; + } + return val; +} + +int32_t Player::Constants::MaxAgiBattleValue() { + auto& val = lcf::Data::system.easyrpg_max_stat_battle_value; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxAgiBattleValue, val); + if (val == -1) { + return max_stat_battle_value; + } + return val; +} + +int32_t Player::Constants::MaxDamageValue() { + auto& val = lcf::Data::system.easyrpg_max_damage; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxDamageValue, val); + if (val == -1) { + return (Player::IsRPG2k() ? 999 : 9'999); + } + return val; +} + +int32_t Player::Constants::MaxExpValue() { + auto& val = lcf::Data::system.easyrpg_max_exp; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxExpValue, val); + if (val == -1) { + return Player::IsRPG2k() ? 999'999 : 9'999'999; + } + return val; +} + +int32_t Player::Constants::MaxLevel() { + int max_level = Player::IsRPG2k() ? max_level_2k : max_level_2k3; + if (TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxLevel, max_level)) { + return max_level; + } + if (lcf::Data::system.easyrpg_max_level > -1) { + max_level = lcf::Data::system.easyrpg_max_level; + } + return max_level; +} + +int32_t Player::Constants::MaxGoldValue() { + int32_t max_gold = 999'999; + if (TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxGoldValue, max_gold)) { + return max_gold; + } + return max_gold; +} + +int32_t Player::Constants::MaxItemCount() { + int32_t max_item_count = (lcf::Data::system.easyrpg_max_item_count == -1 ? 99 : lcf::Data::system.easyrpg_max_item_count);; + TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxItemCount, max_item_count); + return max_item_count; +} + +int32_t Player::Constants::MaxSaveFiles() { + int32_t max_savefiles = Utils::Clamp(lcf::Data::system.easyrpg_max_savefiles, 3, 99); + if (TryGetOverriddenConstant(EXE::Shared::GameConstantType::MaxSaveFiles, max_savefiles)) { + return max_savefiles - 1; + } + return max_savefiles; +} + +bool Player::Constants::TryGetOverriddenConstant(EXE::Shared::GameConstantType const_type, int32_t& out_value) { + auto it = constant_overrides.find(const_type); + if (it != constant_overrides.end()) { + out_value = (*it).second; + } + return it != constant_overrides.end(); +} + +void Player::Constants::OverrideGameConstant(EXE::Shared::GameConstantType const_type, int32_t value) { + constant_overrides[const_type] = value; +} + +void Player::Constants::ResetOverrides() { + constant_overrides.clear(); +} + +void Player::Constants::PrintActiveOverrides() { + std::vector> overridden_constants; + + auto tags = EXE::Shared::kGameConstantType.tags(); + int32_t value; + for (int i = 0; i < tags.size(); i++) { + auto const_type = static_cast(tags[i].value); + if (!TryGetOverriddenConstant(const_type, value)) { + continue; + } + + overridden_constants.push_back(std::make_tuple(tags[i].name, value)); + } + + if (!overridden_constants.empty()) { + std::string out = "Overridden Game Constants: "; + bool first = true; + for (const auto& p : overridden_constants) { + if (!first) { + out += ", "; + } + out += fmt::format("{}: {}", std::get(p), std::get(p)); + first = false; + } + Output::DebugStr(out); + } +} + +bool Player::Constants::HasEmbeddedTemplateString(EXE::Shared::EmbeddedStringTypes type, StringView& template_string) { + //TODO + + using Str = EXE::Shared::EmbeddedStringTypes; + + switch (type) { + case Str::Battle_DamageToEnemy: + case Str::Battle_DamageToAlly: + template_string = "%s took %d%s"; + return true; + default: + break; + } + + return false; +} + +void Player::Constants::SetTemplateString(EXE::Shared::EmbeddedStringTypes type, std::string template_string) { + auto apply_new_placeholders = [](std::string& str, std::initializer_list replacements) { + size_t index = str.find("%"); + auto repl = replacements.begin(); + while (index != std::string::npos) { + index++; + if (index < str.length()) { + char type = str[index]; + if (type == 's' && replacements.end() != repl) { + str[index] = *repl; + ++repl; + } else if (type == 'd') { + str[index] = 'V'; + } + } + + index = str.find("%", index); + } + }; + + using Str = EXE::Shared::EmbeddedStringTypes; + + switch (type) { + case Str::Battle_DamageToEnemy: + case Str::Battle_DamageToAlly: + apply_new_placeholders(template_string, { 'S', 'T' }); + break; + case Str::Battle_AbsorbEnemy: + case Str::Battle_AbsorbAlly: + apply_new_placeholders(template_string, { 'O', 'U', 'T'}); + break; + case Str::Battle_UseItem: + apply_new_placeholders(template_string, { 'S', 'O', 'T' }); + break; + case Str::Battle_StatIncrease: + case Str::Battle_StatDecrease: + case Str::Battle_HpSpRecovery: + apply_new_placeholders(template_string, { 'S', 'U', 'T'}); + break; + case Str::Msg_LevelUp: + apply_new_placeholders(template_string, { 'S', 'L', 'T' }); + break; + default: + break; + } + + //TODO +} diff --git a/src/player.h b/src/player.h index c8ab392c65..e3ef5c3166 100644 --- a/src/player.h +++ b/src/player.h @@ -19,12 +19,14 @@ #define EP_PLAYER_H // Headers +#include "exe_shared.h" #include "fileext_guesser.h" #include "meta.h" #include "translation.h" #include "game_clock.h" #include "game_config.h" #include "game_config_game.h" +#include "game_variables.h" #include #include #include @@ -130,6 +132,10 @@ namespace Player { */ void CreateGameObjects(); + void DetectEngine(bool ignore_patch_override); + + void PrintEngineInfo(); + /** * Change the resolution of the Player * @@ -297,6 +303,8 @@ namespace Player { */ bool IsPatchDestiny(); + bool HasBreakLoopFix(); + /** * @return True when EasyRpg extensions are on */ @@ -407,6 +415,8 @@ namespace Player { /** Translation manager, including list of languages and current translation. */ extern Translation translation; + extern bool break_loop_fix; + /** * The default speed modifier applied when the speed up button is pressed * Only used for configuring the speedup, don't read this var directly use @@ -427,6 +437,55 @@ namespace Player { /** Name of game emscripten uses */ extern std::string emscripten_game_name; #endif + + namespace Constants { + using Var_t = int32_t; + + static constexpr int32_t max_level_2k = 50; + static constexpr int32_t max_level_2k3 = 99; + + static constexpr int32_t max_hp_2k = 999; + static constexpr int32_t max_hp_2k3 = 9'999; + static constexpr int32_t max_stat_base_value = 999; + static constexpr int32_t max_stat_battle_value = 9'999; + + void GetVariableLimits(Var_t& min_var, Var_t& max_var); + + int32_t MaxActorHpValue(); + int32_t MaxActorSpValue(); + + int32_t MaxEnemyHpValue(); + int32_t MaxEnemySpValue(); + + int32_t MaxAtkBaseValue(); + int32_t MaxDefBaseValue(); + int32_t MaxSpiBaseValue(); + int32_t MaxAgiBaseValue(); + + int32_t MaxAtkBattleValue(); + int32_t MaxDefBattleValue(); + int32_t MaxSpiBattleValue(); + int32_t MaxAgiBattleValue(); + + int32_t MaxDamageValue(); + + int32_t MaxExpValue(); + int32_t MaxLevel(); + + int32_t MaxGoldValue(); + int32_t MaxItemCount(); + int32_t MaxSaveFiles(); + + extern std::map constant_overrides; + + bool TryGetOverriddenConstant(EXE::Shared::GameConstantType const_type, int32_t& out_value); + void OverrideGameConstant(EXE::Shared::GameConstantType const_type, int32_t value); + void ResetOverrides(); + void PrintActiveOverrides(); + + bool HasEmbeddedTemplateString(EXE::Shared::EmbeddedStringTypes type, StringView& template_string); + void SetTemplateString(EXE::Shared::EmbeddedStringTypes type, std::string template_string); + } } inline bool Player::IsRPG2k() { @@ -497,8 +556,11 @@ inline bool Player::IsPatchDestiny() { return game_config.patch_destiny.Get(); } +inline bool Player::HasBreakLoopFix() { + return IsPatchManiac() || break_loop_fix; +} + inline bool Player::HasEasyRpgExtensions() { return game_config.patch_easyrpg.Get(); } - #endif diff --git a/src/scene_equip.cpp b/src/scene_equip.cpp index a5390f2a60..6765041d23 100644 --- a/src/scene_equip.cpp +++ b/src/scene_equip.cpp @@ -135,12 +135,10 @@ void Scene_Equip::UpdateStatusWindow() { } add_item(current_item, 1); - int limit = actor.MaxStatBaseValue(); - - atk = Utils::Clamp(atk, 1, limit); - def = Utils::Clamp(def, 1, limit); - spi = Utils::Clamp(spi, 1, limit); - agi = Utils::Clamp(agi, 1, limit); + atk = Utils::Clamp(atk, 1, Player::Constants::MaxAtkBaseValue()); + def = Utils::Clamp(def, 1, Player::Constants::MaxDefBaseValue()); + spi = Utils::Clamp(spi, 1, Player::Constants::MaxSpiBaseValue()); + agi = Utils::Clamp(agi, 1, Player::Constants::MaxAgiBaseValue()); atk = actor.CalcValueAfterAtkStates(atk); def = actor.CalcValueAfterDefStates(def); diff --git a/src/scene_file.cpp b/src/scene_file.cpp index 4e23f64c9c..cd9fa341b9 100644 --- a/src/scene_file.cpp +++ b/src/scene_file.cpp @@ -123,7 +123,7 @@ void Scene_File::Start() { // Refresh File Finder Save Folder fs = FileFinder::Save(); - for (int i = 0; i < Utils::Clamp(lcf::Data::system.easyrpg_max_savefiles, 3, 99); i++) { + for (int i = 0; i < Player::Constants::MaxSaveFiles(); i++) { std::shared_ptr w(new Window_SaveFile(Player::menu_offset_x, 40 + i * 64, MENU_WIDTH, 64)); w->SetIndex(i); @@ -168,7 +168,7 @@ void Scene_File::RefreshWindows() { } void Scene_File::Refresh() { - for (int i = 0; i < Utils::Clamp(lcf::Data::system.easyrpg_max_savefiles, 3, 99); i++) { + for (int i = 0; i < Player::Constants::MaxSaveFiles(); i++) { Window_SaveFile *w = file_windows[i].get(); PopulateSaveWindow(*w, i); w->Refresh(); diff --git a/src/scene_save.cpp b/src/scene_save.cpp index c802b103ad..a01a54766a 100644 --- a/src/scene_save.cpp +++ b/src/scene_save.cpp @@ -53,7 +53,7 @@ Scene_Save::Scene_Save() : void Scene_Save::Start() { Scene_File::Start(); - for (int i = 0; i < Utils::Clamp(lcf::Data::system.easyrpg_max_savefiles, 3, 99); i++) { + for (int i = 0; i < Player::Constants::MaxSaveFiles(); i++) { file_windows[i]->SetHasSave(true); file_windows[i]->Refresh(); } diff --git a/src/scene_title.cpp b/src/scene_title.cpp index 4dc5280a58..f2b0f8846e 100644 --- a/src/scene_title.cpp +++ b/src/scene_title.cpp @@ -226,12 +226,29 @@ void Scene_Title::CreateTitleGraphic() { } void Scene_Title::RepositionWindow(Window_Command& window, bool center_vertical) { + int title_x = 160; if (!center_vertical) { - window.SetX(Player::screen_width / 2 - window.GetWidth() / 2); - window.SetY(Player::screen_height * 53 / 60 - window.GetHeight()); + int title_y = 148; + if (Player::Constants::TryGetOverriddenConstant(EXE::Shared::GameConstantType::TitleX, title_x) + || Player::Constants::TryGetOverriddenConstant(EXE::Shared::GameConstantType::TitleY, title_y)) { + // RPG_RT aligns its command window as "CenterTop" + window.SetX(title_x - window.GetWidth() / 2 + Player::menu_offset_x); + window.SetY(title_y + Player::menu_offset_y); + } else { + window.SetX(Player::screen_width / 2 - window.GetWidth() / 2); + window.SetY(Player::screen_height * 53 / 60 - window.GetHeight()); + } } else { - window.SetX(Player::screen_width / 2 - window.GetWidth() / 2); - window.SetY(Player::screen_height / 2 - window.GetHeight() / 2); + int title_y = 88; + if (Player::Constants::TryGetOverriddenConstant(EXE::Shared::GameConstantType::TitleHiddenX, title_x) + || Player::Constants::TryGetOverriddenConstant(EXE::Shared::GameConstantType::TitleHiddenY, title_y)) { + // RPG_RT aligns its command window as "CenterTop" + window.SetX(title_x - window.GetWidth() / 2 + Player::menu_offset_x); + window.SetY(title_y + Player::menu_offset_y); + } else { + window.SetX(Player::screen_width / 2 - window.GetWidth() / 2); + window.SetY(Player::screen_height / 2 - window.GetHeight() / 2); + } } } diff --git a/src/window_equipstatus.cpp b/src/window_equipstatus.cpp index 719fced295..b794159984 100644 --- a/src/window_equipstatus.cpp +++ b/src/window_equipstatus.cpp @@ -114,7 +114,9 @@ void Window_EquipStatus::DrawParameter(int cx, int cy, int type) { } // Check if 4 digits are needed instead of 3 - int limit = actor.MaxStatBaseValue(); + int limit = std::max(Player::Constants::MaxAtkBaseValue(), Player::Constants::MaxDefBaseValue()); + limit = std::max(limit, Player::Constants::MaxSpiBaseValue()); + limit = std::max(limit, Player::Constants::MaxAgiBaseValue()); bool more_space_needed = (Player::IsRPG2k3() && limit >= 500) || limit >= 1000; // Draw Term diff --git a/src/window_shopparty.cpp b/src/window_shopparty.cpp index d2b98e8ed2..f871756f1e 100644 --- a/src/window_shopparty.cpp +++ b/src/window_shopparty.cpp @@ -22,6 +22,7 @@ #include "game_actor.h" #include "window_shopparty.h" #include "output.h" +#include "player.h" #include #include "sprite_character.h" @@ -101,12 +102,10 @@ static int CmpEquip(const Game_Actor* actor, const lcf::rpg::Item* new_item) { } add_item(new_item, 1); - int limit = actor->MaxStatBaseValue(); - - atk = Utils::Clamp(atk, 1, limit); - def = Utils::Clamp(def, 1, limit); - spi = Utils::Clamp(spi, 1, limit); - agi = Utils::Clamp(agi, 1, limit); + atk = Utils::Clamp(atk, 1, Player::Constants::MaxAtkBaseValue()); + def = Utils::Clamp(def, 1, Player::Constants::MaxDefBaseValue()); + spi = Utils::Clamp(spi, 1, Player::Constants::MaxSpiBaseValue()); + agi = Utils::Clamp(agi, 1, Player::Constants::MaxAgiBaseValue()); int new_score = atk + def + spi + agi; diff --git a/tests/game_actor.cpp b/tests/game_actor.cpp index 52ac73a3f9..efc40e8db2 100644 --- a/tests/game_actor.cpp +++ b/tests/game_actor.cpp @@ -113,8 +113,6 @@ static void testLimits(int hp, int base, int battle) { auto actor = MakeActor(1, 1, 99, 1, 1, 1, 1, 1, 1); REQUIRE_EQ(actor.MaxHpValue(), hp); - REQUIRE_EQ(actor.MaxStatBaseValue(), base); - REQUIRE_EQ(actor.MaxStatBattleValue(), battle); } SUBCASE("base limits") { diff --git a/tests/game_enemy.cpp b/tests/game_enemy.cpp index 0044dfe57e..f97bb22ffb 100644 --- a/tests/game_enemy.cpp +++ b/tests/game_enemy.cpp @@ -56,8 +56,6 @@ static void testLimits(int hp, int base, int battle) { auto& enemy = MakeEnemy(1, 100, 10, 11, 12, 13, 14); REQUIRE_EQ(enemy.MaxHpValue(), hp); - REQUIRE_EQ(enemy.MaxStatBaseValue(), base); - REQUIRE_EQ(enemy.MaxStatBattleValue(), battle); SUBCASE("up") { enemy.SetAtkModifier(999999);