It's slightly more complicated. Allow me to digress. Excessively.
Example C++ CodeHere's how that was done from the Hooville template level source code:
// Set map lighting conditions according to Uses Day/Night checkbox
// Force daylight everywhere if they don't want to use day/night
TethysGame::SetDaylightEverywhere(TethysGame::UsesDayNight() == 0);
// Now for some fun...: if day/night is enabled, force night everywhere all the time =)
TethysGame::SetDaylightMoves(false); // Freeze position of day/night
GameMap::SetInitialLightLevel(-32); // Set daylight off the map, to feeze it in darkness
You must enable day/night or it will always be daytime everywhere:
"SetDaylightEverywhere(true)"
Note: In Hooville, it's either always daytime everywhere, or it's always nighttime everywhere, depending on the checkbox to enable day/night in the multiplayer pre-game setup window. Hence the check for "UsesDayNight() == 0" in the above code.
Now with day/night enabled, there is going to be a rotating strip of daylight. What you can do is disable it from moving, and try to move the daylight position off the map:
"SetDaylightMoves(false)"
"SetInitialLightLevel(-32)" <= This value is dependent on the map size
The light level, if a remember correctly, is a value that increments as daylight moves across the map, and eventually loops back to some earlier value. It didn't appear to be a light level so much as a light position.
Quick note about boolean values:
Boolean (true/false) values are typically mapped as:
true = 1
false = 0
Aside:
Boolean Values vs. Integer-"Boolean" Values(This shouldn't affect you, but you should be aware of it)
As boolean values are stored in registers with much greater range, the assembly code is typically structured so that any non-zero value is considered true. However, you can't always rely on this. Treating any non-zero value as true sometimes requires more complicated code sequences. If the variables were actually declared as an integer type, the compiler won't emit these more complicated code sequences. This restricts how you can use and test integer-"boolean" values. In particular, it restricts how you test if a variable is true, since true is multivalued in such cases. Consider what might happen if a programmer wrote a test "if (booleanVariable == true)". If the variable was actually declared as an int, and held the value 2, this test would result in false, even though it should logically be true. A better test might simply have been "if (booleanVariable)". Another alternative could have been "if (booleanVariable != false)". Testing against false will be safe, since it is single valued (it is always 0). This is important because...
If you look in the TethysGame.h header file you'll see:
static void __fastcall SetDaylightEverywhere(int bOn);
static void __fastcall SetDaylightMoves(int bOn);
Note that parameters are declared as "int", not as type "bool". Hence, you must be careful about how true checks are done on these parameters. You should consider this for tests done by both your own code, and code in the Outpost2 functions you might be calling. At least, assuming you do any checks in your own code, or pass any values other than 0 or 1. (Which you just might find convenient here, when it comes to selecting opcodes and have limited space to work in).
Calling conventionsThere are three main calling conventions used by the MSVC compiler. The main difference between them is how many values can be passed in registers (the remaining parameters are passed on the stack). The number of registers used for parameter passing and the calling convention names are:
0 __cdecl // Default for C functions
1 __stdcall // Default for C++ class member functions
2 __fastcall // Occasionally declared by programmer, typically for small simple functions
The registers used for parameter passing are ECX (first eligible parameter), and EDX (second eligible parameter). As a general rule, any variable that needs to have a memory address is not eligible to be passed in a register. Typically objects (and struct instances, which are basically equivalent) can not be passed in registers as all their member functions (which aren't static), take a hidden "this" pointer. As pointers can only point to memory, and not registers, these functions would only be callable if the object has a memory address, and hence can not be stored in a register. Note however it is perfectly acceptable to pass an object pointer in a register. (In that case, the object it points to must clearly have a memory address, which presents no problems to calling the object's member functions). Any parameter which is not passed in a register is passed on the stack, in reverse order (last parameter is pushed first).
Example: TethysGame::AddMessage
static void __fastcall AddMessage(Unit owner, char *message, int toPlayerNum, int soundIndex); // Note: toPlayerNum: -1 = PlayerAll
Although this method belongs to TethysGame, it is static (hence no hidden "this" pointer to pass). This method is also __fastcall, so ECX and EDX will be used for parameter passing. The first parameter is a Unit object (not a pointer or reference to one), and so is not eligible to be passed in a register. Hence, "message" will be passed in ECX, and "toPlayerNum" will be passed in EDX. The remaining parameter will be pushed onto the stack in reverse order. The last parameter "soundIndex" will be pushed onto the stack first, followed by the first parameter "owner".
Example: Unit::SetDamage
void SetDamage(int damage);
This method is a class member function, which is not static, and hence has a hidden "this" pointer as it's first argument. Translating this C++ declaration to an equivalent C statement would look something like:
Unit_SetDamage(Unit* this, int damage);
As this is a member function, it's default calling convention is __stdcall, so the first eligible parameter will be passed in ECX. Here the first parameter is a
pointer to an object (and not just an object), and so can be passed in a register. Hence, "this" is passed in ECX, and "damage" is passed on the stack.
Translating Calling Conventions to AssemblyNow, getting back to the functions of interest:
static void __fastcall SetDaylightEverywhere(int bOn);
static void __fastcall SetDaylightMoves(int bOn);
These belong to TethysGame, but are both static (no hidden "this" pointer), and both __fastcall, so up to two registers will be used to pass parameters. There is however only one parameter for each of these functions, so for both of them, the first parameter "bOn" is passed in ECX.
Now, on to the assembly code you've posted:
110012CD E8 4E5C0000 CALL cps2.11006F20
110012D2 B9 01000000 MOV ECX,1
110012D7 FF15 6CE40011 CALL DWORD PTR DS:[<&Outpost2.?SetDaylightMoves@TethysGame@@SIXH@Z>]
110012DD 33C9 XOR ECX,ECX
110012DF FF15 70E40011 CALL DWORD PTR DS:[<&Outpost2.?SetDaylightEverywhere@TethysGame@@SIXH@Z>]
110012E5 33C9 XOR ECX,ECX
110012E7 FF15 74E40011 CALL DWORD PTR DS:[<&Outpost2.?SetCheatFastUnits@TethysGame@@SIXH@Z>]
110012ED 33C9 XOR ECX,ECX
Here you can see ECX is being set before each call. This is the value of the first parameter "bOn". Before the call to SetDaylightMoves, we see "MOV ECX,1", which sets bOn = 1 (true). Before the call to SetDaylightEverywhere, we see "XOR ECX, ECX", which sets bOn = 0 (false). But why not use "MOV ECX,0" here?
Using XOR of a register with itself has long been considered a clever way of setting it's value to 0. If you look to the left of each instruction above, you'll see the instruction encoding. For "MOV ECX,1" the encoding is "B9 01000000", which takes 5 bytes (2 hex digits per byte). If you look to the left of the "XOR ECX,ECX" instruction, you'll see it's encoding is "33C9", which is only 2 bytes. Had "MOV ECX, 0" been used instead, this instruction would also have 5 bytes. (Details below if you're interested). Hence, using XOR produces a smaller instruction. On early CPUs XOR was also supposedly faster. On newer CPUs, the relative speed of the two is a bit murkier of a situation. (A smaller instruction may mean a tight loop now fits in a cache line, but using the register as an input source on a pipelined or superscalar CPU may potentially cause delays if ECX was previously written to and it's new value has not yet been updated, provided the XOR instruction isn't automatically converted to something simpler internally).
Aside:
Bitwise-XORXOR
A | B | Result
--------------
0 | 0 | 0 *
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0 *
If A == B, then you have either the first or last row, and so the result is 0.
If a register is being XOR-ed with another value, then XOR is applied to each of the corresponding bits.
Setting all bits to 0, results in the register having the overall value 0.
Aside:
Instruction EncodingsThe instruction opcode "B9" represents an assignment of a 32-bit immediate value (encoded in the instruction stream) into the ECX register. Here, the immediate value is the little endian 32-bit quantity 1 = (01 00 00 00) = "01000000". The "B?" opcodes all represent "MOV register, immediate". The B0-B7 opcodes move an immediate 1 byte value into a register. The B8-BF opcodes move an immediate word or double word value into a register. The size of the immediate is determined by the operand-size attribute. In 32-bit operating mode the immediate constant will be 32-bits, unless the instruction is prefixed with "66" (operand size), in which case the immediate constant will be 16-bits. In 16-bit operating mode, things are reversed, so the immediate constant will be 16-bits, unless the instruction is prefixed with "66" (operand size), in which case it will be 32-bits. The determine which register is being written to, add the register index to the opcode ("B0" for byte moves, and "B8" for word or double word moves). Hence, B9 = B8 + 1, where 1 represents ECX.
The register encoding order is:
32-bit: EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
16-bit: AX, CX, DX, BX, SP, BP, SI, DI (same as 32-bit, but only the lower 16 bits are affected)
8-bit: AL, CL, DL, BL, AH, CH, DH, BH (same as first 4 16-bit registers, broken into low bytes, followed by high bytes).
The instruction "33" in Intel notation is "XOR Gv, Ev". This basically means "XOR register, memory_or_register", where the "G" stands for a "general purpose register", "E" stands for "general purpose register or memory address", and "v" means an operand size of either 16 or 32 bits depending on the operand-size attribute. A "b" (as opposed to a "v") would have been a byte size quantity. The "C9" is a "MOD R/M" byte that follows the opcode, and is used to specify what Registers or Memory address is used. (What "G" and "E" refer to). It is broken into a 2-bit field representing the address mode, and two 3-bit fields representing the registers involved: (address mode, "G" field, "E" field).
Address Mode (slightly simplified, as there is some irregularity):
00 [Register] ("E" field is a memory location, whose address is in a register)
01 [Register] + displacement8
10 [Register] + displacement16/32 (controlled by address-size attribute)
11 Register ("E" field is an immediate value in a register)
The instruction "XOR ECX, ECX" uses register addressing (11) and so the "MOD R/M" byte = (11, 001, 001) = 0b1100_1001 = 0xC9.
Why Instruction Encodings MatterNow, the instruction encoding matters mainly because of the variable length. Since you want to call "SetDaylightEverywhere(true)", you need to load ECX with the value 1 (true). Which is a problem when you examine the instruction lengths:
110012DD 33C9 XOR ECX,ECX
110012DF FF15 70E40011 CALL DWORD PTR DS:[<&Outpost2.?SetDaylightEverywhere@TethysGame@@SIXH@Z>]
If you replace "XOR ECX, ECX" with "MOV ECX, 1", the increased instruction size will overwrite the first few bytes of the following "CALL" instruction.
To make matters worse you also need to call "SetInitialLightLevel". If that call doesn't already exist, you may have to overwrite more code to insert it.
Gaining and Using SpaceNow, the good news. One of the official Sierra updates gutted the cheat functions, such as:
static void __fastcall SetCheatFastUnits(int bOn); // Useless
Essentially, these functions have been replaced with simple "RETN" stubs. As these functions no longer do anything, you're free to overwrite any calls to these functions without changing behavior. Hence, if you need to expand the size of previous instructions, you can gain a little bit of extra space by overwriting that last call. If you only overwrite part of the instruction, you can replace the remaining bytes with NOP (90) bytes. As NOP is a single byte instruction, it lets you easily pad code up to the next instruction boundary. I believe that's what you'll need to do here to make the necessary adjustments.
Another possibility is reusing existing values in registers. Technically, the C++ calling conventions assume the contents of the 4 registers (EAX, ECX, EDX, EBX) are all trashable during a function call. This means there is no guarantee they will have the same value after a function call as they did before the function call. This is why you see ECX set to zero multiple times, before each function call, in the code you posted. The compiler assumes ECX will be overwritten by the function, and so it's value is unknown after each function call. If you analyse the functions, you may determine that ECX, or another register is perhaps preserved, simply because the function had no need to overwrite it. That may allow you avoid setting it's value, or allow you to copy from another register, or increment/decrement a register to get the value you need. All these options should provide a shorter instruction than loading the register with an immediate constant. This is also where loading a ECX with a value other than 1 for true might come in handy. (Remember that true is multi-valued).
Procedure OverviewNow without knowing more about the surrounding code, I can't be certain if you have enough space to work with. But if you do, essentially you can copy the call instructions, update the assignment to the ECX register, and paste the call instruction back into it's new location, moved a few bytes later, and then NOP pad to the next instruction boundary (assume you're not overwriting anything important). It might be easier to NOP out useless instructions ahead of time, such as the call sequence for cheat functions, so you have a better sense of how much space you have to work with. Pay close attention to any important following instructions, and make sure you don't overwrite any of them.
Hopefully that's enough information that you may be able to figure out what to do here.