Yes, there is definately an issue with graphics. The combat vehicles have seperate graphics for the chasis and turret. You can simply draw the chasis you want, and then place the weapon turret over it (single or double). But the guardposts have the graphics all together. There is no seperate guard post structure followed by a turret palced on top of it. So, if you really want a guard post to look like it has a dual turret, you'll need to do some graphics editing. If it had been as simple as the vehicle case, I probably would have posted this long ago.
Also, I was going to post on how to add research topics which allow the dual turret. There are a few complications though, due to the way Outpost2 passes data around internally. When you're creating a unit, the player who owns the unit is not passed in to the actual unit creation part (where the class is instantiated). If it was, you could just simply override the virtual function that creates the class, and set the bit depending on whether or not the player building it has finished researching the topic yet. Checking if a topic is complete is fairly easy, or you could also use a trigger callback for when the research is complete to install the new unit creation function that always sets that bit. Of course since the player number isn't passed into that creation function, this would just give everyone dual turret GPs once anyone researched the tech. There are two ways around this. One is to intercept the unit creation at a different point, probably using code overwrites, or just keep in mind that the unit creation functions should only be called from one place, and lift the player number off the stack. It will be there at a fixed place backwards, provided your function is always called from the same place. The solution is a bit sick, but it does seem to be the easiest way.
I'd also like to point out that since this bit is stored in the unit records, and not the "UnitInfo" records, it won't automatically upgrade all existing GPs when you start setting this bit (such as when a research topic is completed). Personally I think this is a lot more realistic, so I'm happy with this.
The Graphics Functions
--------------------------
First we'll start at the virtual function table for the Guard Post unit. Once a label is added at the correct address, it can quickly be found using the Names window in OllyDbg (Ctrl+N). The entry with the label added looks something like this:
004CFFB0 .rdata User Unit:GuardPost.vtbl
This will take you to a place in the data window that starts with something like this:
004CFFB0 >0042D700 <Outpost2.Unit:GuardPost.GetUnitTypeInfo()>
If we look down 6 lines, we will see something like this:
004CFFC8 0042D450 <Outpost2.Unit:GuardPost.DrawUnit()>
Whenever a unit needs to be drawn on screen, it calls a draw function like this through the virtual function table. By overriding this function, we can control how any unit is drawn. In this particular case, we can control how Guard Posts are drawn.
Note: By changing the various functions in the virtual function table, we can change a fairly large number of unit behaviors. If you're looking to build new units, this is probably how you'd want to go about doing it. There are however a lot of functions in this table, and to prevent crashing the game, you'd need to have every function available for your unit, and all with the correct calling procedure. The number of functions needed changes depending on the type of unit (building, vehicle, disaster, resource), and for the Guard Post, it appears to use 65 functions. A much easier way to add "new" units, is to clone an old unit, and only override a few of the functions. This is essentially what we will do with the Guard Posts, since the only difference between our new unit and the existing one, is the fire rate, and dual turret display, and the existing unit can handle the fire rate already.
Now, there are two obvious ways of overriding the function we are interested in. The first way is to simply change the address of that one function in the virtual function table. This will ensure that all existing units of that type will now use the new function. This method has two disadvantages that I can see. One is that the virtual function table is meant to be read only data, and as such is often placed in a read only section of memory. To modify it successfully (indeed to avoid crashing the program) you'll have to unprotect that area of memory first before modifying it, and then you'll probably want to reprotect it again afterwards. The other disadvantage is that this limits you to changing an old unit to have new behavior. You can't really add a new unit and keep the old unit, unless the new unit can somehow be both.
Neither problem is much of an issue with creating a Dual Turret Guard Post. We have the dual turret flag in each unit, so the unit can use this flag to determine if it should draw a single turret unit or a dual turret one. Thus the new unit can handle both the new and old behaviors quite easily.
The other method that is of interest, is to copy the existing virtual function table to a new area of (read/write) memory, edit the table to update the address of the function you want to change to point to your new function, and then change the virtual function table pointer in the unit object. The unit object will reside in read/write memory, so you won't have to worry about unprotecting and reprotecting memory. The old virtual function table will also continue to exist, so old units can continue using it, while new units can use the updated table. This is a little closer to adding new units, and so I figure this method is more worth being explored for future use.
I'll begin by adding a new file to the existing project. For now, we'll just fill in enough to duplicate the existing virtual function table. We only really need to call one function to do the setup, and all the rest of the changes will be hidden away from the main program. For this reason, I will avoid creating a header file, since we will only need to call one function from the .cpp file. We can just add the function declaration to the file we need to call it from (main.cpp). Also, since I'm only interested in updating the virtual function table for a single unit in this test, I'll need the address of the new virtual function table to replace the old one with. To keep things simple, I'll just have that single function return this address.
Here is the code to duplicate the virtual function table:
DualTurretGP.cpp
--------------------
#include <memory.h>
// DualTurretGPvftbl
void* dualTurretGPvftbl[65];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, 65*4);
// Update desired function pointers here
// Return the new virtual function table
return dualTurretGPvftbl;
}
To use this code, we add the function declaration to main.cpp just after the #include lines:
void* SetupDualTurretGP();
We then need to call this function from InitProc:
void* dualTurretGPvftbl = SetupDualTurretGP();
Then we need to update the virtual function table of the guard post we created:
// Get the address of the virtual function table pointer
int* vtblPtrAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120);
// Set the new virtual function table
*vtblPtrAddress = (int)dualTurretGPvftbl;
All this just clones the virtual function table and sets the guard post to use the new one. We have not yet changed any entries, so the guard post will still behave exactly as the old one did.
This would be a good time to run the test DLL to ensure there are no strange crashes. The changes made so far are summarized as follows: DualTurretGP.cpp was added to the project with the contents given above. The declaration for the setup function in DualTurretGP.cpp was added to main.cpp just after the #include lines. The InitProc function in main.cpp was updated to the following:
int InitProc()
{
Unit unit;
Unit guardPost;
void* dualTurretGPvftbl = SetupDualTurretGP();
// Make sure graphics look right
Player[0].GoEden();
Player[1].GoEden();
// Create a guard post
TethysGame::CreateUnit(guardPost, mapGuardPost, LOCATION(100,40), 0, mapLaser, 0);
// Create a power plant to power the guard post
TethysGame::CreateUnit(unit, mapTokamak, LOCATION(90, 40), 0, mapNone, 0);
// Get the address of the flags
int* flagsAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120 + 0x44);
// Set the dual turret flag
*flagsAddress |= 0x20;
// Get the address of the virtual function table pointer
int* vtblPtrAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120);
// Set the new virtual function table
*vtblPtrAddress = (int)dualTurretGPvftbl;
// Create some enemy units
TethysGame::CreateUnit(unit, mapLynx, LOCATION(110,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(116,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(122,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
// A refrence unit for you
TethysGame::CreateUnit(unit, mapLynx, LOCATION(80,40), 0, mapLaser, 0);
// Center the view on the action
Player[0].CenterViewOn(110, 40);
return 1; // return 1 if OK; 0 on failure
}
Our next step is to override the DrawUnit function. To do this, we'll need to define a new function to replace the old one with, and update the virtual function table to point to this new function. For now, we will keep the new function a simple empty one. This will result in the guard post being invisible, since no code will run when the game asks it to draw itself.
Here is the replacement function to add to DualTurretGP.cpp:
// Replacement DrawUnit function
__declspec(naked) void DualTurretGP_DrawUnit(void *graphicsObject)
{
__asm RETN 4;
}
I'll place this near the top of the file so I don't need to add a forward declaration when I go to update the function address. Especially since I might very well change the function's signature when I go to implement something. But for now, this will do.
The virtual function table is updated in SetupDualTurretGP with the following line:
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DualTurretGP_DrawUnit;
If you look at the virtual function table as an array of function pointers, than the DrawUnit function is at position 6 (starting from 0) in the table.
The new DualTurretGP.cpp file now looks like this:
#include <memory.h>
// Replacement DrawUnit function
__declspec(naked) void DualTurretGP_DrawUnit(void *graphicsObject)
{
__asm RETN 4;
}
// DualTurretGPvftbl
void* dualTurretGPvftbl[65];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, 65*4);
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DualTurretGP_DrawUnit;
// Return the new virtual function table
return dualTurretGPvftbl;
}
Compile this and run it, and you'll see the the Guard Post is now invisible. The tubes and bulldozed terrain around it still give it away though. Plus, you can still select it and see the selection box around it.
Just a quick note on the calling conventions needed by the replacement DrawUnit function. There is one parameter to the function, a pointer to a graphics object that is used to do all the drawing to the game window. This is why we have the RETN 4, since the pointer is 4 bytes long, and needs to be cleaned off the stack when the function exits. There is also a hidden "this" pointer to the Guard Post unit object. The hidden "this" pointer is passed in the ECX register. When you write your replacement function you must keep this in mind and make no other assumptions about registers. In particular, you must make sure that exactly 4 bytes get poped off the stack when the function returns. This is not always an easy task when writing the function in C/C++. You may need to check your compiler documentation to determine how to get your function to conform.
Another suitable replacement function might look like this:
// Replacement DrawUnit function
void __stdcall DualTurretGP_DrawUnit(void *graphicsObject)
{
}
Or to get explicit access to the hidden "this" pointer:
// Replacement DrawUnit function
void __stdcall DualTurretGP_DrawUnit(void *graphicsObject)
{
void *thisGuardPost;
__asm MOV thisGuardPost, ECX;
}
The next topic will be on how to use the graphics object to do some drawing. We will start this off by looking at how the original DrawUnit functions work, and seeing what we can do to draw what we want.
I would also like to put in a call to graphics artists to design some nice dual turret Guard Post graphics. We will eventually need something to draw, and the game doesn't quite have the graphics we need already built it. Perhaps the existing graphics for vehicle turrets and the existing graphics for guard posts can somehow be put to work. Although, the same weapon tends to look quite different between the vehicle and guard post form. I'm sure anything you guys out there put together will look better than anything I can patch together.