Modding Civ I - Playing 1-to-1 or... alone !

darkpanda

Dark Prince
Joined
Oct 28, 2007
Messages
839
One more small hack to CIV.EXE, this time enabling to play against only 1 ... or even 0 opponent!

To make this possible, you need to edit the following 2 byte sequences in your CIV.EXE:

Rich (BB code):
Original:
0002F976h: 83 7E F6 02
0002F998h: B8 06 00 50 B8 02 00 50

Patched:
0002F976h: 83 7E F6 00
0002F998h: B8 06 00 50 B8 00 00 50

The offsets above are given for EN versions of Civ (474.01, 474.05 and 475.01).
For FR versions, the offsets are 30D76h and 30D98h.

Hereunder, what it looks like in Civ:



Notes:
- as you can see in the selection screen, I also hacked Civ to enable "8 Civilizations", but it doesn't seem to have any effect...
- when playing along (1 Civilization), I could build a city and some units without the game ending in victory...


Cheers!
 

Attachments

  • civ_794.png
    civ_794.png
    3.1 KB · Views: 942
  • civ_793.png
    civ_793.png
    3 KB · Views: 989
  • civ_795.png
    civ_795.png
    2.8 KB · Views: 935
Last edited:
Awesome! This is amazing. One question though, is there a way to be able to put 2 civilizations and then get to pick from the whole list? That would be really interesting.
 
Cool. I'm not sure if it's possible either, but it sounds like a great idea to be able to enable all civilizations no matter how many you choose. I'd like to be able to choose the Aztecs when on 2 civilizations...
 
Cool. I'm not sure if it's possible either, but it sounds like a great idea to be able to enable all civilizations no matter how many you choose. I'd like to be able to choose the Aztecs when on 2 civilizations...

I'm looking at this, having some progress, but nothing workable so far... I'll let you know if I can make it possible with some hack.
 
Thanks. It would be interesting to have all civilizations open to you, and the computer, that way you never know which civilization you will face off against...
 
Thanks. It would be interesting to have all civilizations open to you, and the computer, that way you never know which civilization you will face off against...

Since I'm working on it, I thought I'd write some kind of "blog" of what's going on, step by step...

1. First step is: hack CIV so that is shows all Civilizations to select from instead of limiting to the total number of Civs:

What CIV does

When showing the Civ selection dialog, CIV first lists all Civs from 1 (Roman) to levelOfCompetition+1 (included), then lists all Civ from 9 (Russian) to 8 + levelOfCompetition+1 (included).

Examples:
  • if levelOfCompetition is 2, meaning that there are 2 computer-controlled opponents,
    Civ will lists all civs from 1 to (2+1)=3 included and from 9 to 8+(2+1)=11 included,
    that is:
    Code:
                1 = Roman
                2 = Babylonian
                3 = German
                9 = Russian
               10 = Zulu
               11 = French
  • if levelOfCompetition is 5, meaning that there are 5 computer-controlled opponents,
    Civ will lists all civs from 1 to (5+1)=6 included and from 9 to 8+(5+1)=14 included,
    that is:
    Code:
                1 = Roman
                2 = Babylonian
                3 = German
                4 = Egyptians
                5 = Americans
                6 = Greeks
                9 = Russian
               10 = Zulu
               11 = French
               12 = Aztecs
               13 = Chinese
               14 = English

What we need to patch

We need to tell CIV to show all Civs, regardless of the value of "levelOfCompetition".
The actual code assembly for what I described above is a sequence of 2 loops, each loop looking like this
Code:
    mov    varLoop, 1              ; loop counter initialized to 1
loop_start:
    mov    ax, LevelOfCompetition  ; ax now equals levelOfCompetition
    inc    ax                      ; ax now equals levelOfCompetition+1
    inc    ax                      ; ax now equals levelOfCompetition+2
    cmp    varLoop, ax             ; CIV compares the varLoop value and "ax"
    jl     short <some location>   ; if varLoop is strictly below ax (<) it displays the Civ name (else breaks out of the loop)
some_location:
    .... code to display Civ name ...
    inc    varLoop                 ; varLoop incremented by 1 (varLoop++)
    <jump back to loop_start>

Written in C/C++ or Java, this is a typical for loop:
Code:
for(varLoop = 1; varLoop < levelOfCompetition+2; varLoop++) {
    // display name of Civ with ID 'varLoop'
}

So how can we alter it to ignore the level of competition when displaying selectable Civs ?

The obvious way is to change the loop condition to "varLoop < 8"; but doing in this in assembly is actually tricky because assembly instructions have variable byte-length... Let me explain:
  • the assembly "cmp varLoop, ax" is a comparison between a value in memory (varLoop is a local variable in the stack) and a register (ax); in x86 assembly this is written (hex) "39 46 F6";
  • however, "cmp varLoop, 8" is a comparison between a value in memory and a direct value, which is rather coded as (hex) "83 46 F6 08";
  • so the binary code for "cmp varLoop, 8" takes 4 bytes, which is bigger than the binary code for "cmp varLoop, ax" which only takes 3 bytes, so we cannot patch it this way...

The next best option would be to replace the initial "mov ax, levelOfCompetition" by hardcoding the value of ax at this stage, for example "mov ax, 6"; let's see the binary:
  • "mov ax, levelOfCompetition" is a value copy between memory and a register; in binary, it is written as (hex) "A1 14 E2"; without verifying, I am guessing that opcode "A1" is for instruction "mov memory value to AX", and "14 E2" is the address of the memory value to copy to ax... but that's really just a guess
  • then, "mov ax, 6" is a hard-coded value assignment to ax register; in binary it is written as (hex) "B8 06 00"; again, I am not verifying in the x86 reference, but I guess that B8 is the opcode for "write hard-coded short to ax"
  • bingo! both instructions have the same byte-length, and so we can now (attempt to) apply the patch


The actual patch in CIV.EXE:

As explained above, CIV has 2 similar, consecutive loops to display the available Civs, so we need to patch both loops.
The original code and locations for the 2 occurences "mov ax, levelOfCompetition" differ by version:
Code:
          EN 474.01: "[B]A1 14 E2[/B]" located at [B]0x2FB53[/B] and [B]0x2FB8D[/B]
          EN 474.05: "[B]A1 F4 D2[/B]" located at [B]0x2FB58[/B] and [B]0x2FB92[/B]
          FR 474.05: "[B]A1 34 D8[/B]" located at [B]0x30F58[/B] and [B]0x30F92[/B]

Now the idea is to replace those occurrences with "mov ax, 6", which is spelled "B8 06 00" in x86 binary.

After applying the patch, it is now possible to select any Civ in the selection screen, even with only 2 opponents, YAY!

But wait a minute there: I selected 4 Civs total (level of competition = 3), then I chose to be the Greeks, and I end up
being Shaka of the Zulus!!!

Indeed, there is still some work to do...

TO BE CONTINUED
 
I always wondered (and still do) why is there this limitation in the first place? Doesn't have the slightest clue, doesn't make the slightest sense to me... Word "bizarre" comes to mind...

Perhaps, just an implied way to force the player to try to play with more civs? I mean - US were the primary market, and to play as an American you have to play against at least 5 opponents. This reason doesn't seem that much probable, though. Really can't think of a better one...

Good luck darkpanda with remedying this weirdness!
 
Good luck darkpanda with remedying this weirdness!

Thanks Renergy, I'm quite certain I'll need a lot of it!

Pursuing the investigation, let's look at what going in CIV right after displaying the list of Civs (side note for those interested: I'm looking at CIV.EXE using IDA Free Pro v5):

Code:
  // right before this, the player's selected Civ is stored in register
  // 'ax', starting from 0 for the first Civ in the dialog box
00    mov     PlayerCivColor, ax      ; Store user choice in PlayerCivColor (global variable)
01    mov     ax, LevelOfCompetition  ; Move LevelOfCompetition in ax
02    cmp     PlayerCivColor, ax      ; Compare PlayerCivColor and ax (= LevelOfCompetition)
03    jg      short _3E8              ; If PlayerCivColor is strictly greater than ax, jump to _3E8
04    inc     PlayerCivColor          ; Increment PlayerCivColor by 1
05    jmp     short _3F5              ; Unconditional jump to _3F5
06 _3E8:
07    mov     ax, PlayerCivColor      ; Move PlayerCivColor in ax
08    sub     ax, LevelOfCompetition  ; Subtract LevelOfCompetition from ax (= PlayerCivColor)
09    add     ax, 8                   ; Add 8 to ax
0A    mov     PlayerCivColor, ax      ; Move back ax in PlayerCivColor
0B _3F5:

If I rewrite the assembly above in C/Java style code, this would look as below:
Code:
    int PlayerCivColor = getUserSelection();
    if ( PlayerCivColor > LevelOfCompetition ) {
        PlayerCivColor = 8 + (PlayerCivColor - LevelOfCompetition);
    } else {
        PlayerCivColor ++;
    }

In plain english, CIV assumes that if the Civ ID selected by the player is above "LevelOfCompetition", it just means that the player selected the Civ in the second half of the list, so it "normalizes it" to match the actual Civ ID.

Example:
  • If I selected to play with 3 civilizations, LevelOfCompetition is 2
  • As explained in my previous post, CIV proposes 6 Civs for selection: Roman, Babylonian, German, Russian, Zulu or French
  • The selection dialog uses the following list IDs to return the user selection:
    Code:
    0 = Roman
    1 = Babylonian
    2 = German
    3 = Russian
    4 = Zulu
    5 = French
  • If the selected ID is 0, 1 or 2, then ( PlayerCivColor > LevelOfCompetition ) is false, and PlayerCivColor is simply incremented by 1, ending up being 1, 2 or 3, the IDs that match Romans, Babylonians or Germans inside Civ logic
  • If the selected ID is 3, 4 or 5, then ( PlayerCivColor > LevelOfCompetition ) is true; in this case, PlayerCivColor is first shifted back by removing the first half of the list (subtracting LevelOfCompetition), then added 8 to match the IDs of the second set of Civs inside CIV logic; so selected ID 3, 4 or 5 end up being Civ IDs 9, 10 or 11

Now, when we started hacking the selection dialog in my previous post, we also started to mess up the remaining logic:
  • In my example before, I selected 4 total Civs, i.e. level of competition is 3
  • But the dialog box shows all 14 Civs because of the hack
  • So when I select the "Greeks", the dialog box returns ID 5, which corresponds to the 6th item in the list (the list starts at ID 0 for the first item)
  • But then CIV tries to normalize this value based on the logic above: list ID 5 *is* above level of competition 3, so PlayerCivColor ends up being 8 + (5 - 3) = 8 + 2 = 10, i.e. the Civ ID for the Zulus

How can we fix this ?

Here again, the solution is to hack the conditional check... But we also need to hack the computation occuring in case we are in the second half of the list:
  • like before, we change the value assigned to 'ax' before the comparison: "mov ax, LevelOfCompetition" become "mov ax, 6"
  • then we change the calculation of the real Civ ID to subtract '6' instead of the real 'LevelOfCompetition, i.e. "sub ax, LevelOfCompetition" becomes "sub ax, 6"...
  • Let's verify that we can do it:
    • "sub ax, LevelOfCompetition" translates to x86 binary as (hex) "2B 06 14 E2"
    • "sub ax, 6" translates to x86 binary as (hex) "2D 06 00"
    • here we have the problem opposite from before: the patched code is shorter than the original code... Fortunately, this is easier to deal with, by adding 'neutral' code, namely the NOP operation which does nothing... in x86 binary, its opcode is 0x90, so the final patched code is "90 2D 06 00"

The code and locations differ by versions, again:
Code:
    EN 47401: condition code "A1 14 E2" at 0x2FBD9; subtraction code "2B 06 14 E2" at 0x2FBEB
    EN 47405: [I]coming soon[/I]
    FR 47405: [I]coming soon[/I]


Let's patch this, and voila! I can now select the Aztecs and be called Montezuma, even with only 3 Civs playing !!

But wait a minute here: the game starts and I have actually no settler at all... I am left in complete darkness... ! Even worse, as "End of Turn" is turned off by default, the game is actually unwrapping turn after turn all by istelf, and I can't do anything about it... What's happening ? I'm playing a CIV game where my Civ does not exist !!!

Well, still more work to do for this to be playable, it seems... :)

TO BE CONTINUED
 
If the map worked during that, it would be interesting to watch the civilizations build up and destroy each other.
 
I always wondered (and still do) why is there this limitation in the first place? Doesn't have the slightest clue, doesn't make the slightest sense to me... Word "bizarre" comes to mind...

We'll never know the reason for this limitation... Further investigation unfortunately ends up with an issue very hard to circumvent.

CIV relies on a data byte that we could call ActiveCivs (also present in .SVE savegames) to control the initialization of Civs.

The initialization of this byte ActiveCivs is done with the following code, after the player selected the number of total Civs in the game:
Code:
  // right before the code below start, the
  // number of Civs (minus 1) selected by 
  // the player is stored in register 'ax'
    mov     LevelOfCompetition, ax         
    mov     ax, 0FFh
    mov     cl, 6
    sub     cl, byte ptr LevelOfCompetition
    sar     ax, cl
    mov     ActiveCivilizations, ax

Basically, the value of ActiveCivs is the byte value 0b11111111 (written in binary), right-shifted by (6 - LevelOfCompetition) increments:
  • If LevelOfCompetition is 2 (3 total Civs):
    • then 6 - LevelOfCompetition = 4
    • and ActiveCivs = 0b11111111 >> 4 = 0b00001111
    • so we see that ActiveCivs covers Barbarians (bit 0) and the 3 first Civs (white, green, blue) at bits 1, 2 and 3
  • If LevelOfCompetition is 5 (6 total Civs):
    • then 6 - LevelOfCompetition = 1
    • and ActiveCivs = 0b11111111 >> 1 = 0b01111111
    • so we see that ActiveCivs covers all Civs except the grays (bit 7)

So in our previous example, I elected to play with the Aztecs, eventhough the ActiveCivs bit for the yellows (bit 4) was 0, and thus my Civ was not initialized at all.

I tried a first hack where I simply removed the "right-shift" operation, which left the value of ActiveCivs to 0b11111111 whatever the LevelOfCompetition, but then all Civs are active and initialized regardless of the player's choice of number of opponents.

Implementing a randomization of Civs would require to actual code the randomization of ActiveCivs in assembly/binary, taking into account the player's selection of Civs, and finding room where to put this code in CIV.EXE... For the moment, this is definitely beyond my reach, I must admit......

But never say never ;)

TO BE CONTINUED
 
"Svaka &#269;ast" (which, loosely translated, means ":goodjob:") to you for yet another breakthrough, darkpanda!

...which now emboldens me to suggest putting an item from my 'wish' list onto your 'to do' list. I never play one of the set civs, but always hit ESC to input a name of my own. However, this means playing with a colour that's chosen for me by the game. I'd be very grateful if your investigations uncovered a way of choosing which civ was replaced by the ESC-created civ.

This, of course, is not high-priority. You're doing great work, and I have no desire to distract you from it.
 
I'd be very grateful if your investigations uncovered a way of choosing which civ was replaced by the ESC-created civ.

The CIV code that selects a Civ for you when you don't is nothing more than a "random" process...

Problem is: when you press ESC, the GUI routine displaying the list of Civs returns "-1", and so it is not possible to detect which Civ was highlighted when you pressed ESC... So interactively choosing your custom Civ, all the while keeping current Civ selection functionality seems quite complicated to achieve...

What is possible however, is to make CIV always ask you for the Tribe name, even when you don't press ESC... In this case, CIV will use the Civ you selected in the list, but still ask for the Tribe name, so you can control the color you want.

To do this, the patch is as follows:
Code:
  [B]EN 47401[/B]: replace bytes "[COLOR="Red"]75 24[/COLOR]" at offset [B]0x2FC97 [/B]with bytes "[B][COLOR="Blue"]90 90[/COLOR][/B]"
  [B]EN 47405[/B]: replace bytes "[COLOR="Red"]75 24[/COLOR]" at offset [B]0x2FC9C [/B]with bytes "[COLOR="Blue"][B]90 90[/B][/COLOR]"
  [B]FR 47405[/B]: replace bytes "[COLOR="Red"]75 24[/COLOR]" at offset [B]0x3109C [/B]with bytes "[COLOR="Blue"][B]90 90[/B][/COLOR]"

Don't worry about my priorities or To-Do list, they are very unstable, and if a patch is fun and simple enough, I don't mind investigating it ;)

Enjoy!

Note: for those interested, this patch is simply bypassing a conditional jump (overwriting the JNZ code with NOP NOP instructions) that checks whether the Civ was player-selected or not, and that normally pops up the custom Tribe name only if it was selected randomly by pressing ESC... Bypassing this jumps makes Civ always go to the custom Tribe name dialog even when the the Civ was selected manually by the player)
 
What is possible however, is to make CIV always ask you for the Tribe name, even when you don't press ESC... In this case, CIV will use the Civ you selected in the list, but still ask for the Tribe name, so you can control the color you want.

Brilliant! This is exactly what I was hoping for. Thanks a bunch!

BTW, the offset for EN 474.03 is 0x2F49C.
 
mod:
CIV1_PLAY_1TO1 - Play against just 1 computer Civ... Or even 0!
and mod:
CIV1_RERESPAWN - Opponent Civs can now respawn more than once

play against only 1 ... or even 0 opponent!

But what if someone would like to play against ALL civilizations at once (not only 7) on the big map without the possibility of their rebirth?
 
Last edited:
Top Bottom