Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify how to integrate D2Moo for modders #20

Open
Lectem opened this issue Jan 21, 2021 · 3 comments
Open

Clarify how to integrate D2Moo for modders #20

Lectem opened this issue Jan 21, 2021 · 3 comments
Assignees
Labels
help wanted Extra attention is needed question Further information is requested

Comments

@Lectem
Copy link
Member

Lectem commented Jan 21, 2021

Right now I did not choose the way we want to support mods for the D2Moo integration.
In the long term, I think the best option is to use a fork of D2Moo, but it's not necessarily simple to migrate from an existing mod (for example using D2Template) as you would have 2 different patching methods.

In my opinion this should be a discussion with other modders / community, so I listed a few ideas, their advantages and drawbacks in the following table.

Please give your opinion!

D2MOO as Method Integration with existing mod Ease of update Can share includes Compatible with D2Template Can easily change implementation of an ordinal Mod redistribution Can contribute/share easily (bugs) Is own project bugged or is it D2Moo?
subproject Configure CMake to use your own patch files and .def medium easy only unpatched +/- yes, but only from the original dll pov D2Moo launcher + .dlls yes easy to know
subproject Import D2MOO .dlls and patch the functions you want easy easy only unpatched yes yes, but only from the original dll pov Whatever you already had yes easy to know
subproject Link D2Moo statically medium easy only unpatched yes yes, but only from the original dll pov Whatever you already had yes mostly easy to know
fork Fork D2Moo and work directly on it needs some transition (start by importing dll?) easy, might need resolve all +/- yes D2Moo launcher + .dlls (OR simply .dlls if we use forwarding DLLs for unpatched ordinals) yes just need to change branch
source Copy paste (please don't) "easy" but leads to lot of issues hard copy-paste... yes depends on your project patching facilities ? no no
@Lectem Lectem added help wanted Extra attention is needed question Further information is requested labels Jan 21, 2021
@silvermane-HU
Copy link

Reading the list I'd prefer the "subproject - Import D2MOO .dlls and patch the functions you want" If there was a vote that is what I'd choose

@nooperation
Copy link
Contributor

I'm likely doing it wrong and forcing this project to do something it really isn't geared towards, but I had to end up going with copy-paste. I want to detour calls to functions Foo, Bar, and Baz to my own implementations, but I want these functions to continue using the existing D2 logic for everything else so the mod can coexist with other mods and to use code that is known to be working. Half of this issue is something that will disappear as this codebase grows

for instance

SKILLS_GetSkillsTxtRecord_t SKILLS_GetSkillsTxtRecord = nullptr;
...
SKILLS_GetSkillsTxtRecordFromSkill = (SKILLS_GetSkillsTxtRecordFromSkill_t)(D2CommonImageBase + 0x71540);
...
int32_t __fastcall My_SKILLS_SrvSt58_FireClaws(D2GameStrc* pGame, D2UnitStrc* pUnit, int32_t nSkillId, int32_t nSkillLevel)
{
    D2SkillsTxt* pSkillsTxtRecord = SKILLS_GetSkillsTxtRecord(nSkillId); // Call the original SKILLS_GetSkillsTxtRecord
    ...
    MyD2DamageStrc damage = {};
    My_sub_6FD155E0(pGame, pUnit, pTarget, pSkillsTxtRecord, nSkillId, nSkillLevel, &damage, 0); // Call my custom implementation of sub_6FD155E0
    ...
}

Let's say I had a custom My_SKILLS_SrvSt58_FireClaws. I want it to still call SKILLS_GetSkillsTxtRecord from the loaded D2Common library instead of the D2Moo implementation (It's just an example, assume these are both from the same dll and a bit more complex). I do this because I want to call functions that other mods may have injected their own code into and because it's guaranteed to be a correct implementation since it's using the original logic.

I started off as "subproject - Configure CMake to use your own patch files and .def" and this was working well until I started hitting issues with other mods and running into not yet complete logic in this codebase. Am I doing something completely wrong, or do I just have a not so great usecase at the moment?

@Lectem
Copy link
Member Author

Lectem commented Mar 3, 2024

So there are multiple issues at hand.

I want to detour calls to functions Foo, Bar, and Baz to my own implementations, but I want these functions to continue using the existing D2 logic for everything else so the mod can coexist with other mods and to use code that is known to be working. Half of this issue is something that will disappear as this codebase grows

There are multiple ways to do this, but essentially if you want to use D2Moo's patching mechanism you'll need to rely on doing 2 things:

  1. Detours the target function you want to replace
  2. Call the original functions instead of D2Moo's

The first one is easy to do with pretty much any patching mechanism, including D2Moo's.
The second one is a bit more tricky, you will need to either:

  • Keep the current code, and patch D2Moo functions you call to call the original ones instead
  • Change the code to call functions you manually loaded

If using D2Moo's patching mechanism, if you need to patch ordinal functions(or pointers) this couldn't be easier, just change the patch action as done here for datatables:

// Don't patch, datatable
PatchAction::FunctionReplacePatchByOriginal, // DATATBLS_GetLevelDefRecord @10010

If you need to patch something that is not an ordinal, you'll need to use the extra patch actions

static ExtraPatchAction extraPatchActions[] = {

If you need to call the original function from your detoured version, you'll also want to use extra patch actions by specifying a pointer that can store the original function address, much like what the debugger does:

{ 0x6FC386D0 - D2GameImageBase, &GAME_UpdateProgress_WithDebugger, PatchAction::FunctionReplaceOriginalByPatch, &GAME_UpdateProgress_Original},

In theory, we wouldn't need to patch D2Moo to call the original code if:

  • we were done writing/fixing all the functions
  • we didn't care about other patches being installed by "3rd party" mods

One alternative would be to use only D2Moo's headers, write your own patching DLLs and/or mechanism, and only write the functions you want to patch. As soon as would try to call a function that does not exist in your DLL and that is not an imported ordinal, you would end up with a linker error saying it can't find the function. You could then implement it as a simple call to the original function that you loaded with GetProcAddress() . This is what the macros D2FUNC does (in a not really optimized/pretty way). Example in the debugger:

D2FUNC(D2Game, SpawnSuperUnique_6FC6F690, D2UnitStrc*, __fastcall, (D2GameStrc* pGame, D2ActiveRoomStrc* pRoom, int32_t nX, int32_t nY, int32_t nSuperUnique), 0x6FC6F690 - D2GameImageBase);

I hope this can help and answers your question. If I can help making the process easier, don't hesitate to suggest changes to the project. I know one of the current pain points is that currently one .dll can only patch one original .dll. (This was kind of done on purpose to avoid mixing code from multiple .dlls and export ordinals that shouldn't be)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants