sqwishy
Chieftain
- Joined
- Jul 27, 2014
- Messages
- 51
(This is still a WIP and not well understood or refined. I advise against mod authors trying to use this. I'm just sharing what I know so far in case it helps others make progress toward discovering a better approach. Pinging @f1rpo because you at least might be interested in this.)
It seem like couple weeks there's a post in the RI thread about how narrow the bottom left help panel is that shows city information or unit information of whatever plot is being hovered.

I spent some time trying to debug the game to figure what width it uses. It took me a while to realize to find setHelpTextArea() in the Python API
... Apparently the width passed as a parameter to that function is not used.
I was hoping to find an option in the theme files that would control this or a constant in a data section or executable section of the exe that would cleanly control this. But all I found was a couple values in allocations that seem to set a maximum width when the program is wrapping the help text I think? You can modify the values to modify the width, but it's relocated when loading a game. Shown below is a pointer chain. I don't have a lot of confidence with this approach since I don't really understand scaleform or what is being stepped through and it doesn't map on to the program's code or memory access at all; it just happens to work on my copy of vanilla and on Realism Invictus on my computer. But the pointer chain was kind of a proof of concept and maybe something to help debug further.
PlotPinScale is a small library I made earlier for writing to game memory from Python, because ctypes isn't in Python 2.4. You can find a copy of it attached to this post, source and dll included.
Since the address written to is in allocated memory, it only exists some time after a game is loaded into. Many places in the initialization of RI's CvMainInterface are too early and will result in the pointer chain not resolving to a valid memory address. I ended up putting it in setFieldofView and that was late enough.
(I don't recommend this approach, this was just a proof of concept. I was trying to test this on a save for the screenshots, but it was from a multiplayer game and I couldn't figure out how to load the save in single player or enable the console in multiplayer
)
Another drawback of the pointer chain is this only works on a pre-steam BTS executable. If you buy the game on steam, there is a beta branch that installs this older version. The default steam version is a release of BTS that uses steam for multiplayer and doesn't allow direct IP in the multiplayer menu. Since it's a slightly different build, the pointer chain is different and the code above does not work.
In the exe, I've noticed this code that is run when setHelpTextArea is called.
There is a multiplication. On Vanilla, I think eax is always 9 after that call, but I suspect it's connected to the font size in the theme files somehow as I've seen it be higher in mods where the themes use larger font sizes. ebp points to somewhere in the stack, maybe a stack allocated structure, the value at ebp+20 is 1d (dword ptr ss:[dword ptr ss:[ebp+20]]=[0019F5F8]=1D). The result of the multiplication seems to be about how many pixels wide the text can be for that hud help widget. The `set_help_width()` Python function I posted above writes to the address in memory from which the value 1d is copied to the stack and later used in this multiplication. Writing to that address causes this function to multiply eax by the value passed to `set_help_width()` instead of the default value of 1d. But I don't know how that is initialized to 1d in the first place. That's something that might be worth finding out.
One thing that might make this a bit easier is if I had access to the debug symbols (pdb files) for CvGameCoreDLL.dll. It might not make a huge difference because lots of this code is in the exe but I think there are some references to data sections where symbols can provides names or labels for what those data are about. My guess is it's easy for mod authors to distribute symbols since they're already compiling the dll but I'm not sure. I haven't tried building it myself, I imagine it's a bit of a nightmare to get set up but I'm not too sure.
It seem like couple weeks there's a post in the RI thread about how narrow the bottom left help panel is that shows city information or unit information of whatever plot is being hovered.


I spent some time trying to debug the game to figure what width it uses. It took me a while to realize to find setHelpTextArea() in the Python API

I was hoping to find an option in the theme files that would control this or a constant in a data section or executable section of the exe that would cleanly control this. But all I found was a couple values in allocations that seem to set a maximum width when the program is wrapping the help text I think? You can modify the values to modify the width, but it's relocated when loading a game. Shown below is a pointer chain. I don't have a lot of confidence with this approach since I don't really understand scaleform or what is being stepped through and it doesn't map on to the program's code or memory access at all; it just happens to work on my copy of vanilla and on Realism Invictus on my computer. But the pointer chain was kind of a proof of concept and maybe something to help debug further.
Python:
import PlotPinScale
BASE = 0x400000 # base address of the .exe
def _help_width_ptr():
p = BASE
for offset in (0x0089206C, 0x58, 0x164, 0xC, 0x0):
p, = PlotPinScale.getValues(p + offset, 'P')
return p + 0x148
def get_help_width():
return PlotPinScale.getShort(_help_width_ptr())
def set_help_width(value):
return PlotPinScale.setShort(_help_width_ptr(), value)
PlotPinScale is a small library I made earlier for writing to game memory from Python, because ctypes isn't in Python 2.4. You can find a copy of it attached to this post, source and dll included.
Since the address written to is in allocated memory, it only exists some time after a game is loaded into. Many places in the initialization of RI's CvMainInterface are too early and will result in the pointer chain not resolving to a valid memory address. I ended up putting it in setFieldofView and that was late enough.
Diff:
def setFieldofView(self, screen, bDefault):
#if bDefault or not MainOpt.isShowFieldOfView():
if bDefault or (not MainOpt.isShowFieldOfView() and not MainOpt.isRememberFieldOfView()): # K-Mod
self._setFieldofView(screen, self.DEFAULT_FIELD_OF_VIEW)
else:
self._setFieldofView(screen, self.iField_View)
+
+ try:
+ import EpicMeme
+ EpicMeme.set_help_width(55)
+ except Exception, exc:
+ CyInterface().addImmediateMessage(u"%s" % exc, "")
(I don't recommend this approach, this was just a proof of concept. I was trying to test this on a save for the screenshots, but it was from a multiplayer game and I couldn't figure out how to load the save in single player or enable the console in multiplayer

Another drawback of the pointer chain is this only works on a pre-steam BTS executable. If you buy the game on steam, there is a beta branch that installs this older version. The default steam version is a release of BTS that uses steam for multiplayer and doesn't allow direct IP in the multiplayer menu. Since it's a slightly different build, the pointer chain is different and the code above does not work.
In the exe, I've noticed this code that is run when setHelpTextArea is called.
Code:
007D9A72 | FF90 30010000 | call dword ptr ds:[eax+130]
007D9A78 | 0FAF45 20 | imul eax, dword ptr ss:[ebp+20]
There is a multiplication. On Vanilla, I think eax is always 9 after that call, but I suspect it's connected to the font size in the theme files somehow as I've seen it be higher in mods where the themes use larger font sizes. ebp points to somewhere in the stack, maybe a stack allocated structure, the value at ebp+20 is 1d (dword ptr ss:[dword ptr ss:[ebp+20]]=[0019F5F8]=1D). The result of the multiplication seems to be about how many pixels wide the text can be for that hud help widget. The `set_help_width()` Python function I posted above writes to the address in memory from which the value 1d is copied to the stack and later used in this multiplication. Writing to that address causes this function to multiply eax by the value passed to `set_help_width()` instead of the default value of 1d. But I don't know how that is initialized to 1d in the first place. That's something that might be worth finding out.
One thing that might make this a bit easier is if I had access to the debug symbols (pdb files) for CvGameCoreDLL.dll. It might not make a huge difference because lots of this code is in the exe but I think there are some references to data sections where symbols can provides names or labels for what those data are about. My guess is it's easy for mod authors to distribute symbols since they're already compiling the dll but I'm not sure. I haven't tried building it myself, I imagine it's a bit of a nightmare to get set up but I'm not too sure.