Skip to content

Commit f171ea6

Browse files
committed
Add ASI to show some of player's appearance properties on a HUD.
Update ASI template to use posthook
1 parent 1d858fc commit f171ea6

8 files changed

+459
-12
lines changed

LE1-ASI-Plugins.sln

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.31424.327
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31919.166
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LE1ASITemplate", "LE1ASITemplate\LE1ASITemplate.vcxproj", "{A0D8B7E4-D7DA-43FF-AEE1-08C9103B2E18}"
77
EndProject
@@ -27,6 +27,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "2DAPrinter", "2DAPrinter\2D
2727
EndProject
2828
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LE1-SDK", "LE1-SDK\LE1-SDK.vcxproj", "{E8071203-085E-4B90-9C5C-39D546ED6FE2}"
2929
EndProject
30+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LE1AppearanceTest", "LE1AppearanceTest\LE1AppearanceTest.vcxproj", "{F334D9C9-36B2-4D64-A052-E1C13E322A23}"
31+
EndProject
3032
Global
3133
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3234
Debug|x64 = Debug|x64
@@ -113,6 +115,14 @@ Global
113115
{E8071203-085E-4B90-9C5C-39D546ED6FE2}.Release|x64.Build.0 = Release|x64
114116
{E8071203-085E-4B90-9C5C-39D546ED6FE2}.Release|x86.ActiveCfg = Release|Win32
115117
{E8071203-085E-4B90-9C5C-39D546ED6FE2}.Release|x86.Build.0 = Release|Win32
118+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Debug|x64.ActiveCfg = Debug|x64
119+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Debug|x64.Build.0 = Debug|x64
120+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Debug|x86.ActiveCfg = Debug|x64
121+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Debug|x86.Build.0 = Debug|x64
122+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Release|x64.ActiveCfg = Release|x64
123+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Release|x64.Build.0 = Release|x64
124+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Release|x86.ActiveCfg = Release|x64
125+
{F334D9C9-36B2-4D64-A052-E1C13E322A23}.Release|x86.Build.0 = Release|x64
116126
EndGlobalSection
117127
GlobalSection(SolutionProperties) = preSolution
118128
HideSolutionNode = FALSE

LE1ASITemplate.zip

-8 Bytes
Binary file not shown.

LE1ASITemplate/LE1ASITemplate.cpp

+3-10
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ SPI_PLUGINSIDE_ASYNCATTACH;
1616

1717
typedef void (*tProcessEvent)(UObject* Context, UFunction* Function, void* Parms, void* Result);
1818
tProcessEvent ProcessEvent = nullptr;
19-
2019
tProcessEvent ProcessEvent_orig = nullptr;
20+
2121
void ProcessEvent_hook(UObject* Context, UFunction* Function, void* Parms, void* Result)
2222
{
2323
ProcessEvent_orig(Context, Function, Parms, Result);
@@ -30,15 +30,8 @@ SPI_IMPLEMENT_ATTACH
3030

3131
auto _ = SDKInitializer::Instance();
3232

33-
if (auto rc = InterfacePtr->FindPattern((void**)&ProcessEvent, "40 55 41 56 41 57 48 81 EC 90 00 00 00 48 8D 6C 24 20");
34-
rc != SPIReturn::Success)
35-
{
36-
writeln(L"Attach - failed to find ProcessEvent pattern: %d / %s", rc, SPIReturnToString(rc));
37-
return false;
38-
}
39-
40-
41-
if (auto rc = InterfacePtr->InstallHook(MYHOOK "ProcessEvent", ProcessEvent, ProcessEvent_hook, (void**)&ProcessEvent_orig);
33+
INIT_FIND_PATTERN_POSTHOOK(ProcessEvent, /* 40 55 41 56 41 */ "57 48 81 EC 90 00 00 00 48 8D 6C 24 20");
34+
if (auto rc = InterfacePtr->InstallHook(MYHOOK "ProcessEvent", ProcessEvent, ProcessEvent_hook, (void**)&ProcessEvent_orig);
4235
rc != SPIReturn::Success)
4336
{
4437
writeln(L"Attach - failed to hook ProcessEvent: %d / %s", rc, SPIReturnToString(rc));

LE1AppearanceTest/AppearanceHUD.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "AppearanceHUD.h"

LE1AppearanceTest/AppearanceHUD.h

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#pragma once
2+
#include <fstream>
3+
#include <ostream>
4+
#include <streambuf>
5+
#include <sstream>
6+
#include "../LE1-SDK/SdkHeaders.h"
7+
8+
/// <summary>
9+
/// Abstract class for creating custom HUDs similar to StreamingLevelsHUD
10+
/// </summary>
11+
class CustomHUD
12+
{
13+
public:
14+
bool ShouldDraw = true;
15+
CustomHUD(LPSTR windowName = "MassEffect")
16+
{
17+
_windowName = windowName;
18+
}
19+
virtual void Update(UCanvas* canvas)
20+
{
21+
_canvas = canvas;
22+
}
23+
virtual void Draw() = 0;
24+
25+
protected:
26+
UCanvas* _canvas;
27+
float textScale = 1.0f;
28+
float lineHeight = 12.0f;
29+
int yIndex;
30+
LPSTR _windowName;
31+
void SetTextScale()
32+
{
33+
HWND activeWindow = FindWindowA(NULL, _windowName);
34+
if (activeWindow)
35+
{
36+
RECT rcOwner;
37+
if (GetWindowRect(activeWindow, &rcOwner))
38+
{
39+
long width = rcOwner.right - rcOwner.left;
40+
long height = rcOwner.bottom - rcOwner.top;
41+
42+
if (width > 2560 && height > 1440)
43+
{
44+
textScale = 2.0f;
45+
}
46+
else if (width > 1920 && height > 1080)
47+
{
48+
textScale = 1.5f;
49+
}
50+
else
51+
{
52+
textScale = 1.0f;
53+
}
54+
lineHeight = 12.0f * textScale;
55+
}
56+
}
57+
}
58+
59+
void RenderText(std::wstring msg, const float x, const float y, const char r, const char g, const char b, const float alpha)
60+
{
61+
if (_canvas)
62+
{
63+
_canvas->SetDrawColor(r, g, b, alpha * 255);
64+
_canvas->SetPos(x, y + 64); //+ is Y start. To prevent overlay on top of the power bar thing
65+
_canvas->DrawTextW(FString{ const_cast<wchar_t*>(msg.c_str()) }, 1, textScale, textScale, nullptr);
66+
}
67+
}
68+
69+
void RenderTextLine(std::wstring msg, const char r, const char g, const char b, const float alpha)
70+
{
71+
RenderText(msg, 5, lineHeight * yIndex, r, g, b, alpha);
72+
yIndex++;
73+
}
74+
75+
void RenderName(const wchar_t* label, FName name)
76+
{
77+
wchar_t output[512];
78+
swprintf_s(output, 512, L"%s: %S", label, name.GetName());
79+
RenderTextLine(output, 0, 255, 0, 1.0f);
80+
}
81+
82+
void RenderString(const wchar_t* label, FString str)
83+
{
84+
wchar_t output[512];
85+
swprintf_s(output, 512, L"%s: %s", label, str.Data);
86+
RenderTextLine(output, 0, 255, 0, 1.0f);
87+
}
88+
89+
void RenderInt(const wchar_t* label, int value)
90+
{
91+
wchar_t output[512];
92+
swprintf_s(output, 512, L"%s: %d", label, value);
93+
RenderTextLine(output, 0, 255, 0, 1.0f);
94+
}
95+
96+
void RenderBool(const wchar_t* label, bool value)
97+
{
98+
wchar_t output[512];
99+
if (value)
100+
{
101+
swprintf_s(output, 512, L"%s: %s", label, L"True");
102+
}
103+
else
104+
{
105+
swprintf_s(output, 512, L"%s: %s", label, L"False");
106+
}
107+
RenderTextLine(output, 0, 255, 0, 1.0f);
108+
}
109+
};
110+
111+
class AppearanceHUD : public CustomHUD
112+
{
113+
public:
114+
AppearanceHUD() : CustomHUD("MassEffect") { }
115+
116+
void Update(UCanvas* canvas, ABioPawn* pawn)
117+
{
118+
__super::Update(canvas);
119+
_pawn = pawn;
120+
}
121+
122+
void Draw() override
123+
{
124+
if (!ShouldDraw) return;
125+
126+
SetTextScale();
127+
yIndex = 3;
128+
129+
RenderTextLine(L"Appearance Profile", 255, 229, 0, 1.0f);
130+
if (_pawn)
131+
{
132+
RenderName(L"Pawn Name", _pawn->Name);
133+
auto aType = _pawn->m_oBehavior->m_oActorType;
134+
if (aType)
135+
{
136+
RenderString(L"Actor Game Name", aType->ActorGameName);
137+
}
138+
yIndex++;
139+
RenderMeshSection();
140+
if (aType && aType->IsA(UBioPawnChallengeScaledType::StaticClass()))
141+
{
142+
yIndex++;
143+
RenderAppearanceSection(static_cast<UBioPawnChallengeScaledType*>(aType), _pawn->m_oBehavior->m_oAppearanceType);
144+
}
145+
}
146+
else
147+
{
148+
RenderTextLine(L"In Mako", 0, 255, 0, 1.0f);
149+
}
150+
}
151+
private:
152+
ABioPawn* _pawn;
153+
154+
void RenderMeshSection()
155+
{
156+
RenderTextLine(L"Meshes:", 255, 229, 0, 1.0f);
157+
if (_pawn->m_oHairMesh)
158+
{
159+
RenderName(L"Hair Mesh", _pawn->m_oHairMesh->SkeletalMesh->Name);
160+
}
161+
if (_pawn->m_oHeadMesh)
162+
{
163+
RenderName(L"Head Mesh", _pawn->m_oHeadMesh->SkeletalMesh->Name);
164+
}
165+
if (_pawn->m_oHeadGearMesh)
166+
{
167+
RenderName(L"Head Gear Mesh", _pawn->m_oHeadGearMesh->SkeletalMesh->Name);
168+
}
169+
if (_pawn->m_oFacePlateMesh)
170+
{
171+
RenderName(L"Face Plate Mesh", _pawn->m_oFacePlateMesh->SkeletalMesh->Name);
172+
}
173+
if (_pawn->m_oVisorMesh)
174+
{
175+
RenderName(L"Visor Mesh", _pawn->m_oVisorMesh->SkeletalMesh->Name);
176+
}
177+
}
178+
179+
void RenderAppearanceSection(UBioPawnChallengeScaledType* type, UBioInterface_Appearance* interfaceAppr)
180+
{
181+
auto appearance = type->m_oAppearance;
182+
auto body = appearance->Body;
183+
auto headGear = body->m_oHeadGearAppearance;
184+
auto headGearSettings = headGear->m_oSettings;
185+
186+
auto iApprPawn = reinterpret_cast<UBioInterface_Appearance_Pawn*>(interfaceAppr);
187+
188+
RenderTextLine(L"Bio_Appr_Character:", 255, 229, 0, 1.0f);
189+
RenderName(L"Headgear Prefix:", headGear->m_nmPrefix);
190+
if (iApprPawn)
191+
{
192+
auto armorSpecIdx = iApprPawn->m_oSettings->m_oBodySettings->m_eArmorType;
193+
RenderInt(L"Armor Spec Index", armorSpecIdx);
194+
auto armorSpec = headGear->m_aArmorSpec[armorSpecIdx];
195+
if (iApprPawn->m_oSettings->m_oBodySettings->m_oHeadGearSettings)
196+
{
197+
auto hgrVslOvr = iApprPawn->m_oSettings->m_oBodySettings->m_oHeadGearSettings->m_eVisualOverride;
198+
RenderInt(L"Headgear Visual Override Armor Spec", hgrVslOvr);
199+
}
200+
// Need good way of determining this index
201+
auto modelSpecIdx = 2;
202+
if (modelSpecIdx < armorSpec.m_aModelSpec.Count)
203+
{
204+
auto modelSpec = armorSpec.m_aModelSpec.Data[modelSpecIdx];
205+
RenderInt(L"Hgr ModelSpec Index (currently hardcoded)", modelSpecIdx);
206+
RenderInt(L"Hgr ModelSpec MaterialConfigCount", modelSpec.m_nMaterialConfigCount);
207+
RenderBool(L"Hgr ModelSpec bIsHairHidden", modelSpec.m_bIsHairHidden);
208+
RenderBool(L"Hgr ModelSpec bIsHeadHidden", modelSpec.m_bIsHeadHidden);
209+
RenderBool(L"Hgr ModelSpec bSuppressFacePlate", modelSpec.m_bSuppressFacePlate);
210+
RenderBool(L"Hgr ModelSpec bSuppressVisor", modelSpec.m_bSuppressVisor);
211+
}
212+
}
213+
}
214+
};
215+
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include <string>
2+
3+
#include "AppearanceHUD.h"
4+
#include "../LE1-SDK/Interface.h"
5+
#include "../LE1-SDK/Common.h"
6+
#include "../LE1-SDK/SdkHeaders.h"
7+
#include "../LE1-SDK/ME3TweaksHeader.h"
8+
#include "../ConsoleCommandParsing.h"
9+
10+
11+
#define MYHOOK "HBTesting_"
12+
13+
SPI_PLUGINSIDE_SUPPORT(L"HBTesting", L"1.0.0", L"HenBagle", SPI_GAME_LE1, 2);
14+
SPI_PLUGINSIDE_POSTLOAD;
15+
SPI_PLUGINSIDE_ASYNCATTACH;
16+
17+
AppearanceHUD* customHud = new AppearanceHUD;
18+
19+
// ======================================================================
20+
// ExecHandler hook
21+
// ======================================================================
22+
23+
typedef unsigned (*tExecHandler)(UEngine* Context, wchar_t* cmd, void* unk);
24+
tExecHandler ExecHandler = nullptr;
25+
tExecHandler ExecHandler_orig = nullptr;
26+
unsigned ExecHandler_hook(UEngine* Context, wchar_t* cmd, void* unk)
27+
{
28+
if (ExecHandler_orig(Context, cmd, unk))
29+
{
30+
return TRUE;
31+
}
32+
if (IsCmd(&cmd, L"appearance"))
33+
{
34+
customHud->ShouldDraw = !customHud->ShouldDraw;
35+
return TRUE;
36+
}
37+
return FALSE;
38+
}
39+
40+
ABioPawn* GetPlayer(AWorldInfo* worldInfo)
41+
{
42+
if (!worldInfo) return nullptr;
43+
auto bioWorldInfo = (ABioWorldInfo*)worldInfo;
44+
auto pc = bioWorldInfo->LocalPlayerController;
45+
if (!pc) return nullptr;
46+
auto player = pc->Pawn;
47+
if (!player || !player->IsA(ABioPawn::StaticClass())) return nullptr;
48+
else return static_cast<ABioPawn*>(player);
49+
}
50+
51+
// ProcessEvent hook
52+
// ======================================================================
53+
54+
typedef void (*tProcessEvent)(UObject* Context, UFunction* Function, void* Parms, void* Result);
55+
tProcessEvent ProcessEvent = nullptr;
56+
tProcessEvent ProcessEvent_orig = nullptr;
57+
58+
void ProcessEvent_hook(UObject* Context, UFunction* Function, void* Parms, void* Result)
59+
{
60+
static AppearanceHUD* hud = nullptr;
61+
if (!strcmp(Function->GetFullName(), "Function SFXGame.BioHUD.PostRender"))
62+
{
63+
auto biohud = reinterpret_cast<ABioHUD*>(Context);
64+
if (biohud != nullptr)
65+
{
66+
customHud->Update(((ABioHUD*)Context)->Canvas, GetPlayer(biohud->WorldInfo));
67+
customHud->Draw();
68+
}
69+
}
70+
ProcessEvent_orig(Context, Function, Parms, Result);
71+
}
72+
73+
74+
SPI_IMPLEMENT_ATTACH
75+
{
76+
Common::OpenConsole();
77+
78+
auto _ = SDKInitializer::Instance();
79+
80+
INIT_FIND_PATTERN_POSTHOOK(ProcessEvent, /* 40 55 41 56 41 */ "57 48 81 EC 90 00 00 00 48 8D 6C 24 20");
81+
if (auto rc = InterfacePtr->InstallHook(MYHOOK "ProcessEvent", ProcessEvent, ProcessEvent_hook, (void**)&ProcessEvent_orig);
82+
rc != SPIReturn::Success)
83+
{
84+
writeln(L"Attach - failed to hook ProcessEvent: %d / %s", rc, SPIReturnToString(rc));
85+
return false;
86+
}
87+
88+
if (const auto rc = InterfacePtr->FindPattern(reinterpret_cast<void**>(&ExecHandler), "48 8b c4 48 89 50 10 55 56 57 41 54 41 55 41 56 41 57 48 8d a8 d8 fe ff ff");
89+
rc != SPIReturn::Success)
90+
{
91+
writeln(L"Attach - failed to find ExecHandler pattern: %d / %s", rc, SPIReturnToString(rc));
92+
return false;
93+
}
94+
if (const auto rc = InterfacePtr->InstallHook(MYHOOK "ExecHandler", ExecHandler, ExecHandler_hook, reinterpret_cast<void**>(&ExecHandler_orig));
95+
rc != SPIReturn::Success)
96+
{
97+
writeln(L"Attach - failed to hook ExecHandler: %d / %s", rc, SPIReturnToString(rc));
98+
return false;
99+
}
100+
101+
return true;
102+
}
103+
104+
SPI_IMPLEMENT_DETACH
105+
{
106+
Common::CloseConsole();
107+
return true;
108+
}

0 commit comments

Comments
 (0)