Skip to content

Reimpl/simple functions set 1 - #199

Open
gynt wants to merge 241 commits into
mainfrom
reimpl/simple-functions-set-1
Open

gynt wants to merge 241 commits into
mainfrom
reimpl/simple-functions-set-1

Conversation

@gynt

@gynt gynt commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch 2 times, most recently from dbc6c35 to 96be230 Compare August 23, 2026 02:19
Comment thread src/OpenSHC/Audio/mss/SoundSystem/markMusicChangePending.cpp
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch 3 times, most recently from 0b76486 to e6f6ab7 Compare August 24, 2026 19:03
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch 2 times, most recently from 4de8840 to 4dbcfb4 Compare September 2, 2026 09:00
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch 2 times, most recently from 797be2d to 3c5ea81 Compare September 10, 2026 21:35
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch 2 times, most recently from f1f1f12 to d33fac6 Compare September 24, 2026 13:19
@gynt
gynt marked this pull request as ready for review September 25, 2026 05:45
@gynt

gynt commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

The cpp linter report is mistaken, there is nothing wrong with the function's formatting.

Comment thread status/addresses-SHC-3BB0A8C1.txt Outdated
Comment thread status/addresses-SHC-3BB0A8C1.txt Outdated
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch from 99eca9a to bf10e68 Compare September 28, 2026 20:09
Comment thread src/OpenSHC/Game/GameStateStructures/resetTraderState.cpp Outdated
Comment thread src/OpenSHC/Map/LandscapeState/Construct_LandscapeState.cpp
Comment thread src/OpenSHC/UI/Helpers/LoadTGX_shc_back.cpp Outdated
Comment thread src/OpenSHC/Map/Version/SetDairyCheeseToZero.cpp Outdated

@gynt gynt left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments

gynt and others added 7 commits September 29, 2026 21:25
- move UI functions from the removed OpenSHC/UI.func.hpp into their new
  namespaces/folders (MenuModals, DisplayElements, Credits, Helpers, ...)
- remap *_Func references to the resolvers' current namespaces
- fix return types/fields that no longer matched the generated headers
- drop duplicate TribesState/LandscapeState constructor sources

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gynt and others added 13 commits September 29, 2026 21:25
Re-measured all 45 functions this branch added that were recorded below 100%,
with the Globals struct resolvers active.

31 are at normalized 100% -- only call and tail-jump targets differ -- which the
project records as 100% Reimplemented. Two of those were recorded as 0.0%; both
are single-instruction tail-call thunks whose one instruction is the jump.

The remaining 14 carry the reason the diff actually shows instead of the generic
"jump target differs" / "instruction selection differs". Most are register
allocation or instruction scheduling, which is not reachable from the source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gynt
gynt force-pushed the reimpl/simple-functions-set-1 branch from 2a496be to f462cf7 Compare September 29, 2026 19:26
@@ -68321,7 +68321,7 @@ SHC_3BB0A8C1_0x00584001 | 0.0% | Pending

SHC_3BB0A8C1_0x0058401B | 0.0% | Pending

SHC_3BB0A8C1_0x00584026 | 0.0% | Pending
SHC_3BB0A8C1_0x00584026 | 0.0% | instruction selection differs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the entry function, we would kinda need another marker: These thing would only be relevant if we are going for the full/full match. I would consider this infrastructure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I think this is related in: #231
I think I will even just move entry out of the OpenSHC scope in Ghidra.

Comment on lines -59640 to -59646
SHC_3BB0A8C1_0x0053B530 | 0.0% | Pending
SHC_3BB0A8C1_0x0053B530 | 92.3% | global store scheduling

SHC_3BB0A8C1_0x0053B570 | 0.0% | Pending
SHC_3BB0A8C1_0x0053B570 | 90.0% | global store scheduling

SHC_3BB0A8C1_0x0053B5A0 | 0.0% | Pending

SHC_3BB0A8C1_0x0053B5E0 | 0.0% | Pending

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually tested these out of curiosity: Just to be sure, these where not tested together with the parent function?
That would be the only idea I had left: A shared file an them being static helpers. Might have caused pressure issues, assuming it does not inline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried by placing the calling function's body into the same cpp file ( and activating the reimplementation) but it didn't help.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had an epiphany on my way to work today: This works (test since it is easier for me to change the type this way):

    // GLOBAL: STRONGHOLDCRUSADER 0x00EE0FC8
    int test;

    // FUNCTION: STRONGHOLDCRUSADER 0x0053B5E0
    void Version::UpgradeMapUnitsTo_155()
    {
        // DAT_CurrentUnitSlotID::instance = 1;
        for (test = 1; test < 2500; ++test) {
            if (DAT_UnitsState::instance.units[test].logicalState != Units::ULS_INVISIBLE) {
                DAT_UnitsState::instance.units[test].killedFlagUnk = 0;
            }
        }
    }

This also works:

    // GLOBAL: STRONGHOLDCRUSADER 0x00EE0FC8
    int test;

    // FUNCTION: STRONGHOLDCRUSADER 0x0053B5E0
    void Version::UpgradeMapUnitsTo_155()
    {
        test = 1;
        for (int i = 1; i < 2500; ++i) {
            test += 1;
            if (DAT_UnitsState::instance.units[i].logicalState != Units::ULS_INVISIBLE) {
                DAT_UnitsState::instance.units[i].killedFlagUnk = 0;
            }
        }
    }

The compiler knows it adds up to the number and makes it a simple set, since not volatile and no function call is inbetween.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah clever! Great so the compiler makes the devs silly code smarter... (or less dumb). I was breaking my brain on why the 2500 assignment was somehow inside the for loop, but now it makes sense...

Comment thread status/addresses-SHC-3BB0A8C1.txt Outdated

SHC_3BB0A8C1_0x0053B340 | 0.0% | Pending
SHC_3BB0A8C1_0x0053B340 | 81.8% | store sched + jne vs jl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be parent file pressure that could provoke be the change? Also, you do you average on 81, it gets with the unroll a good bit less?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 53% with the unroll. The != 2500 instead of the < 2500 was the good trick to prevent the unroll from happening.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment, might be a similar reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Just the jne vs jl left because of the unroll prevention trick...

Comment thread src/OpenSHC/Map/Units/UnitsState/checkIfCitizenUnitIsAliveBasedOnState.cpp Outdated
Comment thread src/OpenSHC/Map/Units/TribesState/addUnitToSelected.cpp
Comment thread src/OpenSHC/UI/DisplayElements/RenderNoRushDisplayElementUnk.cpp Outdated
Comment thread src/OpenSHC/UI/DisplayElements/RenderNoRushDisplayElementUnk.cpp Outdated
int GameStateStructures::getSalesPrice(int playerID, int resourceType)
{
return (this->mapAndTime.buyAndSalesPriceArray[resourceType].salesPrice / 5)
* MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getSellResourceAmount, this)(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shared file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this signature (the carry over EDX beyond a call, could also happen to ECX) means /GL.

0x45b7fb : -mov edx, ecx
0x45b7fd : -call <OFFSET1>
0x45b802 : -mov edx, dword ptr [edx + esi*8 + 0x51f20]

yes /GL leads to 100%

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I always need to look them up again, but EAX, EDX and ECX are scratch registers. If they are used after a function call in msvc-x86 without being reassigned first, it means:

  • For EDX and ECX that the compiler knew the function body.
  • For EAX that the function may have returned something... or maybe it knew the function body? Have no example for this.

Of course the compiler may also do other stuff when the body is known, but these are very clear signs.

// FUNCTION: STRONGHOLDCRUSADER 0x004549C0
BOOLEnum TextureRenderCore::checkIfGfxTgxStartsWithTransparentPixels(int gfxIndex)
{
return (((byte*)this->gmAndGfxImageDataBuffer)[this->loadedGfxArray[gfxIndex].offsetInBuffer + 8]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume there is some split magic going on if this does not match. Or types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No dice, I tried char * and:

        BOOLEnum TextureRenderCore::checkIfGfxTgxStartsWithTransparentPixels(int gfxIndex)
        {

            GMFileHeaderColorpalette* gm = (GMFileHeaderColorpalette*)(&(
                ((char*)this->gmAndGfxImageDataBuffer)[this->loadedGfxArray[gfxIndex].offsetInBuffer]));

            return (gm->unknown2 & OpenSHC::IO::Graphics::TT_TGX_PIXEL_HEADER)
                == OpenSHC::IO::Graphics::TT_TRANSPARENT_PIXELS;
        }

But stuck at 40%

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic is actually the return:

BOOLEnum TextureRenderCore::checkIfGfxTgxStartsWithTransparentPixels(int gfxIndex)
        {
            int byteIndex = this->loadedGfxArray[gfxIndex].offsetInBuffer + 8;
            byte checkByte = ((byte*)this->gmAndGfxImageDataBuffer)[byteIndex];
            if ((checkByte & OpenSHC::IO::Graphics::TT_TGX_PIXEL_HEADER)
                == OpenSHC::IO::Graphics::TT_TRANSPARENT_PIXELS) {
                return TRUE;
            } else {
                return FALSE;
            }
        }

They rarely use a direct condition result as return. So both variants are always something one could try.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Didn't expect this form though based on the assembly.

Comment on lines +16 to +17
this->entityArray[entityID].logicalState = 2;
this->entityArray[entityID].field72_0xa8 = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check again, I think these are absolute accesses to this object.
They sometimes do this and it gets hidden if ECX is set in Ghidra.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, then fit sinks to 15%

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I read Ghidra wrong, sorry.

As an apology, I found the mismatch reason: The signature is wrong. The function returns the entityID it receives again. 🙃

Notice that EAX is assigned after the call again before the return.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahahaha love it thanks

Comment thread status/addresses-SHC-3BB0A8C1.txt Outdated

SHC_3BB0A8C1_0x0053B340 | 0.0% | Pending
SHC_3BB0A8C1_0x0053B340 | 81.8% | store sched + jne vs jl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment, might be a similar reason.

Comment on lines +16 to +17
this->entityArray[entityID].logicalState = 2;
this->entityArray[entityID].field72_0xa8 = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I read Ghidra wrong, sorry.

As an apology, I found the mismatch reason: The signature is wrong. The function returns the entityID it receives again. 🙃

Notice that EAX is assigned after the call again before the return.

// FUNCTION: STRONGHOLDCRUSADER 0x004549C0
BOOLEnum TextureRenderCore::checkIfGfxTgxStartsWithTransparentPixels(int gfxIndex)
{
return (((byte*)this->gmAndGfxImageDataBuffer)[this->loadedGfxArray[gfxIndex].offsetInBuffer + 8]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic is actually the return:

BOOLEnum TextureRenderCore::checkIfGfxTgxStartsWithTransparentPixels(int gfxIndex)
        {
            int byteIndex = this->loadedGfxArray[gfxIndex].offsetInBuffer + 8;
            byte checkByte = ((byte*)this->gmAndGfxImageDataBuffer)[byteIndex];
            if ((checkByte & OpenSHC::IO::Graphics::TT_TGX_PIXEL_HEADER)
                == OpenSHC::IO::Graphics::TT_TRANSPARENT_PIXELS) {
                return TRUE;
            } else {
                return FALSE;
            }
        }

They rarely use a direct condition result as return. So both variants are always something one could try.

int GameStateStructures::getSalesPrice(int playerID, int resourceType)
{
return (this->mapAndTime.buyAndSalesPriceArray[resourceType].salesPrice / 5)
* MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getSellResourceAmount, this)(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I always need to look them up again, but EAX, EDX and ECX are scratch registers. If they are used after a function call in msvc-x86 without being reassigned first, it means:

  • For EDX and ECX that the compiler knew the function body.
  • For EAX that the function may have returned something... or maybe it knew the function body? Have no example for this.

Of course the compiler may also do other stuff when the body is known, but these are very clear signs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants