Skip to content
Draft
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
13f5b30
reimplement: SHC_3BB0A8C1_0x0045F130 100%
TheRedDaemon Sep 6, 2026
f9a581a
reimplement: SHC_3BB0A8C1_0x0045D080 100%
TheRedDaemon Sep 6, 2026
880c0f9
reimplement: SHC_3BB0A8C1_0x0045D0C0 100%
TheRedDaemon Sep 6, 2026
fd80b76
reimplement: SHC_3BB0A8C1_0x0045D140 100%
TheRedDaemon Sep 6, 2026
7dd592e
reimplement: SHC_3BB0A8C1_0x0045D1A0 100%
TheRedDaemon Sep 6, 2026
53672fc
reimplement: SHC_3BB0A8C1_0x0045D200 100%
TheRedDaemon Sep 7, 2026
c300794
reimplement: SHC_3BB0A8C1_0x0045D370 100%
TheRedDaemon Sep 7, 2026
ade6d87
reimplement: SHC_3BB0A8C1_0x0045D3C0 100%
TheRedDaemon Sep 8, 2026
ce5a54b
reimplement: SHC_3BB0A8C1_0x0045D430 100%
TheRedDaemon Sep 8, 2026
2833586
reimplement: SHC_3BB0A8C1_0x0045D690 100%
TheRedDaemon Sep 8, 2026
636c069
reimplement: SHC_3BB0A8C1_0x0045D6C0 100%
TheRedDaemon Sep 9, 2026
0403f6d
reimplement: SHC_3BB0A8C1_0x0045D200 update return type
TheRedDaemon Sep 9, 2026
73097c3
reimplement: SHC_3BB0A8C1_0x0045D740 100%
TheRedDaemon Sep 9, 2026
4eaf8cd
reimplement: SHC_3BB0A8C1_0x0045D890 100%
TheRedDaemon Sep 9, 2026
8b63cb6
reimplement: SHC_3BB0A8C1_0x0045D8F0 100%
TheRedDaemon Sep 10, 2026
974da2a
reimplement: SHC_3BB0A8C1_0x0045D950 100%
TheRedDaemon Sep 10, 2026
fffd7d2
reimplement: SHC_3BB0A8C1_0x0045D990 100%
TheRedDaemon Sep 10, 2026
5ebf162
reimplement: SHC_3BB0A8C1_0x0045D9E0 100%
TheRedDaemon Sep 10, 2026
5b0f45f
reimplement: SHC_3BB0A8C1_0x0045DA20 100%
TheRedDaemon Sep 10, 2026
b7201f0
reimplement: SHC_3BB0A8C1_0x0045DA40 100%
TheRedDaemon Sep 10, 2026
530d984
reimplement: SHC_3BB0A8C1_0x0045DA80 100%
TheRedDaemon Sep 11, 2026
0cc6a81
reimplement: SHC_3BB0A8C1_0x0045DAE0 100%
TheRedDaemon Sep 11, 2026
eb6db60
reimplement: SHC_3BB0A8C1_0x0045DB40 100%
TheRedDaemon Sep 12, 2026
d31cec6
reimplement: SHC_3BB0A8C1_0x0045F080 and SHC_3BB0A8C1_0x0045F0D0 100%
TheRedDaemon Sep 12, 2026
99d0d80
reimplement: SHC_3BB0A8C1_0x0045F240 100%
TheRedDaemon Sep 12, 2026
841f1c1
reimplement: SHC_3BB0A8C1_0x0045F470 100%
TheRedDaemon Sep 12, 2026
46eadc1
reimplement: SHC_3BB0A8C1_0x0045F580 100%
TheRedDaemon Sep 12, 2026
c4f09ab
reimplement: SHC_3BB0A8C1_0x0045F5B0 99%
TheRedDaemon Sep 13, 2026
28033c6
reimplement: SHC_3BB0A8C1_0x004614D0 100%
TheRedDaemon Sep 17, 2026
b67612d
reimplement: SHC_3BB0A8C1_0x004613E0 100%
TheRedDaemon Sep 17, 2026
cb57047
reimplement: SHC_3BB0A8C1_0x00461550 100%
TheRedDaemon Sep 17, 2026
fd51e6f
reimplement: SHC_3BB0A8C1_0x004619D0 100%
TheRedDaemon Sep 17, 2026
0095072
reimplement: SHC_3BB0A8C1_0x00461A20 100%
TheRedDaemon Sep 17, 2026
f88cb6a
reimplement: SHC_3BB0A8C1_0x00461B90 100%
TheRedDaemon Sep 17, 2026
bbdaaa8
reimplement: SHC_3BB0A8C1_0x00461CF0 100%
TheRedDaemon Sep 17, 2026
2163654
reimplement: SHC_3BB0A8C1_0x00461E50 100%
TheRedDaemon Sep 17, 2026
026ec31
reimplement: SHC_3BB0A8C1_0x00461F90 100%
TheRedDaemon Sep 17, 2026
0299b70
reimplement: SHC_3BB0A8C1_0x004620F0 100%
TheRedDaemon Sep 17, 2026
5707f5e
reimplement: SHC_3BB0A8C1_0x00462150 100%
TheRedDaemon Sep 17, 2026
ca56c28
reimplement: SHC_3BB0A8C1_0x00462190 100%
TheRedDaemon Sep 17, 2026
32ef0d5
reimplement: SHC_3BB0A8C1_0x0045FDC0 buildable, horrible match
TheRedDaemon Sep 18, 2026
d5dee2b
reimplement: OpenSHC::Text::TextEditorState adapt to upstream changes
TheRedDaemon Sep 21, 2026
d12e11f
reimplement: SHC_3BB0A8C1_0x0045FDC0 70.1%
TheRedDaemon Sep 27, 2026
5f48cc0
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
56 changes: 36 additions & 20 deletions IMPLEMENTATION_CHEAT_SHEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,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.

### Loops

Expand Down Expand Up @@ -357,6 +358,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 @@ -399,6 +402,7 @@ unrolled body, write `for (int i = 1; i != 2500; ++i)` instead of `i < 2500` (SH
declared where the original's `xor esi, esi` sits (here before an earlier early-return test, which then compares
against that zero register) this took `generateSiegeCreationInformation` from 39.4% to 100%.


### GOTO

A function may contain multiple GOTOs.
Expand Down Expand Up @@ -449,7 +453,8 @@ so it is built with `/GL`. SHC_3BB0A8C1_0x00504EE0 went from 38.5% to 100% and S
### 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 @@ -478,25 +483,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); }

}
}
2 changes: 1 addition & 1 deletion src/OpenSHC/Text/TextEditorState.func.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ namespace Text {
&TextEditorState::openMapDescriptionEditorDialog)
openMapDescriptionEditorDialog;

MACRO_FUNCTION_RESOLVER(void (TextEditorState::*)(char*, undefined4), false, Address::SHC_3BB0A8C1_0x004620F0,
MACRO_FUNCTION_RESOLVER(void (TextEditorState::*)(char*, int), false, Address::SHC_3BB0A8C1_0x004620F0,
&TextEditorState::setCustomHelpText)
setCustomHelpText;

Expand Down
4 changes: 2 additions & 2 deletions src/OpenSHC/Text/TextEditorState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace Text {
// Constructor
TextEditorState* Constructor_TextEditorState();

void setHelpWindowBounds(int x, int y, int height, int width);
void setHelpWindowBounds(int x, int y, int width, int height);

void resetHelpStateFields();

Expand Down Expand Up @@ -168,7 +168,7 @@ namespace Text {

void openMapDescriptionEditorDialog(int sectionId);

void setCustomHelpText(char* helpText, undefined4 bufferSize);
void setCustomHelpText(char* helpText, int bufferSize);

void popHelpDialogStack();

Expand Down
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