Modifying plot help width

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.

wide-city.jpg wide-stack.jpg

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.

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.
 
It feels like yesterday – no, it was indeed only yesterday – that I let a player of my mod know that increasing font sizes further will lead to awkward line-wrapping in the help text area. Today I learn - tentatively - that increasing the font sizes does make the help text area wider. The effect is just so small that it's easy to miss. My own mod has 16 as the Size2_Normal in Civ4Theme_Common.thm. This is apparently the size used for help text. I'm seeing a value of 10 prior to the setHelpTextArea-related multiplication you mention toward the end of your post, i.e. as one of the factors of the multiplication. Here's how that looks in comparison with BUG, which uses the original Size2_Normal of 14 (resulting probably in a factor of 9 as you write):
Spoiler :
area-width-bug-vs-advciv.jpg
One might think that this difference could also be the result of the generally enlarged HUD in my mod, maybe of the help text area's dimensions after all or the decorations in the lower left corner. However, I can make most of the widgets larger by changing just a couple of parameters, and the factor I see remains 10 and the text area does not get noticeably wider. If I set Size2_Normal to 18, however, the factor becomes 12 – and the help text area actually quite noticeably wider, up to about the left edge of the first unit button in my screenshot above. For the Catapult text, the linebreaks then actually remain the same as in the screenshot, but I suspect that the greater width doesn't quite make up for the wider font in general because I recall having to shorten a bunch of game texts after going from size 12 to 14 to make everything fit.

One nice feature of the VS 2010 IDE is that its debugger allows memory to be edited at runtime. I believe this is no longer possible in recent versions. So I was able to quickly just set the font size factor to 20, and indeed this greatly expanded the help text area akin to how it looks in your screenshots. Aside: Not all that easy to do either; I ended up putting a MOV EAX 20 instruction (and two NOPs) at the end of the function that gets called before the IMUL instruction. Gotta say that the AI assistant has been helpful with this after I fed it some lines from the instruction set reference. The last time I've modified Civ's binary code, I had pieced the code bytes together myself.

This function with the multiplication is apparently not specific to the help text area. Already gets called on the Custom Game screen and, in that case, uses a factor that corrsponds to Size3_Normal, which is apparently the size used by that screen. So my hack would probably have strange side-effects in various places. I've already noticed a bigger box for the loading screen hint. I guess I could try to track what happens with the result of that multiplication as the CPU moves back into code specific to the help text area. Though your approach also seems usable. Maybe really preferable to messing with the program's text section. Hm, still, following that multiplication result seems like it could be helpful and perhaps not that difficult. I think I'll give it a try.

The lack of Steam compatibility is regrettable, but, then, if (non-"Beta") Steam users don't get the wider text area, that's not so tragic either. Will just need to be aware which version of the EXE we're dealing with. On that note, I suppose your plot pin size changes also aren't (fully) Steam-compatible? Or have you somehow figured out the memory locations for Steam? I've gotten my hands on the Steam BtS executable, but, without a proper installation, I can't debug it and haven't been able to find what I was looking for. Even with Steam installed, I'm not sure how well debugging would work. According to Nightinggale, Steamless should make it possible ...

For the record, a cleaner alternative would be to reimplement the help text area widget in Python. Much of the work was already done by the BUG mod before the release of the DLL source. The PLE component has an "info pane" that looks much like the help text area: PLE.displayInfoPane
I think this could be integrated with the DLL by having the DLL return empty strings to the EXE when asked for help text and by, before returning, calling a Python function that will place the text in the info pane. But I think hacking the EXE will be less work, and the PLE widget might turn out to have glitches that can't be fixed.

Lastly, on a Windows system, I don't think setting up an environment for compiling the DLL is very challenging. Leoreth's guide provides a makefile, VS project files, the 2010 IDE (unclear if this is needed in addition to some more recent version; personally, I've been fine with using only the 2010 version) and the two required Microsoft SDKs (MSVC Toolkit, Win SDK). The statically linked Boost and Python libraries are already in the CvGameCoreDLL folder that normally comes with installations of BtS. Edit: And, yes, this setup should generate a .pdb file. I don't have one right at hand for the original (unmodified) source code though.

Edit2: I've tracked what I thought was the result of the multiplication (290), but I may have gotten that mixed up. Perhaps 238 was the value I should've chased; well, that one has been kept together with the 290, so this may not yet be a failure. Eventually, some four stack frames above the multiplication, this instruction ...
00559E51 03 F9 add edi,ecx
... adds 8 from ECX (of unknown origin) to the 290 in EDI. If I edit that to use EDX instead, where I see the value 80 (code bytes then 03 D2), I get a bigger bottom margin in the help text area. That's ... at least something that seems specific to that widget. Not spotting any other effects. Screenshot:
Spoiler :
help-area-with-margin.jpg
 
Last edited:
This function with the multiplication is apparently not specific to the help text area. Already gets called on the Custom Game screen and, in that case, uses a factor that corrsponds to Size3_Normal, which is apparently the size used by that screen.
Yes I think the addresses I mentioned might be used for text wrapping in general.
a cleaner alternative would be to reimplement the help text area widget in Python
I hadn't considered that. The buttons at the bottom of a selected unit group, like auto explore, benefit from changing this code path. But I'm not sure it's easy to substitute the widget in that case. If I mouse over the "Explore (Automated)" button, CyInterface().getHelpString() evaluates to help about the plot underneath the button. So I'm not sure how much access Python has to that and if it can substitute those as well. Mind you, those tooltips are short enough that they really don't need to be widened, it's really more about the city and unit stack mouseover.

So after a bit of sleep I went looking for immediate values for `1d` and found one in a function I had skimmed through earlier that is entered when setHelpTextArea() is called in Python. The function start is at 559780 in my exe (the beta branch on Steam). These are the notes I left in it at different points.

Code:
005597D0 | DB9E 7C030000            | fistp   dword ptr ds:[esi+37C]                              | left
...
005597F2 | DB9E 80030000            | fistp   dword ptr ds:[esi+380]                              | bottom?
...
00559819 | DB9E 88030000            | fistp   dword ptr ds:[esi+388]                              | stores the width, but isn't used i guess

Those notes are from the other day where I noticed the parameters to setHelpTextArea() are stored. Today I noticed this line in that function where 1d is pushed.

Code:
00559A91 | 6A 1D                    | push    1D                                                  |

It seems modifying that machine code and reloading a save game increases the with of that help panel! That line (00559A91) is inside a branch that seems to do with initialization. It's not reached on every call of setHelpTextArea(), but it is executed while loading a saved game. So if I change the instruction from push 1d to push 7f, and reload a saved game, the panel is very wide 🙂
verywode.jpg

Since this is just some instructions in executable memory, it seems easy to patch this at runtime. It's probably a good idea to use a pattern to match the instructions to be modified and those nearby as the exact address of the instructions to be modified might differ between executables like retail vs steam. I think a similar thing has been done with the size of the plot picker pins. And this looks like a similar situation.
 
For the record, a cleaner alternative would be to reimplement the help text area widget in Python.
@Toffer90 Did that in Caveman2Cosmos, GitHub.

But hacking the exe is probably less work.

It's probably a good idea to use a pattern to match the instructions to be modified and those nearby as the exact address of the instructions to be modified might differ between executables like retail vs steam.
Is it still necessary to remove the Steam DRM from the exe to be able to attach a debugger?
Might be interesting to see if the DRM free GoG version is different from retail.
 
Last edited:
Is it still necessary to remove the Steam DRM from the exe to be able to attach a debugger?
Yes, if I recall correctly, I had trouble with this early on and f1rpo or someone suggested using Steamless to make a copy of the exe to fix that issue. And that's what I've been using and it works fine. The addresses in the original exe and the steam exe are the same. I'm pretty sure it kind of has to be for ABI compatibility or whatever.
 
I've implemented a setWidth function based on changing that 1D operand you found. And then ended up putting the width at pretty much its old value because most help text just isn't that wide; it's been phrased with the original limit in mind. Still nice to have the possibility, and I've set the width in such a way that it should get a little wider on bigger screens.

Making the setWidth call just before setHelpTextArea seems to work well. I'm suspecting that it's really (only) that function that reaches the push 1D instruction; and only on its first call after some graphical (re-)initialization, i.e. only once upon launching/ loading a game. To calculate the replacement operand for the push from the target width, I assume that the EXE calculates the width really simply as that operand times the font size factor. Maybe that factor represents the average per-character width rather than the height. The formula could be the font size from the theme (normally 14) divided by 1.5 rounding down. That would be 9 for size 14, 10 for 16, 12 for 18 – consistent with my observations. This would actually be a proportional relationship between font height increases and the additional alotted width – which, intuitively, should suffice to avoid extra line wrapping when the font size is increased. Anyway, if the original width is 29*10, say, and we want it to be 350, then multiplying the operand by the ratio of the two will do the job. That would yield 35 in the example. And, with that, I do get about 350 pixels when measuring in a screenshot.

Still to be done is a search for the context of the push 1D for more robustness. There are a few dozen occurrences of those two code bytes in the EXE, but none close to the one that we're working with. I guess that's another sign that really only that one instruction is relevant here.
It's probably a good idea to use a pattern to match the instructions to be modified and those nearby as the exact address of the instructions to be modified might differ between executables like retail vs steam. I think a similar thing has been done with the size of the plot picker pins. And this looks like a similar situation.
For the plot pins, I did write code that looks for a pattern and computes an offset if it's found at an unexpected address, but that fails for the non-beta Steam EXE (and all other versions seem to have the same addresses anyway).
The addresses in the original exe and the steam exe are the same. I'm pretty sure it kind of has to be for ABI compatibility or whatever.
The Steam beta and DVD/GoG – yes, apparently. At least those editions haven't been causing trouble. I've also heard from a user with a Russian version: same EXE apart from a missing digital signature (VeriSign), and that thing is only an addendum that doesn't affect relevant addresses. The default Steam EXE, however ... I can't even locate the push 1D in there. There are plenty of hits for 6A 1D, but not in a trivially similar context. The next instruction after the push should be a relative call (opcode E8). But the pattern 6A 1D E8 does not exist in the Steam EXE. Nor is the absolute call two instructions before to be found. I bet the registers are also mixed up a lot. This is assuming that I've been sent the correct EXE (but, then, what else would it be); Civ-typical string data is present. Would really need to attach a debugger to find the same spots in the Steam EXE.
Yes, if I recall correctly, I had trouble with this early on and f1rpo or someone suggested using Steamless to make a copy of the exe to fix that issue. And that's what I've been using and it works fine.
I keep forgetting about Steamless, so it must've been @Nightinggale. Time to ping him anyway. The hacks of the binary may not be interesting to him, but perhaps the Python-based alternative:
For the record, a cleaner alternative would be to reimplement the help text area widget in Python.
@Toffer90 Did that in Caveman2Cosmos, GitHub.

But hacking the exe is probably less work.
I see in PythonToolTip that the formatting tags like <color> are getting expanded. I hadn't thought of that. Kind of a pity not to reuse Toffer's thorough-looking work, but, well, I can't say that I have an appetite for porting a lot of Python code. Edit: Here's the corresponding "We the People" Git Issue. Closed at the time for outrageous effort; maybe more reasonable with Toffer's code available.
 
Last edited:
But the pattern 6A 1D E8 does not exist in the Steam EXE.

Yeah, I the steam exe looks weird. I guess is it's some kind of obfuscation to deter cheaters. The steamless exe looks normal and I can find the instruction here:

Code:
0055AD51 | 6A 1D                    | push    1D                                                  |

Then I can run the steam exe (without steamless) and modify the byte at 55ad52 with the PlotPinScale python module to replace 1d with a different value and see the panel width change accordingly.

wide-steam.jpg

(ignore the big silly icons)

Maybe that factor represents the average per-character width rather than the height.

Yes I suspect the value 1d is meant to specify the width in characters or "em" units or something. So it's multiplied by whatever size that is for the font 2 style.

And then ended up putting the width at pretty much its old value because most help text just isn't that wide

Yeah it wouldn't surprise me if the default size is appropriate for most cases. It's really mainly when you hover over a big city or a stack of units with lots of promotions or long names and the panel either the amount of wrapping makes it harder to read or the panel is too high for the display and text clips off screen entirely.
 
Yeah, I the steam exe looks weird. I guess is it's some kind of obfuscation to deter cheaters. The steamless exe looks normal and I can find the instruction here:
Ah, that makes sense. So, at an offset of 160 4800 bytes in this case. And this obfuscation layer apparently gets removed upon loading the EXE. So the approach of looking for a pattern at runtime to calculate the offset should actually work; the pattern I have for the plot pins might just be a little too specific. Edit: 0x55AD51 minus 0x559A91 is 4800 (decimal), not 160. Absolutely can't use AI for such calculations.
 
Last edited:
Now this looks interesting. Not much to say about the help window as I haven't looked into it yet apart from reading this thread.

I keep forgetting about Steamless, so it must've been @Nightinggale.
Yeah I recommend it from time to time. Personally I use the GOG version through. Works out of the box.

Is it still necessary to remove the Steam DRM from the exe to be able to attach a debugger?
Might be interesting to see if the DRM free GoG version is different from retail.
I ran md5 on the colonization exe files from CD, DVD, GOG and steam. The first 3 are identical if you apply the official patch to the CD version. Steam has a different md5 and the steamless version has yet another one. I haven't checked BTS, but I will expect similar results.

For the plot pins, I did write code that looks for a pattern and computes an offset if it's found at an unexpected address, but that fails for the non-beta Steam EXE (and all other versions seem to have the same addresses anyway).
WTP has the solution to just not support the non-beta. I added a test at startup, which can be seen here. It would obviously need to be modified to match BTS, but the concept is fairly simple and it avoids a lot of headache.

Yeah, I the steam exe looks weird. I guess is it's some kind of obfuscation to deter cheaters.
I don't know how they do it, but I have seen an encryption, which has a key, which is just a byte array (essentially just a string). Reading a byte from the file then requires XOR with [index modulo length of key] to get the real data. Files aren't loaded until needed, so you can't easily extract them from memory. There are plenty of ways to keep cheaters (or asset thieves) out and even the primitive ones will keep 99% out.

Having said that, I think the steam exe encryption is pointless in this case. I mean if the game is released on steam today and it's the only version, sure, but cheaters will just get the disc or GOG version and defeat the system that way.

Also steam intentionally crashes games if a debugger is detected. This too is to protect against cheaters. Again doesn't really work if people can just use the game from a non-steam source.

One nice feature of the VS 2010 IDE is that its debugger allows memory to be edited at runtime. I believe this is no longer possible in recent versions.
Works fine in 2017 and I imagine it works in later versions too. It makes debugging/testing much easier, so there will likely be a major backlash if that feature is removed.
 
In the GoG exe the memory location is different, changing the value at 0x00559A92 changes the width.
Screenshot 2024-12-18 131020.png

The value 0x1d is a parameter used in a call to a setter Function.
C:
void __thiscall FUN_007bc5d3(int param_1_00,int param_1)
{
  if (*(int *)(param_1_00 + 0x148) != param_1) {
    *(int *)(param_1_00 + 0x148) = param_1;
    FUN_00795f49(1);
  }
  return;
}
 
Last edited:
So the same as sqwishy initially reported:
Today I noticed this line in that function where 1d is pushed.
Code:
00559A91 | 6A 1D                    | push    1D                                                  |
But not the same as the default Steam version:
The steamless exe looks normal and I can find the instruction here:
Code:
0055AD51 | 6A 1D                    | push    1D                                                  |

Having, at last, downloaded Steamless, I finally get that it simply removes that "SteamStub" protection layer. So I don't need to have Steam installed, I just need the protected EXE (which I have) and Steamless, and then I can – not debug the Steamless EXE (it's still reliant on the Steamworks API), but unprotect it ("unpack" it) and look for the relevant code addresses or check which search patterns will work. Let's see, the address differences between the EXEs I get for the four plot pin code changes are 1616, 704 and two times 544. So there is not going to be any universal offset to employ, and pattern matching can only be useful in close proximity to the change locations. For the plot pins, the four specific instructions to be changed exist only in one location each (in both EXEs), so this is a quite benign example.

WTP has the solution to just not support the non-beta. I added a test at startup, which can be seen here. It would obviously need to be modified to match BTS, but the concept is fairly simple and it avoids a lot of headache.
The 0x48 byte (vs. 0x40) was also the first difference I noticed between the (protected) Steam and the disc EXE for BtS. And I guess you check a few more bytes to be on the safe side. Though the Steamless version of BtS actually only has the 0x48 in common with the protected version. Not sure if your detection will work on the Steamless version of Civ4Col. It's really four different Steam versions that we might encounter on disk – if I get that right: Protected default, protected beta, Steamless default and Steamless beta. I.e. I take it that the beta version also has the protection layer – which is why Steamless is always needed for debugging. Edit: The Steam Civ4BeyondSword_PitBoss.exe that I have apparently has no protection layer. But that EXE and the disc Pitboss EXE each have their own code addresses too.
One nice feature of the VS 2010 IDE is that its debugger allows memory to be edited at runtime. I believe this is no longer possible in recent versions.
Works fine in 2017 and I imagine it works in later versions too. It makes debugging/testing much easier, so there will likely be a major backlash if that feature is removed.
Hm ... something I read somewhere. After a bit of searching, I think it may have been this StackOverflow question saying that the Memory window in VS 2017 doesn't allow edits, is essentially only a (raw) memory viewer. I understand that there is Edit & Continue too, which works at the source code level.
 
Last edited:
I've been looking for the font sizes loaded from Civ4Theme_Common.thm in the binary. I was hoping to be able to control font sizes from the BUG menu in this way and possibly to adjust them automatically to the screen dimensions. I did not find a font size near that (suspected) font width value, but introducing deliberate errors into the thm file got me quite close to code that handles the font sizes:
Spoiler :
spotted.jpg

Changing one of the GFC_FONT_... strings in the thm file to something arbitrary got me a crash at the breakpoint in the screenshot. The instruction at the top loads a font size into EAX for some floating point processing. At the time of the crash, a float representation of the original font size is still present at EBP-0xD8 (a local variable, I suppose). That's 37.f – a font size I had set in the thm file for easier spotting.
I wanted to trace the font size operand to some stable memory location that the DLL could access through a pointer chain like the one this thread has started with. Ultimately, it turned out that the EXE (maybe via the statically linked Scaleform GFC - GUI Foundation Class - library) lets gdi32.dll manage the font data loaded from the thm file, likely as a "logical font":
Spoiler :
wingdi.jpg

EAX points to -37, the inverse of the font size in the thm file. I suspect that it's actually a pointer to a LOGFONT struct. A negative height value means em height (as opposed to cell height). The function called is then likely CreateFontIndirectA. (I haven't found debug symbols for GDI.)
The GDI paradigm for dynamic font size changes appears to be quite heavy-handed. It seems that a new font would need to be created and explicitly linked to the device context(s) that the old font had been associated with. Finding handles to those device contexts in the EXE sounds like a tall order. Finding out where GDI stores font size data might be easier, but it's not clear how well GDI would tolerate changes to that data at random points in time (likely coming from a different thread).

I've decided to just settle for a hook that let's the DLL change the font size only during graphics initialization:
Spoiler :
hooked.jpg

I've found this nicely bulky MOV instruction near one of the numerous places where the font size gets loaded and stored:
00815D37 C7 05 60 3F C2 00 0F 00 00 00 mov dword ptr ds:[0C23F60h],0Fh
This is an instruction that the GameCore DLL can easily take over. So these 10 bytes are available for making the DLL call. The crucial copy of the font size is in EDI at that point, hence my MOV EDI EAX after the DLL call (whose return value goes into EAX). The DLL function reads EDI directly from the register through a line of inline asm because there's not enough space to push (and pop) EDI. (Need 5 byte for the call itself, 2 to move to EDI and 2 to preserve ECX.) The value in EDI is -20 here, i.e. it has already been inverted. This first time that my hook gets reached, the font size (20 – if it even is a font size) does actually not come from Civ4Theme_Common.thm – but all the subsequent calls do. This first one also does not seem to be forwarded to GDI. So I let my DLL function ignore the first call (i.e. just return the -20 unchanged). Well, I really haven't tested all this properly yet, also haven't looked into the Steam addresses. Seems clear enough though that this approach can work.
This means that changing the font size will require a restart, and it won't work through the BUG menu because that's not initialized when fonts get loaded. What even is the benefit then? I will no longer need to include thm files in my mod just to change the font sizes and, more importantly, will get rid of the "Failed to initialize the primary control theme" errors when players rename the mod folder or place it under "My Games". The DLL, when adjusting the font sizes, could also take into account the screen dimensions (though perhaps a bit difficult to determine this early on) and any font size edits that the player may have made through the (original) Civ4Theme_Common.thm in the BtS install folder. Moreover, a font size setting in GlobalDefines will still be easier to find for players than the thm file. (Not sure that I'm myself convinced by these reasons.)

A small nuisance is that the BtS version of Civ4Theme_Common.thm contains errors (already present in the base game) that are apparently harmless, but cause an error log ThemeParseLog.txt to be placed in the BtS install directory. Specifically, the BtS developers wanted to disable three unused font settings and failed to also comment out three corresponding style declarations:
Spoiler :
Code:
.Italic                                =    SF_CtrlTheme_Civ4_Control_Font_Size3_Italic;
.BoldItalic                            =    SF_CtrlTheme_Civ4_Control_Font_Size3_BoldItalic;
// ...
.FooterBold                            =    SF_CtrlTheme_Civ4_Control_Font_Size1_Bold;
Size1_Bold also gets used by Civ4Theme_Window.thm:
Code:
GFC_Control_ToolWindow_Style
{
    .Font_Title                                =    SF_CtrlTheme_Civ4_Control_Font_Size1_Bold;
    // ...
And, in another file, two semicolons are missing:
Code:
Error   : Syntax - ('Civ4Theme_HUD.thm', Ln:887, Col:13) Unexpected '.' in the identifier assignment statement
Error   : Syntax - ('Civ4Theme_HUD.thm', Ln:927, Col:13) Unexpected '.' in the identifier assignment statement
Making overriding copies of the thm files had the benefit of allowing those errors to be corrected. And a well-meaning player might correct them in the BtS file by uncommenting the disused fonts – and then it will be difficult to tell in the DLL which call refers to which GFont definition in the thm file. Maybe best to just go by the font size value received, e.g. take the square root of that and then times a constant factor (or one dependent on the screen dimensions) to increase small font sizes more substantially than large ones – and not to worry whether the received font sizes may already be the result of alterations to the BtS files.

Edit: AdvCiv Git commit
 
Last edited:
Very interesting to read, everyone. Thanks for making me aware of Steamless, it provided me with an option I can offer to people whose connection is incompatible with Gamespy lobby and want to run complicated mods that Steam may ruin. It messes up Python components somehow, possibly more:
Spoiler :
1736080791334.png
On third message below there's an EOF Error, if it matter.
Original History Rewritten DLL is somehow fine, yet one I recompiled in VS2010 (which relies on .NET 4.0 and not newer) also breaks when run through Steam library (or invites):
Spoiler :
1736081966170.png
Failing at Line 1 should tell of something catastrophic, right?
Sorry for offtopic, but... any clues how to reconcile such mods with Steam? Or to access Direct IP if it's hidden and not actually cut from Steam version? Or to transplant newer NAT negotiation to Original Release? (would be nice if it's on some domain like civ4bts.natneg1/2/3.gamespy.com, but I couldn't find anything like that with a superficial sweep) RI fails on Windows 7, but not Windows 10. HR fails on both. It wouldn't matter if people could use Direct IP with Steam's NATNeg, but dissecting .exe is way over heads of everyone I know. I'll move somewhere else if I should.
 
Original History Rewritten DLL is somehow fine, yet one I recompiled in VS2010 (which relies on .NET 4.0 and not newer) also breaks when run through Steam library (or invites):
You need to use VS2003 because that's the compiler, which was used with the exe. More info on how to compile here: https://forums.civfanatics.com/threads/the-easiest-way-to-compile-a-new-dll.608137/

If you want to know why, then read Mixing Multiple Visual Studio Versions in a Program is Evil. As I understand it, it has become possible to mix, but only if all versions involved are 2015 or newer.

Sorry for offtopic, but... any clues how to reconcile such mods with Steam? Or to access Direct IP if it's hidden and not actually cut from Steam version? Or to transplant newer NAT negotiation to Original Release?
Read this. It's written for Civ4colo, but it applies to BTS too.

As for finding each other online without gamespy, most people use hamachi, which is free for up to 5 players. You can also set up NAT to open ports, but each participating computer will contact all computers already in the game, so everybody will have to open ports (that's a mess). Hamachi avoids having to open ports or dealing with router settings.
 
Back
Top Bottom