Inside Ultimate - Part 2: The VCLXChg Magic
Last time I gave you a little overview about the inner workings of RPG Maker 2009 Ultimate. This time, I’ll tell you more about the mysterious vclxchg5.dll/vclxchg6.dll files.
But first, I’ll tell you which methods of manipulation are used in RPG Maker 2009 at all in order to customize the RPG Maker:
- Subclassing of windows in order to catch certain messages and react on them (modifying a window after it was created, reacting on a click on one of my toolbar buttons, etc.).
- Windows Hooks in order to process hotkeys and detect creation of windows (so as to be able to subclass them).
- Function hooking using Microsoft Detours in order to manipulate behaviour of both internal RPG Maker functions (e.g. the function which is responsible for adding the resource files to the resource selector listboxes) and Windows API functions (e.g. CreateFontIndirectA for the font replacement feature).
- Code patching (e.g. for disabling the 4-lines limit of comments)
However, there was one big problem: Even though basic manipulation of windows (hiding, moving, resizing, changing labels, …) was no problem (possible using simple WinAPI), more advanced tasks (calling internal functions in order to simulate menu item clicks, changing listbox contents, changing the status bar, …) didn’t work that way.
The RPG Maker was created using Delphi. Fortunately, Delphi has RTTI (Run-Time Type Information), which allows me to access fields (and invoke methods!) of internal objects by name (this is also how the window properties stored in the RCDATA resource sections work). However, since I am using FreeBasic, the following problems emerge:
- RTTI only works from within Delphi (furthermore, it is not very well documented).
- Not all of the fields/methods are accessible using RTTI.
- For some tasks, I also need to create, use and destroy Delphi objects (e.g. TString objects, to begin with).
- I am mostly working with HWNDs (WinAPI window handles), but in order to access Delphi’s properties, I need pointers to the corresponding TWinControl objects of Delphi.
Fortunately, problem #4 is not very hard to solve. Upon inspection of the RPG Maker’s windows using Winspector Spy, I noticed that every control has a window property like “ControlOfs00400000000004D8″ with a pointer as value. A little googling revealed that this is in fact a pointer to the corresponding Delphi object and that the name of the property consists of “ControlOfs” followed by 8 characters representing the hexadecimal HINSTANCE of the control and another 8 characters representing the hexadecimal thread ID of the thread which created it.
Problems #1, #2 and #3 all resulted from the fact that I am not using Delphi. Actually, it wouldn’t even have helped if I would have switched to Delphi at all, because the memory layout of the internal objects is different in every Delphi version. Therefore, I would have to use the same Delphi version as the RPG Maker was created with. This introduces a new problem, because RPG Maker 2000 was created with Delphi 5 while RPG Maker 2003 was created with Delphi 6.
The solution in this case was to get Delphi 5 and Delphi 6, create a “translator” library which helps me communicating with Delphi (actually the VCL – the Visual Component Library), and compile the same library with both Delphi versions, but to two different files. The vclxchg5.dll and vclxchg6.dll files were born – VCLXChg stands for “VCL Exchange”. Both files are based upon the same source code, but compiled with different Delphi versions (5 and 6). Depending on which RPG Maker version is used, a different VCLXChg version is loaded.
These libraries expose several functions which use Delphi objects in simple exported functions which I can use from FreeBasic, from simple string creation/deletion functions over a function to undock controls from their parent window (together with DockWnd.dll used for the floating tool panel) to RTTI functions.
If you are interested in Delphi’s RTTI, have a look at this article. Also, if you press the Pause/Break key in RPG Maker 2009 Ultimate when debug mode is activated, this will call up the “Inspector” (which is written in Delphi inside the VCLXChg libraries) which allows you to explore the RTTI of the RPG Maker’s window objects. The UIMod feature also works using the RTTI.
For example, this is the Delphi source code of one of the functions inside the VCLXChg library:
function StatusBar_SetContent(control: TStatusBar; part: Integer;
buffer: PChar): Boolean; stdcall;
begin
Result := false;
if control = nil then Exit;
if control.Panels.Count < part + 1 then Exit;
control.Panels.Items[part].Text := String(buffer);
Result := true;
end;
In my FreeBasic code of utimate.dll, I am able to use the function like this:
VCLXChg.StatusBar_SetContent(DllData->cStatusBar, 3, "ID:" & _
Format(GetCurrentMapTreeItem()->ID, "0000"))
I had one problem, however: When I used my VCLXChg library more often (especially when strings (TString) and other objects like TStringList got involved), RPG Maker would suddenly crash with mysterious “Invalid pointer operation” and “Access violation” errors. A little bit of Google research revealed that each module (the main EXE file as well as every loaded DLL is a module) would have its own “memory manager” which handles creation and destruction of objects. This would explain my problems: I had, for example, created a TString in my VCLXChg library and set a window property to this string, which caused the main EXE of the RPG Maker to crash when it tried to destroy my string later (since it did belong to another module). The “normal” solution would be using the “SharedMem” package for Delphi in both the main module and the DLL, but this was impossible here because I didn’t have control over the main module – after all, I couldn’t just write an email to Enterbrain asking them to recompile the RPG Maker with SharedMem enabled so my modifications would work!
After more digging around I found the SetMemoryManager function, which I could use to set the vclxchgX.dll’s memory manager to the RPG Maker’s. However, the problem was that I didn’t have the pointer to the RPG Maker’s TMemoryManager object! And I couldn’t call GetMemoryManager because the RPG Maker would have to call it, not me (we are back at writing emails to Enterbrain at this point)!
My solution was the following function (FreeBasic code):
' This function will find the RPG Maker's TMemoryManager pointer
Private Function GetMemoryManager() As TMemoryManager Ptr
' CC is rarely used, so I am using it as a
' "wildcard" here
#Define UNKNOWN &hCC
' This search pattern is used for the RM2k3 (Delphi 6)
Dim pattern2k3(...) As UByte = {&h53, &h85, &hC0, &h7E, &h15, _
&hFF, &h15, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, _
&h8B, &hD8, &h85, &hDB, &h75, &h0B, &hB0, &h01, &hE8, _
UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, &hEB, &h02, _
&h33, &hDB, &h8B, &hC3, &h5B, &hC3}
' This search pattern is used for the RM2k (Delphi 5)
Dim pattern2k(...) As UByte = {&h85, &hC0, &h74, &h0A, &hFF, _
&h15, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, &h09, _
&hC0, &h74, &h01, &hC3, &hB0, &h01, &hE9, _
UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, &hC3}
' This macro scans the memory in the range 401000..40F000 for the
' pattern and returns the DWORD value at the given
' offset (relative to the first byte of the search pattern)
#Macro scan(pattern, offs)
Dim j As Integer = 0
For i As UByte Ptr = &h401000 To &h40F000 Step 1
If pattern(j) = UNKNOWN OrElse *i = pattern(j) Then
j += 1
If j > UBound(pattern) Then _
Return *CPtr(TMemoryManager Ptr Ptr, _
i - UBound(pattern) + offs)
Else
j = 0
EndIf
Next
#EndMacro
If DllData->IsRM2k3 Then
scan(pattern2k3, 7)
Else
scan(pattern2k, 6)
EndIf
Return NULL
End Function
I used a disassembler to find a function at which the TMemoryManager pointer is hardcoded (by compiling a file which uses GetMemoryManager and then looking at the assembly output) and created a search pattern from it so that I would find the same function (and thus the pointer to the TMemoryManager) in the RPG Maker.
That’s it for today! Next time, we will look at how the injection of my code when the RPG Maker is started actually works (and how I make the RPG Maker load the right project).