Lehona / LeGo

A daedalus library for the game Gothic. It contains various packages to support modders.
19 stars 7 forks source link

OpenGothic compatibility initiative #29

Open Try opened 3 years ago

Try commented 3 years ago

Hello, I'm project-owner of OpenGothic, reaching you to start discussion about compatibility. On my side I have some user requests to support gothic2 mods created with LeGo. Obvious problem: my engine is not based on original source code, and many entry-point do not exist at all, calling convention is not the same and so on.

So, my question is: can we somehow work together on this? The way it look for me LeGo hooks into native code of original game by doing:

    const int call = 0;
    if(CALL_Begin(call)) {
        CALL_{Float|Int}Param(_@(last_param));
       ...
        CALL_{Float|Int}Param(_@(first_param));
        CALL__thiscall(zrenderer_adr, native_function_intptr);
        call = CALL_End();
    };

Will it be enough on my side to hook into CALL_Begin, CALL_*_Param, and CALL_End functions and provide my own implementation?

Will it be enough for me to implement function from EngineAdr_G2.d ? Is there any list/doc about all of them?

Another question is: is there any good content to test against?

Lehona commented 3 years ago

Will it be enough on my side to hook into CALLBegin, CALL*_Param, and CALL_End functions and provide my own implementation?

Not really. Ikarus (used internally by LeGo and by the mods themselves) is a low-level library (think reading/writing pointers, bindings for the zEngine classes) that you will have to support in some way or another. This includes but is not limited to the CALL_ functions. Often times mods (or LeGo) will write/read directly to/from a memory address, so you will have to emulate those addresses as well. Fortunately all pointer-reads/writes go through MEM_WriteInt/ReadInt (and MEM_WriteString/MEM_ReadString) so that makes it reasonably possible to hook.

The EngineAdr_G2.d contains all addresses that are referenced in LeGo, so by searching for those constants you will be able to find all usage sites. There will be additional calls to Ikarus which do not pass an address explicitly (i.e. the function in Ikarus references those addresses internally), so you will have to skim through Ikarus anyway.

LeGo provides the HookEngine-module, which will be passed user-chosen addresses, so although you might be able to emulate a select set of them, this will have to be done on a per-case basis.

Is there any list/doc about all of them?

There is a list of all functions in the gothic.exe, but that has been automatically generated and contains no documentation.

Another question is: is there any good content to test against?

Unfortunately there are no automated tests for LeGo or mods that I know of, but a lot of mods have open-sourced their scripts (and possibly assets), so manual testing is somewhat possible.

So, my question is: can we somehow work together on this?

This is a huge project and I do not have the time to contribute directly, but I'm more than happy to answer any questions.

Try commented 3 years ago

Right, so basically there is Ikarus, which do most of heavy-lifting and LeGo is more of particular use-case, when it comes to hooking zEngine.

The way I see it at this point is:

  1. Emulate 32-bit address space of zEngine + emulate x86 asm is somewhat unreasonable
  2. Ikarus based largely on vulnerability in zEngine VM. pusInt(addr); val = popVar(); that won't produce the same result in OpenGothic. In that case - there is a validation and addr will be returned as-is instead.
  3. Assume people prefer to use LeGo/Ikarus wrappers instead of writing memory directly or running asm code

The way I want to approach this is to implement some subset of top-level api + provide some sort of hit, so script can be aware of what is going on.

lot of mods have open-sourced their scripts, so manual testing is somewhat possible.

Yes, that what I'm looking for. So far I have found "Strong hand" mod for G1, but maybe you can point me out to some others mods?

this will have to be done on a per-case basis. Yep, basically looking forward to emulate:

  • Ikarus-memory (assuming memory is explicitly allocated by script)
  • MEM_Copy
  • MEM_Swap
  • MEM_Clear
  • MEM_Realloc
  • MEM_Compare
  • LeGo-memory
  • malloc_adr = 8078534; //0x7B44C6
  • free_adr = 8078540; //0x7B44CC
  • memcpy_adr = 8213280; //0x7D5320
  • Ikarus-Arrays
  • Alloc / Clear / Free / Size / Read / Write
  • Insert / Push / Pop / Top
  • IndexOf / RemoveIndex / RemoveValue[Once]
  • Sort / Unique
  • ToString
  • Ikarus-String Tools
  • GetCharAt / Length
  • Substring / Prefix
  • Compare
  • STR_ToInt
  • STR_IndexOf
  • STR_Split
  • STR_Upper
  • LeGo cursor
  • Cursor_Ptr = 9246300; //0x8D165C
  • Cursor_sX = 9019720; //0x89A148
  • Cursor_sY = 9019724; //0x89A14C
  • LeGO-Game
  • oCGame__changeLevel = 7107216; //0x6C7290 Hook: Saves
  • oCGame__changeLevelEnd = 7109323; //0x6C7ACB Hook: Saves
  • oCGame__Render = 7112352; //0x6C86A0 Hook: FrameFunctions
  • oCGame__RenderX = 7112704; //0x6C8800 Hook: Quickslots
  • oCGame__UpdateStatus = 7093113; //0x6C3B79 Hook: Focusnames
  • oCGame__UpdateStatus_start = 7090496; //0x6C3140 Hook: Bars
  • oCGame__UpdateScreenResolution = 7089664; //0x6C2E00 unused, kept for compatibility
  • oCGame__UpdateScreenResolution_end = 7090416; //0x6C30F0 Hook: Bars
  • LeGO-Items
  • oCItem__Render = 7420608; //0x713AC0
  • LeGO-Npc
  • oCNpc__CloseInventory = 7742483; //0x762413 Hook: Quickslots
  • oCNpc__DropUnconscious = 7560880; //0x735EB0 Hook: Shields
  • oCNpc__Equip = 7576720; //0x739C90
  • oCNpc__EquipItem = 7545792; //0x7323C0 Hook: Shields
  • oCNpc__EquipWeapon = 7577648; //0x73A030
  • oCNpc__EV_DrawWeapon = 7654416; //0x74CC10 Hook: Shields
  • oCNpc__EV_DrawWeapon1 = 7656160; //0x74D2E0 Hook: Shields
  • oCNpc__EV_PlayAni = 7699121; //0x757AB1 Hook: AI_Function
  • oCNpc__EV_RemoveWeapon = 7658272; //0x74DB20 Hook: Shields
  • oCNpc__EV_RemoveWeapon1 = 7660720; //0x74E4B0 Hook: Shields
  • oCNpc__OpenInventory = 7742032; //0x762250 Hook: Quickslots
  • oCNpc__PutInSlot = 7642288; //0x749CB0
  • oCNpc__RemoveFromSlot = 7643760; //0x74A270
  • oCNpc__UnequipItem = 7546560; //0x7326C0 Hook: Shields
  • oCNpc__UseItem = 7584784; //0x73BC10
  • oCNpc__StartDialogAniX = 7700155; // 0x757EBB
  • oCNpc__StartDialogAniY = 7700162; // 0x757EC2
  • oCNpc__GetPerceptionFunc = 7726080; //0x75E400
  • LeGO-fonts
  • zCFontMan__GetFont = 7898288; //0x7884B0
  • zCFontMan__Load = 7897808; //0x7882D0
  • zCFont__GetFontName = 7902368; //0x7894A0
  • zCFont__GetFontX = 7902448; //0x7894F0
  • zCFont__GetFontY = 7902432; //0x7894E0
  • LeGO-view/input
  • zCInput_zinput = 9246288; //0x8D1650
  • zCInput_Win32__SetDeviceEnabled = 5067008; //0x4D5100
  • zCInput_Win32__GetMouseButtonPressedLeft = 5068688; //0x4D5790
  • zCInput_Win32__GetMouseButtonPressedMid = 5068704; //0x4D57A0
  • zCInput_Win32__GetMouseButtonPressedRight = 5068720; //0x4D57B0
  • zCInput_Win32__GetMousePos = 5068592; //0x4D5730
  • zCView__@zCView = 8017856; //0x7A57C0
  • zCView__Close = 8023600; //0x7A6E30
  • zCView__InsertBack = 8020272; //0x7A6130
  • zCView__Move = 8025824; //0x7A76E0
  • zCView__Open = 8023040; //0x7A6C00
  • zCView__Render = 8045072; //0x7AC210
  • zCView__SetFontColor = 8034576; //0x7A9910
  • zCView__SetSize = 8026016; //0x7A77A0
  • zCView__zCView = 8017664; //0x7A5700
  • zCView_Top = 8021904; //007A6790
  • zCView__PrintTimed_color = 8027512; //0x7A7D78 Hook: Interface
  • zCView__PrintTimedCX_color = 8027837; //0x7A7EBD Hook: Interface
  • zCView__PrintTimedCY_color = 8028042; //0x7A7F8A Hook: Interface
  • zCView__PrintTimedCXY_color = 8028406; //0x7A80F6 Hook: Interface
  • zCView__PrintTimed_colored = 8027536; //0x7A7D90 Hook: Interface
  • zCView__PrintTimedCX_colored = 8027857; //0x7A7ED1 Hook: Interface
  • zCView__PrintTimedCY_colored = 8028058; //0x7A7F9A Hook: Interface
  • zCView__PrintTimedCXY_colored = 8028422; //0x7A8106 Hook: Interface
  • LeGO-D3D
  • zCParser__DoStack = 7936352; //0x791960
  • zCRenderer__DrawTile = 6110448; //0x5D3CF0
  • zCTexture__Load = 6239904; //0x5F36A0
  • zRND_D3D__DrawLine = 6609120; //0x64D8E0
  • zRND_D3D__DrawPolySimple = 6597680; //0x64AC30
  • zRND_D3D__EndFrame = 6610720; //0X64DF20 Hook: Sprite
  • zRND_D3D__SetAlphaBlendFunc = 6628880; //0x652610
  • zCRnd_D3D__XD3D_SetRenderState = 6573808; //0x644EF0
  • LeGO-Math
  • zSinCosApprox = 6269632; //0x5FAAC0
  • _atan2f = 8123804; //0x7BF59C
  • _sinf = 8123910; //0x7BF606
  • _acosf = 8123794; //0x7BF592

This is only subset, what can be mapped relatively easy. OS related function, like LoadLibrary - not here, won't implement. Save-game are also not a thing - OpenGothic, just makes a memory dump to save the game

CarnageMarkus commented 3 years ago

Save game address is used, to enable LeGo custom "object persistence", which further depends on ability to write text files? So it's kind of must have hook. Other hooks like resolution changed, is most likely hook to detect game settings update. Which is used by Ikarus/Lego to retrieve game settings from scripts.

It's long since I worked with any of this, but seems to me you would be missing "most" important "events" on which many of Ikarus/Lego users rely.

Try commented 3 years ago

custom "object persistence"

Don't worry about it - OpenGothic writes full state to save-game file, no need in custom persistence