A powrprof.dll hook which adds new Lua global and member functions to Forces of Corruption.
Drop powrprof.dll into the /corruption folder of your Star Wars: Empire at War Gold Pack installation.
Works with both the debug kit StarWarsI.exe and client version StarWarsG.exe of the Steam version of Forces of Corruption.
Currently does not support the Empire at War base game, though theoretically it could.
Not supported on disc / GoG versions (sorry).
Appends a line of text to the file in the game directory.
Parameters:
| Parameter | Type | Description |
|---|---|---|
| file | string |
The file name. |
| text | string |
The text to append. |
Returns: nil
Example:
WriteToFile("example.txt", "Hello, world!")Returns a 1-based Lua table of all active player objects, iterable with ipairs().
Parameters: none
Returns: table<integer, PlayerObject>
Example:
local players = Get_All_Players()
for _, player in ipairs(players) do
local playerId = player.Get_ID()
if playerId >= 0 then
-- Add non-neutral players to our PlayerTable global
PlayerTable[playerId] = player
end
endAdds or updates a line of text to the tutorial text pane with optional duration and coloring.
Parameters:
| Parameter | Type | Description |
|---|---|---|
| key | string |
The key used to identify this line. Can be later used to update text, duration, and color, or to remove the line. |
| text | string |
The text to display. |
| duration | number? |
Optional, default: -1. The duration to display the text. To display text indefinitely until removal, set to -1. |
| r | number? |
Optional, default: 1.0. The red color component, 0.0 to 1.0. |
| g | number? |
Optional, default: 1.0. The green color component, 0.0 to 1.0. |
| b | number? |
Optional, default: 1.0. The blue color component, 0.0 to 1.0. |
| a | number? |
Optional, default: 1.0. The alpha component, 0.0 to 1.0. |
Returns: nil
Example:
local players = Get_All_Players()
for _, player in ipairs(players) do
local name = player.Get_Name()
local faction = player.Get_Faction_Name()
local credits = player.Get_Credits()
-- For example, the key of the first Empire player might be PC:1
local key = string.format("PC:%d", player.Get_ID())
-- Will print "EMPIRE Player [Name]: $ 8000"
local text = string.format("%s Player %s: $ %d", faction, name, credits)
Add_Tutorial_Text(key, text, -1, 1, 1, 1, 1)
endRemoves the tutorial text line associated with the key if it exists.
Parameters:
| Parameter | Type | Description |
|---|---|---|
| key | string |
The key of the line to remove. |
Returns: nil
Example:
Add_Tutorial_Text("TEXT1", "Hello, world!")
Add_Tutorial_Text("TEXT2", "This is a test.")
Remove_Tutorial_Text("TEXT1")
-- "This is a test." still showsReturns the localized text string display name of the object.
Parameters: none
Returns: string The localized display name text.
Example:
local object = Find_First_Object("TIE_FIGHTER")
if TestValid(object) then
local name = object.Get_Display_Name()
-- Will print "Imperial TIE Fighter" (or whatever its text name in MasterTextFile.dat is)
local text = string.format("Imperial %s", name)
Add_Tutorial_Text("TEXT1", text)
endThe process of reverse engineering StarWarsI.exe using the StarWarsI.pdb that Petroglyph provided is pretty straightforward. It's relatively easy to search for function names in a disassembler such as Ghidra or IDA Free, find the relative virtual address (RVA) of that function, and then add it as a pointer in the DLL that can be called.
The tricky part is finding RVAs in StarWarsG.exe. We do not have the symbols for that binary with the PDB file like we do with StarWarsI.exe. For now, I've been using the version control function of Ghidra to compare StarWarsI.exe with StarWarsG.exe which uses algorithms to map functions between the two binaries that it thinks are a match. So far, it has been mostly accurate, but it has not found matches for everything that I've wanted to add. There are some instances where I had to go digging for references to find out candidate addresses for a match, and there was a lot of trial and error in some cases until things finally worked.
There are also instances where functions in StarWarsI.exe do not exist in StarWarsG.exe. This most commonly seems to be the case with object constructors and field accessors. When Petroglyph compiled StarWarsG.exe, they likely used compiler optimization settings which "inlines" construction of objects and accessors of instance fields to reduce the number of JMP instructions. This works well for performance, but it gets in the way of reverse engineering the game engine. For these cases, we usually have to re-create our own constructors and hard reference the exact offset of fields. These offset addresses also are not identical across StarWarsI.exe and StarWarsG.exe which adds to the complexity.