Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
092495e
reimplement: SHC_3BB0A8C1_0x0045F130 100%
TheRedDaemon Sep 6, 2026
04f4f00
reimplement: SHC_3BB0A8C1_0x0045D080 100%
TheRedDaemon Sep 6, 2026
b066a7b
reimplement: SHC_3BB0A8C1_0x0045D0C0 100%
TheRedDaemon Sep 6, 2026
7b2bf25
reimplement: SHC_3BB0A8C1_0x0045D140 100%
TheRedDaemon Sep 6, 2026
b4cd8aa
reimplement: SHC_3BB0A8C1_0x0045D1A0 100%
TheRedDaemon Sep 6, 2026
29815ea
reimplement: SHC_3BB0A8C1_0x0045D200 100%
TheRedDaemon Sep 7, 2026
6483df4
reimplement: SHC_3BB0A8C1_0x0045D370 100%
TheRedDaemon Sep 7, 2026
8bdf42a
reimplement: SHC_3BB0A8C1_0x0045D3C0 100%
TheRedDaemon Sep 8, 2026
a3ddfce
reimplement: SHC_3BB0A8C1_0x0045D430 100%
TheRedDaemon Sep 8, 2026
90ce68b
reimplement: SHC_3BB0A8C1_0x0045D690 100%
TheRedDaemon Sep 8, 2026
8d0f978
reimplement: SHC_3BB0A8C1_0x0045D6C0 100%
TheRedDaemon Sep 9, 2026
fa58174
reimplement: SHC_3BB0A8C1_0x0045D200 update return type
TheRedDaemon Sep 9, 2026
2a57a09
reimplement: SHC_3BB0A8C1_0x0045D740 100%
TheRedDaemon Sep 9, 2026
0c55173
reimplement: SHC_3BB0A8C1_0x0045D890 100%
TheRedDaemon Sep 9, 2026
130ea72
reimplement: SHC_3BB0A8C1_0x0045D8F0 100%
TheRedDaemon Sep 10, 2026
c8f53bb
reimplement: SHC_3BB0A8C1_0x0045D950 100%
TheRedDaemon Sep 10, 2026
c37f34a
reimplement: SHC_3BB0A8C1_0x0045D990 100%
TheRedDaemon Sep 10, 2026
a4c403a
reimplement: SHC_3BB0A8C1_0x0045D9E0 100%
TheRedDaemon Sep 10, 2026
55f21bc
reimplement: SHC_3BB0A8C1_0x0045DA20 100%
TheRedDaemon Sep 10, 2026
56d82f1
reimplement: SHC_3BB0A8C1_0x0045DA40 100%
TheRedDaemon Sep 10, 2026
8c1577a
reimplement: SHC_3BB0A8C1_0x0045DA80 100%
TheRedDaemon Sep 11, 2026
f5b5d96
reimplement: SHC_3BB0A8C1_0x0045DAE0 100%
TheRedDaemon Sep 11, 2026
7434c6e
reimplement: SHC_3BB0A8C1_0x0045DB40 100%
TheRedDaemon Sep 12, 2026
beb3ad7
reimplement: SHC_3BB0A8C1_0x0045F080 and SHC_3BB0A8C1_0x0045F0D0 100%
TheRedDaemon Sep 12, 2026
67c28da
reimplement: SHC_3BB0A8C1_0x0045F240 100%
TheRedDaemon Sep 12, 2026
00fdfc1
reimplement: SHC_3BB0A8C1_0x0045F470 100%
TheRedDaemon Sep 12, 2026
6308b41
reimplement: SHC_3BB0A8C1_0x0045F580 100%
TheRedDaemon Sep 12, 2026
d7a520c
reimplement: SHC_3BB0A8C1_0x0045F5B0 99%
TheRedDaemon Sep 13, 2026
64faf8f
reimplement: SHC_3BB0A8C1_0x004614D0 100%
TheRedDaemon Sep 17, 2026
427813f
reimplement: SHC_3BB0A8C1_0x004613E0 100%
TheRedDaemon Sep 17, 2026
2efc138
reimplement: SHC_3BB0A8C1_0x00461550 100%
TheRedDaemon Sep 17, 2026
16c035e
reimplement: SHC_3BB0A8C1_0x004619D0 100%
TheRedDaemon Sep 17, 2026
6e32ff3
reimplement: SHC_3BB0A8C1_0x00461A20 100%
TheRedDaemon Sep 17, 2026
f994a66
reimplement: SHC_3BB0A8C1_0x00461B90 100%
TheRedDaemon Sep 17, 2026
803645d
reimplement: SHC_3BB0A8C1_0x00461CF0 100%
TheRedDaemon Sep 17, 2026
92db2d4
reimplement: SHC_3BB0A8C1_0x00461E50 100%
TheRedDaemon Sep 17, 2026
77c6a8c
reimplement: SHC_3BB0A8C1_0x00461F90 100%
TheRedDaemon Sep 17, 2026
bba5eba
reimplement: SHC_3BB0A8C1_0x004620F0 100%
TheRedDaemon Sep 17, 2026
7b96214
reimplement: SHC_3BB0A8C1_0x00462150 100%
TheRedDaemon Sep 17, 2026
b07dbc0
reimplement: SHC_3BB0A8C1_0x00462190 100%
TheRedDaemon Sep 17, 2026
8c299e7
reimplement: SHC_3BB0A8C1_0x0045FDC0 buildable, horrible match
TheRedDaemon Sep 18, 2026
a21f1db
reimplement: OpenSHC::Text::TextEditorState adapt to upstream changes
TheRedDaemon Sep 21, 2026
d512f74
reimplement: SHC_3BB0A8C1_0x0045FDC0 70.1%
TheRedDaemon Sep 27, 2026
d799d92
reimplement: OpenSHC::Text::TextEditorState clean up
TheRedDaemon Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions IMPLEMENTATION_CHEAT_SHEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ SEC_RNG::ptr->currentNumber1 % 4
}
```
Consider this, should a switch structure arise with strange fallthrough and loops, like SHC_3BB0A8C1_0x004870B0.
- Non-consecutive case labels will likely result in a mixture of if-else and switch cases, sometimes even only if-elses. These are hard to spot. One sign, outside of weird decompiler artifacts, is that logic might be put inside a lot of conditions that feature only a single variable. Another can be a lot of GOTOs.

- Write a comparison with its operands in the order the assembly shows. `if (a > b)` gives `cmp eax, ecx` where
`if (b < a)` gives `cmp ecx, eax`; flipping seven `iVar6 < lag[n].average1` to `lag[n].average1 > iVar6` took
Expand Down Expand Up @@ -386,6 +387,8 @@ you might have found an unrolled loop. Therefore, try to reproduce the logic in

If GOTOs are present that clearly jump to the start of a loop, but the logic does not allow to do this without a GOTO, for example from a loop inside a loop, you might be able to move the continue or break condition to the outside. Methods could be placing a fitting condition related to the contained loop conditions after the loop or using a boolean flag that then functions as conditional. Both can sometimes be optimized away.

If an explicit loop condition is removed from the compiler, it is able to prove all paths through the loop. If such a case happens where the condition is actually eliminated from the re-implementation, the re-implementation might be missing a condition that is present in the logic somewhere, but removed from the loop condition itself. The optimization pass failed to detect the resulting predictable loop in this latter case and the condition remains.

A global used as a running counter alongside the loop index is usually incremented inside the loop, even when its final
value is a constant. Ghidra shows the folded result: `DAT_CurrentUnitSlotID = 2500;` placed around the loop is really
`DAT_CurrentUnitSlotID = 1;` before it and `DAT_CurrentUnitSlotID += 1;` as the first statement of the body. This was
Expand Down Expand Up @@ -513,7 +516,8 @@ translation unit.
### Parameters

Parameters might not be pushed like normal in certain cases.
Usually, if two functions are followed by each other, the parameters are pushed for the first function, then the call is executed and then parameters for the second function are pushed. If parameters for the second function are pushed before the first call, it might indicate that the first call was executed in place of a variable, to directly feed the return into the second function.
Usually, if two functions are followed by each other, the parameters are pushed for the first function, then the call is executed and then parameters for the second function are pushed. If parameters for the second function are pushed before the first call, it might indicate that the first call was executed in place of a variable, to directly feed the return into the second function.
This possibility is actually universal. If a computation of a value happens between pushes of parameters, it makes it likely the value was computed in-place. The other way around is also possible. If a computed value is created completely or partially before any push, it might have been a temporary.

### Implicit functions

Expand Down Expand Up @@ -542,25 +546,36 @@ They are replaced by specific assembly instructions or otherwise inlined.
A list of all intrinsics can be found in `intrin.h` in the std library, but these are mainly very low level instructions.
However, there are also C functions that can be replaced by intrinsics. The following attempts to extract these from the header:

| Intrinsic | Does |
| --------- | ---------------------------------------------------------------------------------------------- |
| `memcpy` | Copies a memory block. **Does not support overlapping regions** (use `memmove` for overlap). |
| `memset` | Fills a memory block with a byte value. |
| `memcmp` | Compares two memory blocks byte-by-byte. |
| `memchr` | Searches memory for the first occurrence of a byte. |
| `strcpy` | Copies a null-terminated C string. |
| `strlen` | Returns the length of a null-terminated string (excluding `\0`). |
| `strcmp` | Compares two null-terminated C strings. |
| `strcat` | Appends one null-terminated C string to another. |
| `strncpy` | Copies up to N characters from a string; **may not null-terminate** if the source is too long. |
| `strncmp` | Compares up to N characters of two strings. |
| `wcscpy` | Copies a null-terminated wide-character string. |
| `wcslen` | Returns the length of a null-terminated wide string. |
| `ceil` | Rounds a floating-point value upward to the nearest integer value. |
| `abs` | Returns the absolute value of an `int`. |
| `labs` | Returns the absolute value of a `long`. |
| `longjmp` | Restores a saved execution context created by `setjmp`, continuing execution from that point. |
| `_setjmp` | Saves the current execution context for later restoration with `longjmp`. |
| Intrinsic | Does |
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
| `memcpy` | Copies a memory block. **Does not support overlapping regions** (use `memmove` for overlap). |
| `memset` | Fills a memory block with a byte value. |
| `memcmp` | Compares two memory blocks byte-by-byte. |
| `memchr` | Searches memory for the first occurrence of a byte. |
| `strcpy` | Copies a null-terminated C string. |
| `strlen` | Returns the length of a null-terminated string (excluding `\0`). |
| `strcmp` | Compares two null-terminated C strings. |
| `strcat` | Appends one null-terminated C string to another. |
| `strncpy` | Copies up to N characters from a string; **may not null-terminate** if the source is too long. |
| `strncmp` | Compares up to N characters of two strings. |
| `strset` | Sets every character in a null-terminated string to the specified character. |
| `_strset` | Microsoft-specific version of `strset`; sets every character in a null-terminated string to the specified character. |
| `wcscpy` | Copies a null-terminated wide-character string. |
| `wcslen` | Returns the length of a null-terminated wide string. |
| `wcscmp` | Compares two null-terminated wide-character strings. |
| `wcscat` | Appends one null-terminated wide-character string to another. |
| `wcsncpy` | Copies up to N wide characters; **may not null-terminate** if the source is too long. |
| `wcsncmp` | Compares up to N wide characters of two strings. |
| `_wcsset` | Sets every character in a null-terminated wide string to the specified wide character. |
| `ceil` | Returns the smallest integral floating-point value greater than or equal to the argument. |
| `abs` | Returns the absolute value of an `int`. |
| `labs` | Returns the absolute value of a `long`. |
| `longjmp` | Restores a saved execution context created by `setjmp`, continuing execution from that point. |
| `_setjmp` | Saves the current execution context for later restoration with `longjmp`. |
| `_AddressOfReturnAddress` | Returns the address of the current function's return-address storage. |
| `_WriteBarrier` | Prevents certain compiler memory-write reorderings across the barrier. |
| `__trap` | Generates a processor/compiler trap, optionally with arguments. |


In cases where the decompiler seems to perform one of these actions via simple instructions, one can also try one of these functions.

Expand Down
16 changes: 15 additions & 1 deletion src/OpenSHC/OS/OS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace OS {
wchar_t* _wcsncpy(wchar_t* _Dest, wchar_t* _Source, size_t _Count) { return wcsncpy(_Dest, _Source, _Count); }

// STUB: STRONGHOLDCRUSADER 0x00580B69
int __wcsicmp(wchar_t* _Str1, wchar_t* _Str2) { return _wcsicmp(_Str1, _Str2); }
int __wcsicmp(wchar_t const* _Str1, wchar_t const* _Str2) { return _wcsicmp(_Str1, _Str2); }

// Only jumps to _free in game version
// STUB: STRONGHOLDCRUSADER 0x00580DC1
Expand Down Expand Up @@ -146,5 +146,19 @@ namespace OS {
// STUB: STRONGHOLDCRUSADER 0x00583D55
void _exit(int _Code) { exit(_Code); }

// STUB: STRONGHOLDCRUSADER 0x005807A8
int _fwprintf(FILE* stream, wchar_t const* format, ...)
{
// needed for proxy
va_list args;
va_start(args, format);
int result = vfwprintf(stream, format, args);
va_end(args);
return result;
}

// STUB: STRONGHOLDCRUSADER 0x00580C0B
long __wtol(wchar_t const* str) { return _wtol(str); }

}
}
43 changes: 43 additions & 0 deletions src/OpenSHC/Text/TextEditorState/Constructor_TextEditorState.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "../TextEditorState.func.hpp"

#include "OpenSHC/OS.func.hpp"

#include "OpenSHC/Globals/DAT_UserHelpDefinedData.hpp"

namespace OpenSHC {
namespace Text {

// FUNCTION: STRONGHOLDCRUSADER 0x0045F130
TextEditorState* TextEditorState::Constructor_TextEditorState()
{
this->isDialogStateInitialized = 0;
this->helpDialogVariant = 0;
this->field48_0x23960 = 0;
this->helpDialogSubMode = 0;
this->useAlternateHelpTab = 0;
this->customHelpTextLength = 0;
this->pendingTokenTypeToSkip = 0;
this->useWideHelpLayout = 0;

MACRO_CALL_MEMBER(TextEditorState_Func::resetHelpStateFields, this)();

this->DAT_PointerToTemporaryTextMemory = (LPWSTR)MACRO_CALL(OS_Func::_malloc)(40000);
this->customTextMaxLength = -1;

// FIXME:: Requires a finishing zero case, or it will not set the counter
for (int counter = 0; counter < 500; ++counter) {
if (strlen(DAT_UserHelpDefinedData::instance.HelpSections[counter])) {
continue;
}
this->counter = counter;
break;
}

for (int i = 0; i < 20; ++i) {
this->intArray1[i] = 0;
}
return this;
}

}
}
27 changes: 27 additions & 0 deletions src/OpenSHC/Text/TextEditorState/bltTextToScreenIfNeedBe.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "../TextEditorState.func.hpp"

#include "OpenSHC/UI/Rendering/WindowAndDirectDraw.func.hpp"

#include "OpenSHC/Globals/DAT_TextEditorState.hpp"
#include "OpenSHC/Globals/DAT_WindowAndDirectDraw.hpp"

namespace OpenSHC {
namespace Text {

// FUNCTION: STRONGHOLDCRUSADER 0x0045D690
void TextEditorState::bltTextToScreenIfNeedBe()
{
if (!DAT_TextEditorState::instance.field48_0x23960) {
return;
}
if (this->savedMenuViewType == UI::Enums::MVT_MAP_EDITOR_LANDSCAPING
|| this->savedMenuViewType == UI::Enums::MVT_BUILD_MENU
|| this->savedMenuViewType == UI::Enums::MVT_BUILDING_AND_STATUS_MENU) {
MACRO_CALL_MEMBER(UI::Rendering::WindowAndDirectDraw_Func::bltMapGameSurfaceToScreenMenuSurfaceComplete,
DAT_WindowAndDirectDraw::ptr)();
}
DAT_TextEditorState::instance.field48_0x23960 = FALSE;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "../TextEditorState.func.hpp"

#include "OpenSHC/Game/GameCore.func.hpp"
#include "OpenSHC/OS.func.hpp"
#include "OpenSHC/UI/MenuModalComposition.func.hpp"
#include "OpenSHC/Util/WideCharMultiByteState.func.hpp"

#include "OpenSHC/Globals/DAT_GameCore.hpp"
#include "OpenSHC/Globals/DAT_MenuModalComposition1.hpp"
#include "OpenSHC/Globals/DAT_MenuView_TriggerPrepare.hpp"
#include "OpenSHC/Globals/DAT_WideCharMultiByteState.hpp"

namespace OpenSHC {
namespace Text {

// FUNCTION: STRONGHOLDCRUSADER 0x0045F240
void TextEditorState::closeHelpDialogAndReturnToMenu()
{
if (!this->isDialogStateInitialized) {
return;
}
MACRO_CALL_MEMBER(TextEditorState_Func::resetHelpStateFields, this)();
this->isDialogStateInitialized = 0;

if (this->isCustomTextMode) {
MACRO_CALL_MEMBER(UI::MenuModalComposition_Func::activateModalDialog, DAT_MenuModalComposition1::ptr)(
UI::Enums::MMT_NONE, FALSE);
for (int i = 0; i < 20; ++i) {
this->intArray1[i] = 0;
}
if (this->customHelpTextPointer) {
if (!this->isCustomHelpTextWide) {
MACRO_CALL_MEMBER(Util::WideCharMultiByteState_Func::wideCharToMultiByteWithSize,
DAT_WideCharMultiByteState::ptr)(this->customHelpTextPointer,
this->DAT_PointerToTemporaryTextMemory, this->customHelpTextBufferSize);
} else {
MACRO_CALL(OS_Func::_wcsncpy)((wchar_t*)this->customHelpTextPointer,
this->DAT_PointerToTemporaryTextMemory, this->customHelpTextBufferSize);
}
}
DAT_MenuView_TriggerPrepare::instance = TRUE;

} else if (this->helpDialogVariant == 0) {

if (this->savedMenuViewType == UI::Enums::MVT_BUILD_MENU) {
DAT_GameCore::instance.buildmenuMenuTabToSwitchTo.tabType = this->savedActiveMenuTab;
MACRO_CALL_MEMBER(Game::GameCore_Func::switchToMenuView, DAT_GameCore::ptr)(
(UI::Enums::MenuViewType)this->savedMenuViewType, 0);

} else if (this->savedMenuViewType == UI::Enums::MVT_MAP_EDITOR_LANDSCAPING) {
DAT_GameCore::instance.landscapingmenuMenuTabToSwitchTo = this->savedActiveMenuTab;
MACRO_CALL_MEMBER(Game::GameCore_Func::switchToMenuView, DAT_GameCore::ptr)(
(UI::Enums::MenuViewType)this->savedMenuViewType, 0);

} else if (this->savedMenuViewType == UI::Enums::MVT_BUILDING_AND_STATUS_MENU) {
DAT_GameCore::instance.buildingandstatusmenuMenuTabToSwitchTo = this->savedActiveMenuTab;
MACRO_CALL_MEMBER(Game::GameCore_Func::switchToMenuView, DAT_GameCore::ptr)(
(UI::Enums::MenuViewType)this->savedMenuViewType, 0);

} else {
DAT_GameCore::instance.menuTabToSwitchTo.tabType = this->savedActiveMenuTab;
MACRO_CALL_MEMBER(Game::GameCore_Func::switchToMenuView, DAT_GameCore::ptr)(
(UI::Enums::MenuViewType)this->savedMenuViewType, 0);
}

} else {
MACRO_CALL_MEMBER(UI::MenuModalComposition_Func::activateModalDialog, DAT_MenuModalComposition1::ptr)(
UI::Enums::MMT_NONE, FALSE);
for (int i = 0; i < 20; ++i) {
this->intArray1[i] = 0;
}
}

this->helpDialogVariant = 0;
}

}
}
34 changes: 34 additions & 0 deletions src/OpenSHC/Text/TextEditorState/drawBorderStyle0x20.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "../TextEditorState.func.hpp"

#include "OpenSHC/Globals/DAT_00df2964.hpp"
#include "OpenSHC/Globals/DAT_00df296c.hpp"
#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp"
#include "OpenSHC/Globals/INT_00df2968.hpp"
#include "OpenSHC/Globals/INT_00df2970.hpp"

namespace OpenSHC {
namespace Text {

// FUNCTION: STRONGHOLDCRUSADER 0x0045D6C0
void TextEditorState::drawBorderStyle0x20(int dialogX, int dialogY, int dialogWidth, int dialogHeight)
{
this->dialogX = dialogX;
this->dialogY = dialogY;
this->dialogWidth = dialogWidth;
this->dialogHeight = dialogHeight;

int const contentX = this->dialogX + DAT_00df2964::instance;
int const contentY = this->dialogY + DAT_00df296c::instance;
int const contentHeight = this->dialogHeight - INT_00df2970::instance;
int const contentWidth = this->dialogWidth - INT_00df2968::instance;
this->dialogContentX = contentX;
this->dialogContentY = contentY;
this->dialogContentWidth = contentWidth;
this->dialogContentHeight = contentHeight;

this->helpContentScrollX = DAT_ViewportRenderState::instance.viewportState.currentCameraOffsetX;
this->helpContentScrollY = DAT_ViewportRenderState::instance.viewportState.currentCameraOffsetY;
}

}
}
Loading
Loading