Skip to content
This repository was archived by the owner on Jan 1, 2025. It is now read-only.

Commit 9a2c98d

Browse files
authored
Add hook type checking, auto-inject console (#20)
1 parent 31f68eb commit 9a2c98d

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

Python/init.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import bl2sdk
2+
from bl2sdk import *
23
import os
4+
import mypy
35

46
def log(s):
57
s = str(s)
@@ -27,11 +29,11 @@ def Disable(self):
2729

2830
import randomizer
2931

30-
def LoadModList(caller, function, parms, result):
32+
def LoadModList(caller: UObject, function: UFunction, parms: FStruct, result: FStruct) -> bool:
3133
caller.SetStoreHeader("Mods", 0, "By Abahbob", "Mod Manager")
32-
pc = bl2sdk.GetEngine().GamePlayers[0]
34+
pc = GetEngine().GamePlayers[0]
3335
for idx, mod in enumerate(bl2sdk.Mods):
34-
obj = caller.CreateMarketplaceItem(bl2sdk.FMarketplaceContent())
36+
obj = caller.CreateMarketplaceItem(FMarketplaceContent())
3537
obj.SetString(caller.Prop_contentTitleText, mod.Name, pc.GetTranslationContext())
3638
obj.SetString(caller.Prop_descriptionText, mod.Description, pc.GetTranslationContext())
3739
obj.SetString(caller.Prop_offeringId, str(idx), pc.GetTranslationContext())
@@ -40,11 +42,11 @@ def LoadModList(caller, function, parms, result):
4042
caller.PostContentLoaded(True)
4143
return False
4244

43-
bl2sdk.RemoveEngineHook("WillowGame.MarketplaceGFxMovie.OnDownloadableContentListRead", "InjectMods")
44-
bl2sdk.RegisterEngineHook("WillowGame.MarketplaceGFxMovie.OnDownloadableContentListRead", "InjectMods", LoadModList)
45+
RemoveEngineHook("WillowGame.MarketplaceGFxMovie.OnDownloadableContentListRead", "InjectMods")
46+
RegisterEngineHook("WillowGame.MarketplaceGFxMovie.OnDownloadableContentListRead", "InjectMods", LoadModList)
4547

46-
def process_hook(caller, function, parms, result):
47-
pc = bl2sdk.GetEngine().GamePlayers[0]
48+
def process_hook(caller: UObject, function: UFunction, parms: FStruct, result: FStruct) -> bool:
49+
pc = GetEngine().GamePlayers[0]
4850
ControllerId = parms.popInt()
4951
ukey = parms.popFName()
5052
event = parms.popByte()
@@ -65,11 +67,11 @@ def process_hook(caller, function, parms, result):
6567
return False
6668
return True
6769

68-
bl2sdk.RemoveEngineHook("WillowGame.MarketplaceGFxMovie.ShopInputKey", "OpenModMenu")
69-
bl2sdk.RegisterEngineHook("WillowGame.MarketplaceGFxMovie.ShopInputKey", "OpenModMenu", process_hook)
70+
RemoveEngineHook("WillowGame.MarketplaceGFxMovie.ShopInputKey", "OpenModMenu")
71+
RegisterEngineHook("WillowGame.MarketplaceGFxMovie.ShopInputKey", "OpenModMenu", process_hook)
7072

7173

72-
def ReplaceDLCWithMods(caller, stack, result, function):
74+
def ReplaceDLCWithMods(caller: UObject, stack: FFrame, result: FStruct, function: UFunction) -> bool:
7375
EventID = stack.popInt()
7476
Caption = stack.popFString()
7577
bDisabled = stack.popULong()
@@ -81,12 +83,12 @@ def ReplaceDLCWithMods(caller, stack, result, function):
8183
return False
8284

8385

84-
def HookMainMenuPopulateForMods(caller, stack, result, function):
85-
bl2sdk.RegisterScriptHook("WillowGame.WillowScrollingList.AddListItem", "ReplaceDLCWithMods", ReplaceDLCWithMods)
86+
def HookMainMenuPopulateForMods(caller: UObject, stack: FFrame, result: FStruct, function: UFunction) -> bool:
87+
RegisterScriptHook("WillowGame.WillowScrollingList.AddListItem", "ReplaceDLCWithMods", ReplaceDLCWithMods)
8688
caller.Populate(stack.popObject())
87-
bl2sdk.RemoveScriptHook("WillowGame.WillowScrollingList.AddListItem", "ReplaceDLCWithMods")
89+
RemoveScriptHook("WillowGame.WillowScrollingList.AddListItem", "ReplaceDLCWithMods")
8890
stack.SkipFunction()
8991
return False
9092

91-
bl2sdk.RemoveEngineHook("WillowGame.WillowScrollingListDataProviderFrontEnd.Populate", "HookMainMenuPopulateForMods")
92-
bl2sdk.RegisterScriptHook("WillowGame.WillowScrollingListDataProviderFrontEnd.Populate", "HookMainMenuPopulateForMods", HookMainMenuPopulateForMods)
93+
RemoveEngineHook("WillowGame.WillowScrollingListDataProviderFrontEnd.Populate", "HookMainMenuPopulateForMods")
94+
RegisterScriptHook("WillowGame.WillowScrollingListDataProviderFrontEnd.Populate", "HookMainMenuPopulateForMods", HookMainMenuPopulateForMods)

bl2-sdk/BL2-SDK.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,13 +369,12 @@ namespace BL2SDK
369369
Logging::PrintLogHeader();
370370

371371
//// Set console key to Tilde if not already set
372-
//gameConsole = (UConsole *)UObject::FindStr("WillowConsole", "Transient.WillowGameEngine_0:WillowGameViewportClient_0.WillowConsole_0");
373-
//if (gameConsole && (gameConsole->ConsoleKey == FName("None") || gameConsole->ConsoleKey == FName("Undefined")))
374-
// gameConsole->ConsoleKey = FName("Tilde");
372+
gameConsole = (UConsole *)UObject::Find("WillowConsole", "Transient.WillowGameEngine_0:WillowGameViewportClient_0.WillowConsole_0");
373+
if (gameConsole && (gameConsole->ConsoleKey == FName("None") || gameConsole->ConsoleKey == FName("Undefined")))
374+
gameConsole->ConsoleKey = FName("Tilde");
375375

376376
GameHooks::UnrealScriptHookManager->RemoveStaticHook(function, "StartupSDK");
377377
GameHooks::EngineHookManager->Register("WillowGame.WillowGameViewportClient.PostRender", "GetCanvas", getCanvasPostRender);
378-
//GameHooks::UnrealScriptHookManager->Register("Function GearboxFramework.LeviathanService.OnSparkInitialized", "CheckSpark", &SparkReady);
379378

380379
return true;
381380
}

bl2-sdk/CPythonInterface.cpp

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,77 @@
1212
#include <string>
1313
#include <cstdlib>
1414

15+
bool VerifyPythonFunction(py::object funcHook, const char** expectedKeys) {
16+
PyObject *obj = funcHook.ptr();
17+
if (!obj) {
18+
Logging::LogF("[Error] Object passed to hook is null\n");
19+
return false;
20+
}
21+
if (!PyFunction_Check(obj)) {
22+
Logging::LogF("[Error] Object passed to hook is not a function\n");
23+
return false;
24+
}
25+
PyObject *Annotations = PyFunction_GetAnnotations(obj);
26+
if (!Annotations || !PyDict_Check(Annotations)) {
27+
Logging::LogF("[Error] Function passed to hook does not contain annotations\n");
28+
return false;
29+
}
30+
// Python dicts aren't ordered, but we need to assume this dict is to verify the function args
31+
PyObject *Keys = PyDict_Keys(Annotations);
32+
PyObject *Values = PyDict_Values(Annotations);
33+
if (!PyList_Check(Keys) || !PyList_Check(Values)) {
34+
Logging::LogF("[Error] Function passed to hook does not contain annotations\n");
35+
return false;
36+
}
37+
for (int x = 0; x < PyList_GET_SIZE(Keys) - 1; x++) {
38+
PyObject *Key = PyList_GET_ITEM(Keys, x);
39+
char *KeyString = PyUnicode_AsUTF8AndSize(Key, 0);
40+
if (strcmp(KeyString, expectedKeys[x]))
41+
{
42+
Logging::LogF("[Error] Got unexpected argument '%s'. Expected '%s'.\n", KeyString, expectedKeys[x]);
43+
return false;
44+
}
45+
}
46+
return true;
47+
}
48+
1549
void RegisterEngineHook(const std::string& funcName, const std::string& hookName, py::object funcHook) {
16-
GameHooks::EngineHookManager->Register(funcName, hookName, [funcHook](UObject* caller, UFunction* function, void* parms, void* result) {
50+
static const char *params[] = { "caller", "function", "parms", "result", "return" };
51+
if (VerifyPythonFunction(funcHook, params))
52+
GameHooks::EngineHookManager->Register(funcName, hookName, [funcHook](UObject* caller, UFunction* function, void* parms, void* result) {
1753
try {
1854
py::object py_caller = py::cast(caller, py::return_value_policy::reference);
1955
py::object py_function = py::cast(function, py::return_value_policy::reference);
2056
py::object py_parms = py::cast(FStruct(parms), py::return_value_policy::reference);
2157
py::object py_result = py::cast(FStruct(result), py::return_value_policy::reference);
2258
py::object ret = funcHook(py_caller, py_function, py_parms, py_result);
2359
return ret.cast<bool>();
24-
} catch (std::exception e) {
60+
}
61+
catch (std::exception e) {
2562
Logging::LogF(e.what());
2663
}
2764
return true;
28-
}
65+
}
2966
);
3067
}
3168

3269
void RegisterScriptHook(const std::string& funcName, const std::string& hookName, py::object funcHook) {
33-
GameHooks::UnrealScriptHookManager->Register(funcName, hookName, [funcHook](UObject* caller, FFrame& stack, void* const result, UFunction* function) {
70+
static const char *params[] = { "caller", "stack", "result", "function", "return"};
71+
if (VerifyPythonFunction(funcHook, params))
72+
GameHooks::UnrealScriptHookManager->Register(funcName, hookName, [funcHook](UObject* caller, FFrame& stack, void* const result, UFunction* function) {
3473
try {
3574
py::object py_caller = py::cast(caller, py::return_value_policy::reference);
3675
py::object py_stack = py::cast(stack, py::return_value_policy::reference);
3776
py::object py_result = py::cast(FStruct(result), py::return_value_policy::reference);
3877
py::object py_function = py::cast(function, py::return_value_policy::reference);
3978
py::object ret = funcHook(py_caller, py_stack, py_result, py_function);
4079
return ret.cast<bool>();
41-
} catch (std::exception e) {
80+
}
81+
catch (std::exception e) {
4282
Logging::LogF(e.what());
4383
}
4484
return true;
45-
}
85+
}
4686
);
4787
}
4888

@@ -134,8 +174,9 @@ bool CheckPythonCommand(UObject* caller, FFrame& stack, void* const result, UFun
134174
stack.SkipFunction();
135175
return false;
136176
}
137-
stack.Code = code;
138-
return true;
177+
((UConsole *)caller)->eventConsoleCommand(*command);
178+
stack.SkipFunction();
179+
return false;
139180
}
140181

141182
CPythonInterface::CPythonInterface()

0 commit comments

Comments
 (0)