georgeto / gothic3sdk

An inofficial SDK for Gothic 3.
Other
27 stars 6 forks source link

How to create headers linked with G3 #7

Closed LionBlazer closed 1 year ago

LionBlazer commented 1 year ago

Hello I ran into a problem: I needed to call functions from gCMap_PS, but their definitions aren't provided in the sdk

image

I tried to restore the definition from the decompiled code Game.dll

image image

But apparently I'm doing something wrong, because during the CalcUV call, the game crashes. Can you tell me how to add definitions from the game?

georgeto commented 1 year ago

But apparently I'm doing something wrong, because during the CalcUV call, the game crashes. Can you tell me how to add definitions from the game?

The problem is that you have declared the function as virtual, although in fact it is not a virtual function. If you remove the virtual specifier, calling the CalcUV function should work as expected.

Well, not quite yet, because now you will get a linker error complaining that it can't find the CalcUV function. The reason is that your declaration of CalcUVis missing the const specifier.

With these changes, the function declaration is exactly what can be seen in your Ghidra decompiler screenshot, i.e. is what is exported by Game.dll.

I have attached the header for gCMap_PS in case you want to access additional methods. ge_map_ps.zip

LionBlazer commented 1 year ago

Thanks, game has stopped crashing. But apparently the problem was not only this: Maybe I am incorrectly passing a pointer to gCMap_PS to my function, is it legal to do this?

_mCCallHook Hook1; void GE_STDCALL fun1(CFFGFCWnd param_1) { hook_base(param_1, (gCMap_PS )(param1 + 0x10)); // is it correct? }

_(*(gCMap_PS **)(param1 + 0x10) crashes)

I inject the hook with following code:

_Hook1.Prepare(RVA_Game(0xa87d9), &fun1) .AddRegArg(mCRegisterBase::mERegisterTypeEdi) .InsertCall().Hook();

address in game.dll: image

I took the offset 0x10 based on this fragment image


My current code doesn't crash game but any access to gCMap_PS returns zeros, an attempt to write something produces crash.

`void hook_base(CFFGFCWnd param_1, gCMap_PS map) {

bCVector playerPos = Entity::GetPlayer().GetInstance()->GetWorldPosition();

GEFloat u = 0.0;
GEFloat v = 0.0;
map->CalcUV(playerPos, u, v);

// text rendering
CFFGFCDeviceContext* context = param_1->GetDC();
std::string s = std::to_string((unsigned long long) map);
bCString str = VectorToString(bCVector(u, v, 0)) /* always undefined value */ + " " + VectorToString(playerPos);
bCRect rect(100, 100, 200, 200);

context->CalcTextRect(0, str.GetUnicodeText(), -1, rect, 0x25);
bCFloatAlphaColor b_c_float_alpha_color(1);
bCFloatAlphaColor b_c_float_alpha_color2(0x100a8bb6);
param_1->GetClientRect(rect);
context->DrawTextA(0, str.GetUnicodeText(), -1, &rect, 0x25, b_c_float_alpha_color, &b_c_float_alpha_color2);

//icon drawing
CFFGFCBitmap icon;
icon.Create("TEST.png");

bCRect rect2(u, v, u + 30, v + 30);
context->DrawBitmap(&icon, &rect2, 0, 0.0);

}`

when trying to output UV, an "undefined" value is output:

image
georgeto commented 1 year ago

mCCallHook Hook1; void GE_STDCALL fun1(CFFGFCWnd param_1) { hook_base(param_1, (gCMap_PS )(param_1 + 0x10)); // is it correct? }

The pointer arithmetic here is subtly wrong. param_1 is of type CFFGFCWnd *, i.e. a pointer to a CFFGFCWnd object. Therefore the address resulting from param_1 + 0x10 is actually param_1 + 0x10 * sizeof(CFFGFCWnd). To get the result you expect, you first have to cast param_1 to a byte sized pointer (e.g. GELPByte, which is defined as unsigned char *): (gCMap_PS *)((GELPByte)param_1 + 0x10)

Ghidra probably does not know the size of the CFFGFCWnd type, and thus simply assumes a size of one (byte), so the decompiler output is misleading.

(For a more descriptive explanation how pointer arithmetic works see for example here).


My current code doesn't crash game but any access to gCMap_PS returns zeros, an attempt to write something produces crash.

If no map is currently selected, the pointer to gCMap_PS is null. If you then invoke CalcUV this will cause a crash. To detect this situation, you can simply check for map being != null.

Or as an alternative, move your hook further down into the function, for example RVA_Game(0xA8811), and rely on the check that is already present in FUN_100a87f0 (whose actual name is by the way gCHUDStaticMap::OnPaint), see the if condition in line 32. Don't forget to then also adjust .AddRegArg(mCRegisterBase::mERegisterType_Edi) to use mERegisterType_Esi instead.

LionBlazer commented 1 year ago

Thank you, I added cast to byte, and everything worked. hook_base(param_1, *(gCMap_PS **)((GELPByte)param_1 + 0x10));