Player
Player copied to clipboard
Engine detection: Patched RPG_RT constants / strings / PatchDetection / WhiteDragon patch compatibility
This PR moves some of the commonly patched constant values inside a new namespace "Player::Constants" and adds a new mechanism to the ExeReader, which tries to extract these from several known RPG_RT versions. The validity of the values is verified by checking some of the previous bytes in the .CODE segment, whenever the reader is trying to extract a constant value from a known Hex location. Because many of the disassembled code segments look very similar across versions, it should prove quite easy to determine the exact code locations for other RPG_RT versions.
Starting the Player with custom runtime versions
To make it easier to test this feature, I also implemented a new command argument "--engine-path" (See Issue #3166 )
Usage:
Player.exe --test-play --engine-path RT_rm2k3_advo_108_en.dat
(At least for the way I organized my different RPG_RT versions. Supply any path to a custom .EXE here)
Constants supported in this DRAFT:
Versions of RPG_RT supported:
- RM2K: 1.03b, 1.05b, 1.06 (Don Miguel's installer versions)
- RM2K3: 1.04, 1.06, 1.08 (All the known versions translated by RPG Advocate, I was able to find)
Constant values extracted:
- Minimum & Maximum valid range bounds for Variable IDs
- X/Y location of command window on Title screens
- Maximum upper bounds for experience (only rm2k3-v1.08)
- Maximum gold value (only rm2k3-v1.08)
- Maximum damage value (only rm2k3-v1.08)
- Maximum Actor HP/SP (only rm2k3-v1.08)
- Maximum Base Atk/Def/Spi/Agi (only rm2k3-v1.08)
- Maximum effective (Battle) Atk/Def/Spi/Agi (only rm2k3-v1.08)
Documentation used for this feature:
- https://www.makerpendium.de/index.php?title=RPG_RT.exe
- https://www.makerpendium.de/index.php?title=RM_Limit_Changer
- Cherry's "Hyper Patcher"
- Cross referencing rm2k3 1.08 with the associated Italian "WhiteDragon" patch.
- And my own disassembly of several RT versions
Observations regarding the battle stats:
These took some effort to debug & extract, and all of the code locations should definitely be verified by some other person who has more experience in the inner workings of the battle system.
At the moment, battle-related constant locations are only supplied for rm2k3 v1.08 .
Battle Stat Split
Even though they all share the same value, RPG_RT has different constant locations for the base stats: Atk, Def, Agi, Spi. Thus they can be individually patched. Tools like "RM Limit Changer" make it even possible to change these values individually. So I decided to split the Player constants for "MaxBaseStatValue" & "MaxBattleStatValue" into those 4 types each. But: If I understood the disassembly correctly, the code which would be responsible for calculating skill effects uses a single constant location for all of these. So some battle-related calculations might differ in EasyRPG when these values are patched.
Inconsistent clamping of bounds
When applying a modifier to a base stat ("SetBaseAtk", etc.. in EasyRPG code), the resulting value is clamped between -999 & 999. EasyRPG´s code uses a single value to represent both the lower & upper bounds of this operation. But it might be possible for patches (And I verified this for the WhiteDragon patch) to omit patching one of these values. WhiteDragon only patches the positive values, so setting a new Atk value would actually clamp the modifier value between -999 & 9999 instead in RPG_RT. I chose not to extract the negative values individually, to make the result more correct. But there might be edge cases where calculations would differ from RPG_RT. :/
Max Enemy HP/SP just editor limitations?
I was looking for these values in my disassembly but wasn't able to find a candidate. Maybe these upper bounds should be removed from the Player code?
Rm2k3: CommandChangeClass actually uses the effective "Battle" stats instead of "Base" stats?
This might be one of countless bugs related to the Rm2k3 battle system. It looks like whenever a class is changed in RPG_RT, a constant of '9999' is used for clamping, instead of the proper '999' which would be used for base stats. I could be wrong, because I barely have any documentation on this mangled code, and the values used here might actually be the right ones (so that only the wrong upper bound is used here). Can anyone with more knowledge on the inner workings of the battle system verify that?
This work could be also useful for some kind of patch detection tool inside the Tools repo
This work could be also useful for some kind of patch detection tool inside the Tools repo
Yup, I also thought about that. It would be nice to have a tool that extracts all the information about RPG_RT/build-related customizations + packages them into its own file format that can be used by the web player. (Which would otherwise be unable to use the ExeReader)
oops, the last commit is missing some changes for liblcf. Here's a "quick patch" (get it?):
---
src/inireader.cpp | 17 +++++++++++++++++
src/lcf/inireader.h | 2 ++
2 files changed, 19 insertions(+)
diff --git a/src/inireader.cpp b/src/inireader.cpp
index 6222ae2..679559d 100644
--- a/src/inireader.cpp
+++ b/src/inireader.cpp
@@ -151,6 +151,23 @@ bool INIReader::HasValue(const std::string& section, const std::string& name) co
return _values.count(key);
}
+
+std::map<std::string, std::string> INIReader::GetSection(const std::string& section) const {
+ std::map<std::string, std::string> result;
+
+ std::string section_lc = section;
+ std::transform(section_lc.begin(), section_lc.end(), section_lc.begin(), ::tolower);
+
+ for (auto it = _values.begin(); it != _values.end(); ++it) {
+ auto& key = it->first;
+ if (key.size() > section_lc.size() + 2 && key.rfind(section_lc, 0) == 0 && key[section_lc.size()] == '=') {
+ auto name = key.substr(section_lc.size() + 1);
+ result[name] = it->second;
+ }
+ }
+ return result;
+}
+
std::string INIReader::MakeKey(const std::string& section, const std::string& name)
{
std::string key = section + "=" + name;
diff --git a/src/lcf/inireader.h b/src/lcf/inireader.h
index e3d62a8..980b278 100644
--- a/src/lcf/inireader.h
+++ b/src/lcf/inireader.h
@@ -81,6 +81,8 @@ public:
// Return true if a value exists with the given section and field names.
bool HasValue(const std::string& section, const std::string& name) const;
+ std::map<std::string, std::string> GetSection(const std::string& section) const;
+
private:
int _error;
std::map<std::string, std::string> _values;
--
2.47.1.windows.2
If you want you can PR the liblcf part. Could be also useful for other applications.