├── .gitattributes
├── .gitignore
├── Mods
├── BackpackManager
│ └── __init__.py
├── ModMenu
│ ├── DeprecationHelper.py
│ ├── HookManager.py
│ ├── KeybindManager.py
│ ├── MenuManager.py
│ ├── ModObjects.py
│ ├── NetworkManager.py
│ ├── OptionManager.py
│ ├── Options.py
│ ├── SettingsManager.py
│ └── __init__.py
├── Quickload
│ └── __init__.py
├── ReadOnly
│ └── __init__.py
├── SkillRandomizer
│ └── __init__.py
├── __init__.py
├── linters.txt
└── setup.cfg
├── PythonSDK.sln
├── PythonSDK.sln.DotSettings.user
├── PythonSDK.vcxproj
├── PythonSDK.vcxproj.filters
├── README.md
├── scripts
├── add_init.py
├── add_static.py
├── distribute_tarrays.py
├── fix_classes.py
├── fix_fuckup.py
├── fix_funcs.py
├── fix_pointers.py
├── fix_references.py
├── gen_funcs.py
├── merge.py
├── prune.py
├── readd_bitfields.py
├── regen_pydefs.py
├── regen_statics.py
├── setup_polymorphism.py
├── sort_relationships.py
├── split_sdk.py
└── tarray_finder.py
└── src
├── AntiDebug.cpp
├── CHookManager.cpp
├── CPythonInterface.cpp
├── CSigScan.cpp
├── CSimpleDetour.cpp
├── CoreExtensions.cpp
├── Logging.cpp
├── PackageFix.cpp
├── Settings.cpp
├── UnrealEngine
├── Core_functions.cpp
└── Engine_functions.cpp
├── UnrealSDK.cpp
├── Util.cpp
├── include
├── AntiDebug.h
├── CHookManager.h
├── CPythonInterface.h
├── CSigScan.h
├── CSimpleDetour.h
├── Exceptions.h
├── Exports.h
├── Games.h
├── Logging.h
├── MemoryDebug.h
├── MemorySignature.h
├── Settings.h
├── Signatures.h
├── TypeMap.h
├── UnrealEngine
│ ├── Core
│ │ ├── Core_classes.h
│ │ ├── Core_f_structs.h
│ │ └── Core_structs.h
│ └── Engine
│ │ ├── Engine_classes.h
│ │ ├── Engine_f_structs.h
│ │ └── Engine_structs.h
├── UnrealSDK.h
├── Util.h
├── detours
│ ├── detours.cpp
│ ├── detours.h
│ ├── detver.h
│ ├── disasm.cpp
│ └── modules.cpp
├── gamedefines.h
├── pybind11
│ ├── attr.h
│ ├── buffer_info.h
│ ├── cast.h
│ ├── chrono.h
│ ├── common.h
│ ├── complex.h
│ ├── detail
│ │ ├── class.h
│ │ ├── common.h
│ │ ├── descr.h
│ │ ├── init.h
│ │ ├── internals.h
│ │ └── typeid.h
│ ├── eigen.h
│ ├── embed.h
│ ├── eval.h
│ ├── functional.h
│ ├── iostream.h
│ ├── numpy.h
│ ├── operators.h
│ ├── options.h
│ ├── pybind11.h
│ ├── pytypes.h
│ ├── stl.h
│ └── stl_bind.h
├── pydef.h
├── stb_image.h
└── stdafx.h
├── main.cpp
├── pydefs
├── Core_classes.cpp
├── Core_structs.cpp
├── _TArray.cpp
└── gamedefines.cpp
└── stdafx.cpp
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Dd]ebug
2 | [Rr]elease
3 | .vs
4 | *.dll
5 | *.pyc
6 | *.json
7 | *.vcxproj.user
--------------------------------------------------------------------------------
/Mods/BackpackManager/__init__.py:
--------------------------------------------------------------------------------
1 | import unrealsdk
2 |
3 | from Mods.ModMenu import EnabledSaveType, Hook, ModTypes, Options, RegisterMod, SDKMod
4 |
5 |
6 | class BackpackManager(SDKMod):
7 | Name: str = "Backpack Manager"
8 | Author = "FromDarkHell"
9 | Description: str = "Customize the size of your character's backpack on the fly!"
10 | Version: str = "1.1"
11 |
12 | Types: ModTypes = ModTypes.Gameplay
13 | SaveEnabledState: EnabledSaveType = EnabledSaveType.LoadWithSettings
14 |
15 | BackpackSize: Options.Slider = Options.Slider(
16 | "Backpack", "Change the size of your character's backpack
Default is 39", 39, 0, 200, 1
17 | )
18 | Options = [BackpackSize]
19 |
20 | @Hook("WillowGame.WillowHUD.CreateWeaponScopeMovie")
21 | def _GameLoad(self, caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
22 | PC = unrealsdk.GetEngine().GamePlayers[0].Actor
23 | if PC and PC.Pawn:
24 | PC.Pawn.InvManager.InventorySlotMax_Misc = self.BackpackSize.CurrentValue
25 | return True
26 |
27 | def ModOptionChanged(self, option, newValue) -> None:
28 | if option == self.BackpackSize:
29 | PC = unrealsdk.GetEngine().GamePlayers[0].Actor
30 | if PC and PC.Pawn:
31 | PC.Pawn.InvManager.InventorySlotMax_Misc = newValue
32 |
33 |
34 | RegisterMod(BackpackManager())
35 |
--------------------------------------------------------------------------------
/Mods/ModMenu/DeprecationHelper.py:
--------------------------------------------------------------------------------
1 | import unrealsdk
2 | import functools
3 | from typing import Any, Callable, Dict, List, Optional, Set, Tuple
4 |
5 | __all__: Tuple[str, ...] = (
6 | "Deprecated",
7 | "NameChangeMsg",
8 | "PrintWarning",
9 | )
10 |
11 | _printed_deprecation_warnings: Set[str] = set()
12 |
13 |
14 | def PrintWarning(msg: str) -> None:
15 | """
16 | Prints a warning containing the provided message. Will only happen once per message.
17 |
18 | Args:
19 | msg: The message to print.
20 | """
21 | if msg not in _printed_deprecation_warnings:
22 | _printed_deprecation_warnings.add(msg)
23 | unrealsdk.Log(f"[Warning] {msg}")
24 |
25 |
26 | def NameChangeMsg(old_name: str, new_name: str) -> str:
27 | """
28 | Helper returning a generic deprecation message for when something's name changed.
29 |
30 | Args:
31 | old_name: The deprecated name.
32 | new_name: The new name.
33 | Returns:
34 | The name change deprecation message.
35 | """
36 | return f"Use of '{old_name}' is deprecated, use '{new_name}' instead."
37 |
38 |
39 | def Deprecated(
40 | msg: str,
41 | func: Optional[Callable[..., Any]] = None
42 | ) -> Callable[..., Any]:
43 | """
44 | Decorator that prints a deprecation message when it's wrapped function is called.
45 |
46 | Can also be called with the function as an argument.
47 |
48 | Args:
49 | msg: The message to print.
50 | func: The function to wrap.
51 | Returns:
52 | The wrapped function
53 | """
54 | def decorator(old_func: Callable[..., Any]) -> Callable[..., Any]:
55 | @functools.wraps(old_func)
56 | def new_func(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
57 | PrintWarning(msg)
58 | return old_func(*args, **kwargs)
59 | return new_func
60 |
61 | if func is None:
62 | return decorator
63 | else:
64 | return decorator(func)
65 |
--------------------------------------------------------------------------------
/Mods/ModMenu/HookManager.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import unrealsdk
4 | import functools
5 | import weakref
6 | from inspect import Parameter, signature
7 | from typing import Any, Callable, Optional, Tuple, Union
8 |
9 | __all__: Tuple[str, ...] = (
10 | "AnyHook",
11 | "Hook",
12 | "HookFunction",
13 | "HookMethod",
14 | "RegisterHooks",
15 | "RemoveHooks",
16 | )
17 |
18 |
19 | HookFunction = Callable[
20 | [unrealsdk.UObject, unrealsdk.UFunction, unrealsdk.FStruct],
21 | Optional[bool]
22 | ]
23 | HookMethod = Callable[
24 | [Any, unrealsdk.UObject, unrealsdk.UFunction, unrealsdk.FStruct],
25 | Optional[bool]
26 | ]
27 | AnyHook = Union[HookFunction, HookMethod]
28 |
29 |
30 | def Hook(target: str, name: str = "{0}.{1}") -> Callable[[AnyHook], AnyHook]:
31 | """
32 | A decorator for functions that should be invoked in response to an Unreal Engine method's
33 | invokation.
34 |
35 | The function being decorated may be a standalone function, in which case its signature must
36 | match that of `unrealsdk.RegisterHook` functions:
37 | (caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct)
38 |
39 | Alternatively, the function may be an instance method of any object. In this case, the hook will
40 | be activated once `ModMenu.RegisterHooks(object)` has been called on the object. The signature
41 | of the method must match that of `unrealsdk.RegisterHook` functions, with the addition of `self`
42 | as the first parameter:
43 | (self, caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct)
44 |
45 | Upon invokation of the Unreal Engine method, the decorated function will be called. Its `caller`
46 | argument will contain the Unreal Engine object whose method was invoked, the `function` argument
47 | will contain the Unreal Engine function that was invoked, and the `params` argument will contain
48 | an `FStruct` with the arguments passed to the method.
49 |
50 | Args:
51 | target:
52 | A string representing the Unreal Engine method that should be hooked, in the format
53 | "..".
54 | name:
55 | A string which, when paired with the hook target, uniquely identifies this hook within
56 | the SDK. By default, a name is generated using the function's module, qualified name,
57 | and `id()` (in the case of mod instance method hooks, the mod instance's `id()` is used
58 | instead).
59 |
60 | If a custom name is provided, it may be a simple string, or a format string with either
61 | one or two replacement tokens. Token `{0}` will contain the function's module name and
62 | qualified name, separated by a ".". Argument `{1}` will contain the `id()` of the
63 | function or mod instance.
64 | """
65 | def apply_hook(function: AnyHook) -> AnyHook:
66 | # If the function has four parameters, it should be a method.
67 | params = signature(function).parameters
68 | is_method = (len(params) == 4)
69 |
70 | # Retrieve the function's dictionary of targets. If it does not yet have one, we preform
71 | # initial setup on it now.
72 | hook_targets = getattr(function, "HookTargets", None)
73 | if hook_targets is None:
74 | param_exception = ValueError(
75 | "Hook functions must have the signature"
76 | " ([self,] caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct)"
77 | )
78 |
79 | # If the function is an instance method, create a mutable list of the parameters and
80 | # remove the `self` one, so we may check the remaining ones same as a non-method.
81 | param_list = list(params.values())
82 | if is_method:
83 | del param_list[0]
84 | # If the function has neither 4 nor 3 parameters, it is invalid.
85 | elif len(param_list) != 3:
86 | raise param_exception
87 | # If the functions parameters do not accept positional arguments, it is invalid.
88 | for param in param_list:
89 | if Parameter.POSITIONAL_ONLY != param.kind != Parameter.POSITIONAL_OR_KEYWORD:
90 | raise param_exception
91 |
92 | # If the function is a method, store the name format string on it for formatting with
93 | # future instances. If it's a simple function, format its name for use now.
94 | function.HookName = name if is_method else name.format( # type: ignore
95 | f"{function.__module__}.{function.__qualname__}", id(function)
96 | )
97 |
98 | # With the function now known as valid, create its set of targets.
99 | hook_targets = function.HookTargets = set() # type: ignore
100 |
101 | hook_targets.add(target)
102 |
103 | if not is_method:
104 | unrealsdk.RunHook(target, function.HookName, function) # type: ignore
105 |
106 | return function
107 | return apply_hook
108 |
109 |
110 | def _create_method_wrapper(obj_ref: weakref.ReferenceType[object], obj_function: HookMethod) -> HookFunction:
111 | """Return a "true" function for the given bound method, passable to `unrealsdk.RegisterHook`."""
112 | @functools.wraps(obj_function)
113 | def method_wrapper(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> Any:
114 | obj = obj_ref()
115 | method = obj_function.__get__(obj, type(obj)) # type: ignore
116 | return method(caller, obj_function, params)
117 | return method_wrapper
118 |
119 |
120 | def RegisterHooks(obj: object) -> None:
121 | """
122 | Registers all `@Hook` decorated methods for the object. Said methods will subsequently be called
123 | in response to the hooked Unreal Engine methods.
124 |
125 | Args:
126 | obj: The object for which to register method hooks.
127 | """
128 |
129 | # Create a weak reference to the object which we may use in attributes on it without creating
130 | # cyclical references. Before destruction, `RemoveHooks` should be called on the object to
131 | # ensure there are no remaining hooks that reference it.
132 | obj_ref = weakref.ref(obj, RemoveHooks)
133 |
134 | # Iterate over each attribute on the object's class that contains a function.
135 | for attribute_name, function in type(obj).__dict__.items():
136 | if not callable(function):
137 | continue
138 |
139 | # Attempt to get the set of hook targets from the function. If it doesn't have one, or if
140 | # its signature doesn't have 4 parameters, it is not a hook method.
141 | hook_targets = getattr(function, "HookTargets", None)
142 | if hook_targets is None or len(signature(function).parameters) != 4:
143 | continue
144 |
145 | # Create a wrapper to replace the descriptor of the attribute, "binding" the function to the
146 | # mod's weak reference, in a function that can be passed to `unrealsdk.RunHook`.
147 | method_wrapper = _create_method_wrapper(obj_ref, function)
148 | setattr(obj, attribute_name, method_wrapper)
149 |
150 | # Format the provided hook name.
151 | method_wrapper.HookName = function.HookName.format( # type: ignore
152 | f"{function.__module__}.{function.__qualname__}", id(obj)
153 | )
154 |
155 | for target in hook_targets:
156 | unrealsdk.RunHook(target, method_wrapper.HookName, method_wrapper) # type: ignore
157 |
158 |
159 | def RemoveHooks(obj: object) -> None:
160 | """
161 | Unregisters all `@Hook` decorated methods for the object. Said methods will no longer be called
162 | in response to the hooked Unreal Engine methods.
163 |
164 | Args:
165 | obj: The object for which to unregister method hooks.
166 | """
167 | for function in obj.__dict__.values():
168 | if not callable(function):
169 | continue
170 |
171 | hook_targets = getattr(function, "HookTargets", None)
172 | if hook_targets is None:
173 | continue
174 |
175 | for target in hook_targets:
176 | unrealsdk.RemoveHook(target, function.HookName)
177 |
--------------------------------------------------------------------------------
/Mods/ModMenu/OptionManager.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import unrealsdk
4 | from typing import Any, List, Sequence, Tuple
5 |
6 | from . import MenuManager, ModObjects, Options, SettingsManager
7 |
8 | __all__: Tuple[str, ...] = ()
9 |
10 |
11 | _modded_data_provider_stack: List[unrealsdk.UObject] = []
12 | _nested_options_stack: List[Options.Nested] = []
13 |
14 | _MOD_OPTIONS_EVENT_ID: int = 1417
15 | _MOD_OPTIONS_MENU_NAME: str = "MODS"
16 |
17 | _INDENT: int = 2
18 |
19 |
20 | class _ModHeader(Options.Field):
21 | def __init__(self, Caption: str) -> None:
22 | self.Caption = Caption
23 | self.Description = ""
24 | self.IsHidden = False
25 |
26 |
27 | def _create_data_provider(name: str) -> unrealsdk.UObject:
28 | """
29 | Helper function that creates a new data provider and adds it to the stack.
30 |
31 | Args:
32 | name: The menu name to give the new data provider.
33 | Returns:
34 | The data provider.
35 | """
36 | provider = unrealsdk.ConstructObject(
37 | Class=unrealsdk.FindClass("WillowScrollingListDataProviderOptionsBase")
38 | )
39 | # See issue #45
40 | unrealsdk.GetEngine().GamePlayers[0].Actor.ServerRCon(
41 | f"set {provider.PathName(provider)} MenuDisplayName {name}"
42 | )
43 | _modded_data_provider_stack.append(provider)
44 | return provider
45 |
46 |
47 | def _is_anything_shown(option_list: Sequence[Options.Base]) -> bool:
48 | """
49 | Helper function that recursively checks if anything in the provided option list is shown.
50 |
51 | Args:
52 | option_list: The list of options to check.
53 | Returns:
54 | True if at least one of the options in the list, or in any nested lists, is shown.
55 | """
56 | for option in option_list:
57 | if option.IsHidden:
58 | continue
59 | if isinstance(option, Options.Nested):
60 | if _is_anything_shown(option.Children):
61 | return True
62 | else:
63 | return True
64 | return False
65 |
66 |
67 | def _TopLevelOptionsPopulate(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
68 | """ This function is called to create the options menu. We use it to inject our `MODS` menu. """
69 | # If not mods have accessable options, we want to disable the mods entry
70 | disabled = True
71 | for mod in ModObjects.Mods:
72 | if not mod.IsEnabled:
73 | continue
74 | if _is_anything_shown(mod.Options):
75 | disabled = False
76 | break
77 |
78 | def AddListItem(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
79 | """
80 | This function is called every time an item is added to *any* menu list - we obviously can't
81 | use a generic hook.
82 | Using it cause it simplifies the code to add our own entry.
83 | """
84 | if params.Caption == "$WillowGame.WillowScrollingList.BackCaption":
85 | caller.AddListItem(_MOD_OPTIONS_EVENT_ID, _MOD_OPTIONS_MENU_NAME, disabled, False)
86 |
87 | return True
88 |
89 | unrealsdk.RunHook("WillowGame.WillowScrollingList.AddListItem", "ModMenu.OptionManager", AddListItem)
90 |
91 | unrealsdk.DoInjectedCallNext()
92 | caller.Populate(params.TheList)
93 |
94 | unrealsdk.RemoveHook("WillowGame.WillowScrollingList.AddListItem", "ModMenu.OptionManager")
95 | return False
96 |
97 |
98 | def _WillowScrollingListOnClikEvent(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
99 | """
100 | This function is called on a few different events to do with these scrolling lists. We're
101 | interested in it to detect when you open up one of our modded menus.
102 | """
103 | global isMenuPluginMenu
104 |
105 | if params.Data.Type != "itemClick":
106 | return True
107 |
108 | # For some reason `caller.GetCurrentDataProvider()` returns a null object?
109 | provider = None
110 | for obj in caller.DataProviderStack:
111 | provider = obj.DataProvider.ObjectPointer
112 | if provider is None:
113 | return True
114 |
115 | if provider in _modded_data_provider_stack:
116 | # If you pressed the back button
117 | if params.Data.Index == len(_nested_options_stack[-1].Children):
118 | return True
119 |
120 | option = _nested_options_stack[-1].Children[params.Data.Index]
121 | if isinstance(option, Options.Nested):
122 | _nested_options_stack.append(option)
123 | caller.MyOwnerMovie.PlayUISound("MenuOpen")
124 | caller.PushDataProvider(_create_data_provider(option.Caption))
125 | return False
126 | elif isinstance(option, Options.Field):
127 | return False
128 |
129 | elif (
130 | provider.Class.Name == "WillowScrollingListDataProviderTopLevelOptions"
131 | and caller.IndexToEventId[params.Data.Index] == _MOD_OPTIONS_EVENT_ID
132 | ):
133 | caller.MyOwnerMovie.PlayUISound("MenuOpen")
134 | caller.PushDataProvider(_create_data_provider(_MOD_OPTIONS_MENU_NAME))
135 | return False
136 |
137 | return True
138 |
139 |
140 | def _DataProviderOptionsBasePopulate(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
141 | """
142 | This function is called to fill in a few of a scrolling lists. Our custom data providers are of
143 | this type, so we use it to populate the lists ourselves.
144 | """
145 | if caller not in _modded_data_provider_stack:
146 | return True
147 |
148 | # If we're on the first level we need to setup the inital list
149 | if len(_nested_options_stack) == 0:
150 | all_options: List[Options.Base] = []
151 | for mod in MenuManager.GetOrderedModList():
152 | if not mod.IsEnabled:
153 | continue
154 |
155 | one_shown = False
156 | for option in mod.Options:
157 | if option.IsHidden:
158 | continue
159 | if not one_shown:
160 | one_shown = True
161 | all_options.append(_ModHeader(mod.Name))
162 | all_options.append(option)
163 |
164 | _nested_options_stack.append(Options.Nested(_MOD_OPTIONS_MENU_NAME, "", all_options))
165 |
166 | first_level = len(_nested_options_stack) == 1
167 | for idx, option in enumerate(_nested_options_stack[-1].Children):
168 | if option.IsHidden:
169 | continue
170 |
171 | indent = " " * _INDENT if first_level and not isinstance(option, _ModHeader) else ""
172 |
173 | if isinstance(option, Options.Spinner):
174 | spinner_idx: int
175 | if isinstance(option, Options.Boolean):
176 | spinner_idx = int(option.CurrentValue)
177 | else:
178 | spinner_idx = option.Choices.index(option.CurrentValue)
179 |
180 | params.TheList.AddSpinnerListItem(
181 | idx, indent + option.Caption, False, spinner_idx, option.Choices
182 | )
183 | elif isinstance(option, Options.Slider):
184 | params.TheList.AddSliderListItem(
185 | idx,
186 | indent + option.Caption,
187 | False,
188 | option.CurrentValue,
189 | option.MinValue,
190 | option.MaxValue,
191 | option.Increment,
192 | )
193 | elif isinstance(option, Options.Field):
194 | disabled = False
195 | if isinstance(option, Options.Nested):
196 | disabled = not _is_anything_shown(option.Children)
197 | params.TheList.AddListItem(idx, indent + option.Caption, disabled, False)
198 |
199 | caller.AddDescription(idx, option.Description)
200 |
201 | return False
202 |
203 |
204 | def _DataProviderOptionsBaseOnPop(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
205 | """
206 | This function is called when the data provider is popped off the stack, when you leave the menu.
207 | Unsuprisingly, we do the same with our stacks. We can also use it to save settings when you
208 | leave the outermost menu.
209 | """
210 | if caller in _modded_data_provider_stack:
211 | _modded_data_provider_stack.pop()
212 | _nested_options_stack.pop()
213 | if len(_modded_data_provider_stack) == 0:
214 | SettingsManager.SaveAllModSettings()
215 |
216 | return True
217 |
218 |
219 | def _HandleSpinnerSliderChange(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
220 | """
221 | The two functions that have this hook get called when a spinner or slider changes value. We use
222 | the hook to update our version of the objects.
223 | """
224 | if caller not in _modded_data_provider_stack:
225 | return True
226 |
227 | changed_option = _nested_options_stack[-1].Children[params.EventID]
228 |
229 | new_value: Any
230 | if isinstance(changed_option, Options.Slider):
231 | new_value = int(params.NewSliderValue)
232 | elif isinstance(changed_option, Options.Boolean):
233 | new_value = bool(params.NewChoiceIndex)
234 | elif isinstance(changed_option, Options.Spinner):
235 | new_value = changed_option.Choices[params.NewChoiceIndex]
236 | else:
237 | raise RuntimeError(f"Option of bad type '{type(changed_option)}' somehow changed value.")
238 |
239 | def in_option_list(option_list: Sequence[Options.Base]) -> bool:
240 | return any(
241 | in_option_list(option.Children)
242 | if isinstance(option, Options.Nested)
243 | else option == changed_option
244 | for option in option_list
245 | )
246 |
247 | for mod in ModObjects.Mods:
248 | if in_option_list(mod.Options):
249 | # Calling this before updating the value
250 | mod.ModOptionChanged(changed_option, new_value)
251 | changed_option.CurrentValue = new_value
252 | break
253 |
254 | return True
255 |
256 |
257 | unrealsdk.RunHook("WillowGame.WillowScrollingListDataProviderTopLevelOptions.Populate", "ModMenu.OptionManager", _TopLevelOptionsPopulate)
258 | unrealsdk.RunHook("WillowGame.WillowScrollingList.OnClikEvent", "ModMenu.OptionManager", _WillowScrollingListOnClikEvent)
259 | unrealsdk.RunHook("WillowGame.WillowScrollingListDataProviderOptionsBase.Populate", "ModMenu.OptionManager", _DataProviderOptionsBasePopulate)
260 | unrealsdk.RunHook("WillowGame.WillowScrollingListDataProviderOptionsBase.OnPop", "ModMenu.OptionManager", _DataProviderOptionsBaseOnPop)
261 | unrealsdk.RunHook("WillowGame.WillowScrollingListDataProviderOptionsBase.HandleSpinnerChange", "ModMenu.OptionManager", _HandleSpinnerSliderChange)
262 | unrealsdk.RunHook("WillowGame.WillowScrollingListDataProviderOptionsBase.HandleSliderChange", "ModMenu.OptionManager", _HandleSpinnerSliderChange)
263 |
--------------------------------------------------------------------------------
/Mods/ModMenu/SettingsManager.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import unrealsdk
4 | import inspect
5 | import json
6 | import traceback
7 | from os import path
8 | from typing import Any, Dict, Sequence, Set, Tuple
9 |
10 | from . import DeprecationHelper as dh
11 | from . import KeybindManager, ModObjects, Options
12 |
13 | __all__: Tuple[str, ...] = (
14 | "GetSettingsFilePath",
15 | "SaveModSettings",
16 | "SaveAllModSettings",
17 | "LoadModSettings",
18 | )
19 |
20 |
21 | _OPTIONS_CATEGORY_NAME = "Options"
22 | _KEYBINDS_CATEGORY_NAME = "Keybinds"
23 | _ENABLED_CATEGORY_NAME = "AutoEnable"
24 | _SETTINGS_FILE_NAME = "settings.json"
25 |
26 | _mods_to_enable_on_main_menu: Set[ModObjects.SDKMod] = set()
27 |
28 |
29 | def GetSettingsFilePath(mod: ModObjects.SDKMod) -> str:
30 | """
31 | Gets the path of a mod's settings file.
32 |
33 | Args:
34 | mod: The instance of the mod whose settings file path should be retrived.
35 | Returns:
36 | The path to the file, which is in the same folder as the file defining the mod class.
37 | """
38 | return path.join(path.dirname(inspect.getfile(mod.__class__)), _SETTINGS_FILE_NAME)
39 |
40 |
41 | def SaveModSettings(mod: ModObjects.SDKMod) -> None:
42 | """
43 | Saves the options, keybinds, and enabled state of a mod, where applicable.
44 |
45 | Args:
46 | mod: The instance of the mod whose settings should be saved.
47 | """
48 | mod_settings: Dict[str, Any] = {}
49 |
50 | def create_options_dict(options: Sequence[Options.Base]) -> Dict[str, Any]:
51 | settings = {}
52 | for option in options:
53 | if isinstance(option, Options.Value):
54 | settings[option.Caption] = option.CurrentValue
55 | elif isinstance(option, Options.Nested):
56 | settings[option.Caption] = create_options_dict(option.Children)
57 | return settings
58 |
59 | options_dict = create_options_dict(mod.Options)
60 |
61 | if len(options_dict) > 0:
62 | mod_settings[_OPTIONS_CATEGORY_NAME] = options_dict
63 |
64 | keybinds_dict = {}
65 | for input in mod.Keybinds:
66 | if isinstance(input, KeybindManager.Keybind):
67 | if not input.IsRebindable:
68 | continue
69 | keybinds_dict[input.Name] = input.Key
70 | else:
71 | dh.PrintWarning(KeybindManager.Keybind._list_deprecation_warning)
72 | keybinds_dict[input[0]] = input[1]
73 |
74 | if len(keybinds_dict) > 0:
75 | mod_settings[_KEYBINDS_CATEGORY_NAME] = keybinds_dict
76 |
77 | if mod.SaveEnabledState != ModObjects.EnabledSaveType.NotSaved:
78 | mod_settings[_ENABLED_CATEGORY_NAME] = mod.IsEnabled
79 |
80 | if len(mod_settings.keys()) > 0:
81 | with open(GetSettingsFilePath(mod), "w") as file:
82 | json.dump(mod_settings, file, indent=4)
83 |
84 |
85 | def SaveAllModSettings() -> None:
86 | """ Saves the options, keybinds, and enabled state of all loaded mods, where applicable. """
87 | for mod in ModObjects.Mods:
88 | try:
89 | SaveModSettings(mod)
90 | except Exception:
91 | unrealsdk.Log(f"Unable to save settings for '{mod.Name}'")
92 | tb = traceback.format_exc().split('\n')
93 | unrealsdk.Log(f" {tb[-4].strip()}")
94 | unrealsdk.Log(f" {tb[-3].strip()}")
95 | unrealsdk.Log(f" {tb[-2].strip()}")
96 |
97 |
98 | def LoadModSettings(mod: ModObjects.SDKMod) -> None:
99 | """
100 | Loads the options, keybinds, and enabled state of a mod back from disk.
101 |
102 | Args:
103 | mod: The instance of the mod to load settings onto.
104 | """
105 | settings: Dict[str, Any]
106 | try:
107 | with open(GetSettingsFilePath(mod)) as file:
108 | settings = json.load(file)
109 | except (FileNotFoundError, json.JSONDecodeError):
110 | return
111 |
112 | def load_options_dict(options: Sequence[Options.Base], settings: Dict[str, Any]) -> None:
113 | for option in options:
114 | if option.Caption not in settings:
115 | continue
116 |
117 | value = settings[option.Caption]
118 |
119 | if isinstance(option, Options.Boolean):
120 | if isinstance(value, str):
121 | if value in option.Choices:
122 | option.CurrentValue = bool(option.Choices.index(value))
123 | elif value.lower() == "true":
124 | option.CurrentValue = True
125 | elif value.lower() == "false":
126 | option.CurrentValue = False
127 | else:
128 | option.CurrentValue = bool(value)
129 | elif isinstance(option, Options.Spinner):
130 | if value in option.Choices:
131 | option.CurrentValue = str(value)
132 | else:
133 | option.CurrentValue = option.StartingValue
134 | elif isinstance(option, Options.Slider):
135 | option.CurrentValue = max(option.MinValue, min(option.MaxValue, int(value)))
136 | elif isinstance(option, Options.Hidden):
137 | option.CurrentValue = value
138 |
139 | elif isinstance(option, Options.Nested):
140 | load_options_dict(option.Children, value)
141 |
142 | load_options_dict(mod.Options, settings.get(_OPTIONS_CATEGORY_NAME, {}))
143 |
144 | saved_keybinds = settings.get(_KEYBINDS_CATEGORY_NAME, {})
145 | for input in mod.Keybinds:
146 | if isinstance(input, KeybindManager.Keybind):
147 | if input.Name in saved_keybinds:
148 | input.Key = saved_keybinds[input.Name]
149 | else:
150 | dh.PrintWarning(KeybindManager.Keybind._list_deprecation_warning)
151 | if input[0] in saved_keybinds:
152 | input[1] = saved_keybinds[input[0]]
153 |
154 | if settings.get(_ENABLED_CATEGORY_NAME, False):
155 | if mod.SaveEnabledState == ModObjects.EnabledSaveType.LoadWithSettings:
156 | if not mod.IsEnabled:
157 | mod.SettingsInputPressed("Enable")
158 | elif mod.SaveEnabledState == ModObjects.EnabledSaveType.LoadOnMainMenu:
159 | _mods_to_enable_on_main_menu.add(mod)
160 |
161 |
162 | def _FrontendGFxMovieStart(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
163 | """
164 | This function is called upon reaching the main menu, after hotfix objects already exist and all
165 | the main packages are loaded. We use it to enable all `LoadOnMainMenu` mods.
166 | """
167 | for mod in _mods_to_enable_on_main_menu:
168 | if not mod.IsEnabled:
169 | mod.SettingsInputPressed("Enable")
170 |
171 | _mods_to_enable_on_main_menu.clear()
172 |
173 | return True
174 |
175 |
176 | unrealsdk.RunHook("WillowGame.FrontendGFxMovie.Start", "ModMenu.SettingsManager", _FrontendGFxMovieStart)
177 |
--------------------------------------------------------------------------------
/Mods/ModMenu/__init__.py:
--------------------------------------------------------------------------------
1 | import unrealsdk
2 | import sys
3 | from typing import Tuple
4 |
5 | __all__: Tuple[str, ...] = (
6 | "AnyHook",
7 | "ClientMethod",
8 | "Deprecated",
9 | "EnabledSaveType",
10 | "Game",
11 | "GetOrderedModList",
12 | "GetSettingsFilePath",
13 | "Hook",
14 | "HookFunction",
15 | "HookMethod",
16 | "InputEvent",
17 | "Keybind",
18 | "KeybindCallback",
19 | "LoadModSettings",
20 | "ModPriorities",
21 | "Mods",
22 | "ModTypes",
23 | "NameChangeMsg",
24 | "Options",
25 | "PrintWarning",
26 | "RegisterHooks",
27 | "RegisterMod",
28 | "RegisterNetworkMethods",
29 | "RemoveHooks",
30 | "SaveAllModSettings",
31 | "SaveModSettings",
32 | "SDKMod",
33 | "ServerMethod",
34 | "UnregisterNetworkMethods",
35 | )
36 |
37 |
38 | # Need to define these up here so that they're accessable when importing the other files
39 | VERSION_MAJOR = 2
40 | VERSION_MINOR = 5
41 |
42 | unrealsdk.Log(f"[ModMenu] Version: {VERSION_MAJOR}.{VERSION_MINOR}")
43 |
44 | from . import DeprecationHelper as dh # noqa: E402
45 | from . import OptionManager, Options, SettingsManager # noqa: E402
46 | from .DeprecationHelper import Deprecated, NameChangeMsg, PrintWarning # noqa: E402
47 | from .HookManager import (AnyHook, Hook, HookFunction, HookMethod, RegisterHooks, # noqa: E402
48 | RemoveHooks)
49 | from .KeybindManager import InputEvent, Keybind, KeybindCallback # noqa: E402
50 | from .MenuManager import GetOrderedModList # noqa: E402
51 | from .ModObjects import (EnabledSaveType, Game, ModPriorities, Mods, ModTypes, # noqa: E402
52 | RegisterMod, SDKMod)
53 | from .NetworkManager import (ClientMethod, RegisterNetworkMethods, ServerMethod, # noqa: E402
54 | UnregisterNetworkMethods)
55 | from .SettingsManager import (GetSettingsFilePath, LoadModSettings, # noqa: E402
56 | SaveAllModSettings, SaveModSettings)
57 |
58 | from . import ModObjects # noqa: E402 # isort: skip # Avoid circular import
59 |
60 | """
61 | From this point on this file defines just aliases, most of which should be considered deprecated.
62 |
63 |
64 | When enabling a mod, the default `SettingsInputPressed` calls `ModOptionChanged` on every option.
65 | This behaviour should be considered deprecated, move it into your `Enable` if you need it.
66 | Unfortuantly there's no easy way to detect mods that rely on this to print a warning, and it's not
67 | something that can be aliased, so this text warning will have to do.
68 | """
69 |
70 |
71 | sys.modules["bl2sdk"] = unrealsdk
72 | sys.modules["Mods.ModManager"] = ModObjects
73 | sys.modules["Mods.OptionManager"] = OptionManager
74 | sys.modules["Mods.SaveManager"] = SettingsManager
75 |
76 | unrealsdk.PythonManagerVersion = VERSION_MAJOR
77 |
78 | ModObjects.BL2MOD = ModObjects.SDKMod # type: ignore
79 | unrealsdk.BL2MOD = ModObjects.SDKMod
80 |
81 | unrealsdk.Mods = ModObjects.Mods
82 | unrealsdk.ModTypes = ModObjects.ModTypes
83 | unrealsdk.RegisterMod = ModObjects.RegisterMod
84 |
85 |
86 | OptionManager.Options = Options
87 | unrealsdk.Options = Options
88 |
89 | # When removing this, also make sure to edit `Spinner.__init__()`
90 | _msg = dh.NameChangeMsg("Spinner.StartingChoice", "Spinner.StartingValue")
91 | Options.Spinner.StartingChoice = property( # type: ignore
92 | dh.Deprecated(_msg, lambda self: self.StartingValue),
93 | dh.Deprecated(_msg, lambda self, val: self.__setattr__("StartingValue", val))
94 | )
95 | _msg = dh.NameChangeMsg("Boolean.StartingChoiceIndex", "Boolean.StartingValue")
96 | Options.Boolean.StartingChoiceIndex = property( # type: ignore
97 | dh.Deprecated(_msg, lambda self: self.StartingValue),
98 | dh.Deprecated(_msg, lambda self, val: self.__setattr__("StartingValue", val))
99 | )
100 | del _msg
101 |
102 |
103 | SettingsManager.storeModSettings = SettingsManager.SaveAllModSettings # type: ignore
104 | storeModSettings = SettingsManager.SaveAllModSettings # noqa: N816
105 |
--------------------------------------------------------------------------------
/Mods/Quickload/__init__.py:
--------------------------------------------------------------------------------
1 | import unrealsdk
2 | from unrealsdk import *
3 | from Mods import ModMenu
4 | from Mods.ModMenu import EnabledSaveType, Mods, ModTypes, Options, RegisterMod, SDKMod, Hook
5 | from typing import List
6 |
7 |
8 | _DefaultGameInfo = UObject.FindObjectsContaining("WillowCoopGameInfo WillowGame.Default__WillowCoopGameInfo")[0]
9 |
10 |
11 | def _DisplayFeedback(message, time=2.0) -> None:
12 | playerController = GetEngine().GamePlayers[0].Actor
13 | HUDMovie = playerController.GetHUDMovie()
14 | if HUDMovie is None:
15 | return
16 | duration = time * _DefaultGameInfo.GameSpeed
17 | HUDMovie.ClearTrainingText()
18 | HUDMovie.AddTrainingText(
19 | message, "Map Loader", duration, (), "", False, 0, playerController.PlayerReplicationInfo, True
20 | )
21 |
22 |
23 | def _StoreLocation() -> None:
24 | PC = GetEngine().GamePlayers[0].Actor
25 | locale = PC.Pawn.Location
26 | _Position = [locale.X, locale.Y, locale.Z]
27 | _ModInstance.Coords[0] = locale.X
28 | _ModInstance.Coords[1] = locale.Y
29 | _ModInstance.Coords[2] = locale.Z
30 | rotate = PC.Rotation
31 | _ModInstance.Rotation[0] = rotate.Pitch
32 | _ModInstance.Rotation[1] = rotate.Roll
33 | _ModInstance.Rotation[2] = rotate.Yaw
34 | Log(f"[Map Loader] Storing location: ({_ModInstance.Coords}), ({_ModInstance.Rotation})")
35 |
36 |
37 | def _RestoreLocation() -> None:
38 | PC = GetEngine().GamePlayers[0].Actor
39 | # Restore our location.
40 | PC.Pawn.Location.X = _ModInstance.Coords[0]
41 | PC.Pawn.Location.Y = _ModInstance.Coords[1]
42 | PC.Pawn.Location.Z = _ModInstance.Coords[2]
43 | Log(f"[Map Loader] Restoring location: ({_ModInstance.Coords}), ({_ModInstance.Rotation})")
44 | rotate = PC.Rotation
45 | rotate.Pitch = _ModInstance.Rotation[0]
46 | rotate.Roll = _ModInstance.Rotation[1]
47 | rotate.Yaw = _ModInstance.Rotation[2]
48 |
49 |
50 | def _ReloadCurrentMap(skipSave):
51 | PC = GetEngine().GamePlayers[0].Actor
52 | if _ModInstance.toggledLocation:
53 | if _ModInstance.consistentLocation:
54 | _StoreLocation()
55 | else:
56 | if not _ModInstance.consistentLocation:
57 | _StoreLocation()
58 |
59 | _ModInstance.toggledLocation = False
60 | # Our currently selected difficulty for the main menu
61 | _ModInstance.SelectedDifficulty = PC.GetCurrentPlaythrough()
62 | # Get our current save game we'll need it for the OP levels
63 | wsg = PC.GetCachedSaveGame()
64 |
65 | # Our current OP level if we need it, game is weird
66 | if wsg.LastOverpowerChoice and wsg.NumOverpowerLevelsUnlocked:
67 | _ModInstance.OverpoweredLevel = max(min(wsg.LastOverpowerChoice, wsg.NumOverpowerLevelsUnlocked), 0)
68 | else:
69 | _ModInstance.OverpoweredLevel = -1
70 |
71 | # Load Map
72 | _ModInstance.loading = True
73 | # This is the function that BL2 uses for save quits.
74 | # ReturnToTitleScreen(optional bool bSkipSave, optional bool bRemoveSplitPlayer)
75 | # (In the SDK, optional arguments aren't optional)
76 |
77 | PC.ReturnToTitleScreen(skipSave, False)
78 |
79 |
80 | @Hook("WillowGame.WillowHUD.CreateWeaponScopeMovie")
81 | def _MapLoadHook(caller: UObject, function: UFunction, params: FStruct) -> None:
82 | if _ModInstance.restoreLocation and _ModInstance.loading:
83 | pc = GetEngine().GamePlayers[0].Actor
84 | HUDMovie = pc.myHUD.HUDMovie
85 | # PC is sometimes none when the hooked function is called, this means that the hook is running to early.
86 | # Same thing with the HUDMovie.
87 | if pc.Pawn is None or HUDMovie is None:
88 | return True
89 | _RestoreLocation()
90 | _DisplayFeedback("Farming Location Restored", 3.0)
91 | _ModInstance.loading = False
92 | return True
93 |
94 |
95 | # This is how we know that we're in the main menu. Its slightly janky, but it works.
96 | @Hook("WillowGame.FrontendGFxMovie.OnTick")
97 | def _MainMenuHook(caller: UObject, function: UFunction, params: FStruct) -> None:
98 | try:
99 | if _ModInstance.loading == True:
100 | PC = GetEngine().GamePlayers[0].Actor
101 | # We'll need this to reload to the current difficulty.
102 | gfx = UObject.FindObjectsContaining("FrontendGFxMovie ")[1]
103 | if gfx is None or PC is None:
104 | return True
105 | # This is how the game knows what OP level we're on.
106 | if _ModInstance.OverpoweredLevel != -1:
107 | PC.OnSelectOverpowerLevel(PC.GetCachedSaveGame(), _ModInstance.OverpoweredLevel)
108 | # I don't *think* this does anything, might want to do it just in case. Weird Game.
109 | gfx.CurrentSelectedOverpowerLevel = _ModInstance.OverpoweredLevel
110 | Log(f"[Map Loader] Loading WSG @ {_ModInstance.SelectedDifficulty}, OP{_ModInstance.OverpoweredLevel}")
111 | # Here we reload our save, like how the `Continue` button does.
112 | gfx.LaunchSaveGame(_ModInstance.SelectedDifficulty)
113 | except:
114 | pass
115 | return True
116 |
117 |
118 | class MapLoader(SDKMod):
119 | Name: str = "Borderlands 2 Map Reloader"
120 | Version: str = "1.1"
121 | Author: str = "FromDarkHell"
122 | Description: str = "Quickly farm items and save quit at a button press!\n\nLocation Restore: Whether to restore location on quickload"
123 | Types: ModTypes = ModTypes.Utility
124 | SaveEnabledState: EnabledSaveType = EnabledSaveType.LoadWithSettings
125 |
126 | Keybinds: List[ModMenu.Keybind] = [
127 | ModMenu.Keybind("Quickload w/o Saving", "F7"),
128 | ModMenu.Keybind("Quickload w/ Saving", "F8"),
129 | ModMenu.Keybind("Toggle Location Restore", "F10"),
130 | ModMenu.Keybind("Save Location", "F5"),
131 | ]
132 |
133 | def __init__(self):
134 | # It might be a good idea to restore our position after a load.
135 | self.restoreLocation = True
136 | self.loading = False
137 | self.consistentLocation = False
138 | self.toggledLocation = False
139 | # Store some data that we can use to reload the map
140 | self.SelectedDifficulty = 0
141 | self.OverpoweredLevel = 0
142 | self.Coords = [0, 0, 0] # X, Y, Z
143 | self.Rotation = [0, 0, 0] # Pitch, Roll, Yaw
144 |
145 | def GameInputPressed(self, input) -> None:
146 | name = input.Name
147 | if name == "Quickload w/o Saving":
148 | _ReloadCurrentMap(True)
149 | elif name == "Quickload w/ Saving":
150 | _ReloadCurrentMap(False)
151 | elif name == "Toggle Location Restore":
152 | self.restoreLocation = not self.restoreLocation
153 | state = "Location restoration is now {}".format("enabled" if self.restoreLocation else "disabled")
154 | Log(f"[Map Loader] {state}")
155 | _DisplayFeedback(state)
156 | elif name == "Save Location":
157 | self.toggledLocation = True
158 | self.consistentLocation = not self.consistentLocation
159 | state = "Save Location is now {}".format(
160 | "enabled (Saves on quickload quit)" if self.consistentLocation else "disabled"
161 | )
162 | Log(f"[Map Loader] {state}")
163 | _DisplayFeedback(state)
164 |
165 | def Enable(self) -> None:
166 | super().Enable()
167 |
168 | def Disable(self) -> None:
169 | ModMenu.RemoveHooks(self)
170 |
171 |
172 | _ModInstance = MapLoader()
173 | RegisterMod(_ModInstance)
174 |
--------------------------------------------------------------------------------
/Mods/ReadOnly/__init__.py:
--------------------------------------------------------------------------------
1 | from unrealsdk import *
2 | from ..ModManager import BL2MOD, RegisterMod
3 | import math
4 |
5 | class ReadOnly(BL2MOD):
6 | Name = "Borderlands Easy Read Only"
7 | Description = "Toggle Read Only on a button press"
8 | readOnly = False
9 | toggledReadOnly = False
10 |
11 | DefaultGameInfo = UObject.FindObjectsContaining("WillowCoopGameInfo WillowGame.Default__WillowCoopGameInfo")[0]
12 | Keybinds = [["Toggle Read Only", "F2"]]
13 |
14 | def displayFeedback(self):
15 | PC = GetEngine().GamePlayers[0].Actor
16 | HUDMovie = PC.myHUD.HUDMovie
17 | try:
18 | if PC is None or HUDMovie is None:
19 | return True
20 | if self.readOnly:
21 | HUDMovie.AddTrainingText("Read Only: Enabled", "Read Only", math.inf, (), "", False, 0, PC.PlayerReplicationInfo, True, 0, 0)
22 | elif self.toggledReadOnly:
23 | self.toggledReadOnly = False
24 | HUDMovie.ClearTrainingText()
25 | except:
26 | return True
27 | return True
28 |
29 | def GameInputPressed(self, input):
30 | if input.Name == "Toggle Read Only":
31 | self.toggledReadOnly = True
32 | self.readOnly = not self.readOnly
33 | self.displayFeedback()
34 |
35 | def Enable(self):
36 |
37 | def hookCanSaveGame(caller: UObject, function: UFunction, params: FStruct) -> bool:
38 | if self.readOnly:
39 | return False
40 | return True
41 |
42 | def hookTrainingText(caller: UObject, function: UFunction, params: FStruct):
43 | self.displayFeedback()
44 | return True
45 |
46 | RegisterHook("WillowGame.WillowPlayerController.CanSaveGame", "HookSaveGame", hookCanSaveGame)
47 | RegisterHook("WillowGame.WillowHUDGFxMovie.DrawTrainingText", "HookTrainingText", hookTrainingText)
48 | RegisterHook("WillowGame.WillowHUD.CreateWeaponScopeMovie", "HookTrainingText", hookTrainingText)
49 |
50 | def Disable(self):
51 | RemoveHook("WillowGame.WillowPlayerController.CanSaveGame", "HookSaveGame")
52 | RemoveHook("WillowGame.WillowHUDGFxMovie.DrawTrainingText", "HookTrainingText")
53 | RemoveHook("WillowGame.WillowHUD.CreateWeaponScopeMovie", "HookTrainingText")
54 |
55 | RegisterMod(ReadOnly())
--------------------------------------------------------------------------------
/Mods/__init__.py:
--------------------------------------------------------------------------------
1 | import unrealsdk
2 | import importlib
3 | import os
4 | import traceback
5 |
6 | # Need to make sure this is all loaded and aliased up before loading any mods
7 | from Mods import ModMenu # noqa: F401, E402
8 |
9 | _full_traceback = False
10 |
11 | for name in os.listdir(os.path.dirname(__file__)):
12 | absolute_path = os.path.join(os.path.dirname(__file__), name)
13 | if not os.path.isdir(absolute_path):
14 | continue
15 |
16 | # Temporarily filter out `General` incase people forget to delete it
17 | if name.startswith(".") or name in ("__pycache__", "ModMenu", "General"):
18 | continue
19 |
20 | try:
21 | importlib.import_module(f".{name}", "Mods")
22 | except Exception:
23 | unrealsdk.Log(f"Failed to import mod: {name}")
24 | tb = traceback.format_exc().split('\n')
25 | if _full_traceback:
26 | for line in tb:
27 | unrealsdk.Log(line)
28 | else:
29 | unrealsdk.Log(f" {tb[-4].strip()}")
30 | unrealsdk.Log(f" {tb[-3].strip()}")
31 | unrealsdk.Log(f" {tb[-2].strip()}")
32 |
--------------------------------------------------------------------------------
/Mods/linters.txt:
--------------------------------------------------------------------------------
1 | flake8-bugbear==20.11.1
2 | flake8-comprehensions==3.3.1
3 | flake8-isort==4.0.0
4 | flake8-mutable==1.2.0
5 | flake8-noqa==1.1.0
6 | flake8-pyi==20.10.0
7 | flake8-simplify==0.13.0
8 | flake8==3.8.4
9 | isort==5.7.0
10 | mypy==0.800
11 | pep8-naming==0.11.1
12 | rope==0.18.0
13 |
--------------------------------------------------------------------------------
/Mods/setup.cfg:
--------------------------------------------------------------------------------
1 | [mypy]
2 | python_version = 3.7
3 | strict = True
4 |
5 | [mypy-unrealsdk]
6 | ignore_missing_imports = True
7 |
8 | [flake8]
9 | python_version = 3.7
10 | ignore = Y011, SIM105, SIM106, E501, W503, N802, N803, N813, B901, B950
11 | max-line-length = 100
12 | ignore-names = PC,PCs,PRI,PRIs
13 | exclude = .env
14 | noqa-require-code = True
15 |
16 | [isort]
17 | py_version = 37
18 | line_length = 100
19 | known_unrealsdk = unrealsdk
20 | known_modmenu = Mods.ModMenu
21 | sections = FUTURE, UNREALSDK, STDLIB, THIRDPARTY, MODMENU, FIRSTPARTY, LOCALFOLDER
22 | no_lines_before = STDLIB, FIRSTPARTY
23 |
--------------------------------------------------------------------------------
/PythonSDK.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2037
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Borderlands 2 SDK", "PythonSDK.vcxproj", "{32794345-2BB2-45E0-A461-914D13B0279F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x86 = Debug|x86
11 | Release|x86 = Release|x86
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {32794345-2BB2-45E0-A461-914D13B0279F}.Debug|x86.ActiveCfg = Debug|Win32
15 | {32794345-2BB2-45E0-A461-914D13B0279F}.Debug|x86.Build.0 = Debug|Win32
16 | {32794345-2BB2-45E0-A461-914D13B0279F}.Release|x86.ActiveCfg = Release|Win32
17 | {32794345-2BB2-45E0-A461-914D13B0279F}.Release|x86.Build.0 = Release|Win32
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {0F5A3673-B782-4AAF-B0EA-528EA4892494}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/PythonSDK.sln.DotSettings.user:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repository has been archived.
2 |
3 | If you're looking for the [BL2/TPS/AoDK Mod Manager, see here](https://github.com/bl-sdk/willow2-mod-manager/)
4 |
5 | If you were looking for the code, [bl-sdk/unrealsdk](https://github.com/bl-sdk/unrealsdk) and
6 | [bl-sdk/pyunrealsdk](https://github.com/bl-sdk/pyunrealsdk) and are the successors to this repo.
7 |
8 |
9 | # UnrealEngine PythonSDK
10 | [](https://discord.gg/bXeqV8Ef9R)
11 | [](https://discord.gg/VJXtHvh)
12 |
13 | An UnrealEngine Plugin enabling using Python to write plugins that interact directly with UE objects
14 |
15 | ## List of confirmed compatible games
16 | - Borderlands 2
17 | - Borderlands: The Pre-Sequel
18 | - Tiny Tina’s Assault on Dragon Keep: A Wonderlands One-shot Adventure
19 |
--------------------------------------------------------------------------------
/scripts/add_init.py:
--------------------------------------------------------------------------------
1 | from os import listdir
2 |
3 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\pydefs\\'
4 |
5 | relationships = {}
6 |
7 | template = '\t\t.def(py::init<>())\n'
8 |
9 | for filename in listdir(sdk_dir):
10 | if '_structs' in filename:
11 | lines = []
12 | with open(sdk_dir + filename) as f:
13 | for line in f.readlines():
14 | lines.append(line)
15 | if 'py::class_' in line:
16 | lines.append(template)
17 |
18 | with open(sdk_dir + filename, 'w') as f:
19 | for line in lines:
20 | f.write(line)
--------------------------------------------------------------------------------
/scripts/add_static.py:
--------------------------------------------------------------------------------
1 | from os import listdir
2 |
3 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\pydefs\\'
4 |
5 | relationships = {}
6 |
7 | template = ' .def_static("StaticClass", &{}::StaticClass, py::return_value_policy::reference)\n'
8 |
9 | for filename in listdir(sdk_dir):
10 | if 'Class' in filename:
11 | lines = []
12 | with open(sdk_dir + filename) as f:
13 | for line in f.readlines():
14 | lines.append(line)
15 | if 'py::class_' in line:
16 | obj_def = line.split('<')[1].split('>')[0].strip()
17 | obj = obj_def.split(',')[0]
18 | lines.append(template.format(obj))
19 |
20 | with open(sdk_dir + filename, 'w') as f:
21 | for line in lines:
22 | f.write(line)
--------------------------------------------------------------------------------
/scripts/distribute_tarrays.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | modules = ['Core',
5 | 'Engine',
6 | 'GameFramework',
7 | 'GFxUI',
8 | 'GearboxFramework',
9 | 'WillowGame',
10 | 'AkAudio',
11 | 'IpDrv',
12 | 'WinDrv',
13 | 'XAudio2',
14 | 'OnlineSubsystemSteamworks',
15 | 'Base']
16 |
17 | dir_path_structs = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/python/include/sdk/structs/'
18 | dir_path_classes = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/python/include/sdk/classes/'
19 | tarrays_file = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/python/include/sdk/TArrayTypes.py'
20 |
21 | tarrays = {}
22 |
23 | header = "._fields_ = "
24 | template = """class {}(Structure):\n\
25 | pass\n\n"""
26 |
27 | with open(tarrays_file) as f:
28 | lines = f.readlines()
29 | for index, line in enumerate(lines):
30 | if header in line:
31 | tarray_type = lines[index + 1].split('(')[-1].split(')')[0]
32 | tarrays[tarray_type] = 'class ' + ''.join(lines[index : index + 5]).replace('._fields_','(Structure)\n _fields_')
33 |
34 | for module in modules:
35 | lines = []
36 | found = []
37 | top = []
38 | done = False
39 | with open(dir_path_structs + module + '.py') as f:
40 | after = None
41 | for line in f.readlines():
42 | lines.append(line)
43 | if 'class ' in line:
44 | defining = line.split(' ')[1].split('(')[0]
45 | if defining in tarrays.keys():
46 | found.append(defining)
47 | after = defining
48 | if line == '\n' and after:
49 | lines.append(tarrays[after])
50 | after = None
51 | with open(dir_path_structs + module + '.py', 'w') as f:
52 | for l in lines:
53 | f.write(l)
--------------------------------------------------------------------------------
/scripts/fix_classes.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | function_regex = re.compile(r'\[(\d+)\] Class (\w+)\.(\w+).*')
5 | functions = {}
6 | with open('C:\\SDK_GEN\\BL2\\ObjectsDump.txt') as f:
7 | lookahead = None
8 | for line in f.readlines():
9 | if '] Class ' in line:
10 | matches = function_regex.match(line)
11 | if matches:
12 | id, module, clas = matches.groups()
13 | if module not in functions.keys():
14 | functions[module] = {}
15 | functions[module][clas] = id
16 |
17 |
18 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\'
19 | for module in functions.keys():
20 | lines = []
21 | with open(sdk_dir + module + '_classes.h') as f:
22 | new_id = None
23 | for line in f.readlines():
24 | if line.startswith('class '):
25 | clas = line.split(' ')[1][1:].strip()
26 | if clas in functions[module].keys():
27 | new_id = functions[module][clas]
28 | if 'UObject::GObjObjects()->Data' in line:
29 | old_id = line.split('[')[1].split(']')[0]
30 | line = line.replace(old_id, str(int(new_id)))
31 | lines.append(line)
32 | with open(sdk_dir + module + '_classes.h', 'w') as f:
33 | for line in lines:
34 | f.write(line)
--------------------------------------------------------------------------------
/scripts/fix_fuckup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | modules = ['Core',
5 | 'Engine',
6 | 'GameFramework',
7 | 'GFxUI',
8 | 'GearboxFramework',
9 | 'WillowGame',
10 | 'AkAudio',
11 | 'IpDrv',
12 | 'WinDrv',
13 | 'XAudio2',
14 | 'OnlineSubsystemSteamworks']
15 |
16 | dir_path_lua = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/include/sdk/classes/'
17 | dir_path_python = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/python/include/sdk/classes/'
18 | used = set()
19 | defined = set()
20 |
21 | used_regex = re.compile(r'.* TArray_(/w+).*')
22 | defined_regex = re.compile(r'.*struct TArray_(/w+) {')
23 |
24 | for module in modules:
25 | lens = []
26 | with open(dir_path_lua + module + '.lua') as f:
27 | for line in f.readlines():
28 | if '[0x' in line:
29 | lens += [line.split('0x')[1].split(']')[0]]
30 | i = 0
31 | lines = []
32 | with open(dir_path_python + module + '.py') as f:
33 | for line in f.readlines():
34 | if '0x' in line:
35 | if '0x)' in line:
36 | print(line)
37 | line = line.replace('0x)', '0x{})'.format(lens[i]))
38 | print(line)
39 | i = i + 1
40 | lines.append(line)
41 | with open(dir_path_python + module + '.py', 'w') as f:
42 | for line in lines:
43 | f.write(line)
--------------------------------------------------------------------------------
/scripts/fix_funcs.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | function_regex = re.compile(r'\[(\d+)\] Function (\w+)\.(\w+)\.(\w+).*')
5 | property_regex = re.compile(r'\[(\d+)\] \w+ (\w+)\.(\w+)\.(\w+)\.(\w+).*')
6 | functions = {}
7 | with open('C:\\SDK_GEN\\BL2\\ObjectsDump.txt') as f:
8 | lookahead = None
9 | for line in f.readlines():
10 | if '] Function ' in line:
11 | matches = function_regex.match(line)
12 | if matches:
13 | id, module, clas, func = matches.groups()
14 | if module not in functions.keys():
15 | functions[module] = {}
16 | functions[module]['{}.{}'.format(clas, func)] = {'id': id, 'properties': {}}
17 | elif 'Property ' in line:
18 | matches = property_regex.match(line)
19 | if matches:
20 | id, module, clas, func, prop = matches.groups()
21 | if module in functions.keys() and '{}.{}'.format(clas, func) in functions[module].keys():
22 | functions[module]['{}.{}'.format(clas, func)]['properties'][prop] = id
23 |
24 |
25 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\'
26 | for module in functions.keys():
27 | lines = []
28 | with open(sdk_dir + module + '_functions.cpp') as f:
29 | new_id = None
30 | for line in f.readlines():
31 | if line[0] != '\t' and '::' in line:
32 | clas = line.split('::')[0].split(' ')[-1].strip()[1:]
33 | if 'pFn' in line and 'UObject::GObjObjects()->Data' in line:
34 | func = clas + '.' + line.split('pFn')[1].split(' ')[0]
35 | if func in functions[module].keys():
36 | new_id = functions[module][func]
37 | old_id = line.split('[')[1].split(']')[0]
38 | print(new_id)
39 | line = line.replace(old_id, str(int(new_id['id'])))
40 | lines.append(line)
41 | with open(sdk_dir + module + '_functions.cpp', 'w') as f:
42 | for line in lines:
43 | f.write(line)
--------------------------------------------------------------------------------
/scripts/fix_pointers.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | files = ['Core_classes.h',
5 | 'Engine_classes.h',
6 | 'GameFramework_classes.h',
7 | 'GFxUI_classes.h',
8 | 'GearboxFramework_classes.h',
9 | 'WillowGame_classes.h',
10 | 'AkAudio_classes.h',
11 | 'IpDrv_classes.h',
12 | 'WinDrv_classes.h',
13 | 'XAudio2_classes.h',
14 | 'OnlineSubsystemSteamworks_classes.h']
15 | import os
16 |
17 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
18 |
19 | template = 'UClass* {}::pClassPointer = NULL;'
20 |
21 | funcs = {}
22 | for clas in files:
23 | os.system('cls')
24 | print(clas)
25 | with open(dir_path_python + clas) as f:
26 | for line in f.readlines():
27 | if line.startswith('class '):
28 | print(template.format(line.split(' ')[1]))
29 | input("Press Enter to continue...")
--------------------------------------------------------------------------------
/scripts/fix_references.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | files = ['Core_classes.h',
5 | 'Engine_classes.h',
6 | 'GameFramework_classes.h',
7 | 'GFxUI_classes.h',
8 | 'GearboxFramework_classes.h',
9 | 'WillowGame_classes.h',
10 | 'AkAudio_classes.h',
11 | 'IpDrv_classes.h',
12 | 'WinDrv_classes.h',
13 | 'XAudio2_classes.h',
14 | 'OnlineSubsystemSteamworks_classes.h']
15 |
16 | classes = ["AActor",
17 | "ACamera",
18 | "AController",
19 | "ADualWieldActionSkill",
20 | "AGameInfo",
21 | "AGearboxMind",
22 | "AInventoryManager",
23 | "ALiftActionSkill",
24 | "AMissionTracker",
25 | "APawn",
26 | "APlayerController",
27 | "AResourcePoolManager",
28 | "AStatusEffectProxyActor",
29 | "AVehicle",
30 | "AWillowCoopGameInfo",
31 | "AWillowEquipAbleItem",
32 | "AWillowInteractiveObject",
33 | "AWillowInventory",
34 | "AWillowInventoryManager",
35 | "AWillowItem",
36 | "AWillowPawn",
37 | "AWillowPlayerController",
38 | "AWillowPlayerPawn",
39 | "AWillowReplicatedEmitter",
40 | "AWillowShield",
41 | "AWillowTurretWeapon",
42 | "AWillowVehicle",
43 | "AWillowVehicleBase",
44 | "AWillowVehicleWeapon",
45 | "AWillowVendingMachineBlackMarket",
46 | "AWillowWeapon",
47 | "AWillowWeaponPawn",
48 | "AWorldInfo",
49 | "UActionSequence",
50 | "UAIFactoryBase",
51 | "UAnimSequence",
52 | "UAssetLibraryManager",
53 | "UAttributeDefinition",
54 | "UBehaviorBase",
55 | "UBehaviorKernel",
56 | "UBodyClassDefinition",
57 | "UCameraModifierLookAt",
58 | "UChassisDefinition",
59 | "UCustomizationGFxMovie",
60 | "UCylinderComponent",
61 | "UFaceFXAsset",
62 | "UForceFeedbackWaveform",
63 | "UGearboxAIFactory",
64 | "UGearboxCoverStateManager",
65 | "UGearboxDialogComponent",
66 | "UIHitRegionInfoProvider",
67 | "UInteractiveObjectDefinition",
68 | "UIParameterBehavior",
69 | "UIStatusEffectTarget",
70 | "UMaterialInstanceConstant",
71 | "UMaterialInterface",
72 | "UMeshComponent",
73 | "UNavigationHandle",
74 | "UParticleSystemComponent",
75 | "UPhysicalMaterial",
76 | "UPlayerSkillTree",
77 | "UPrimitiveComponent",
78 | "USeqAct_Toggle",
79 | "USequenceAction",
80 | "USequenceOp",
81 | "USkeletalMeshComponent",
82 | "UStaticMeshComponent",
83 | "UUIDataStore_OnlinePlaylists",
84 | "UUIResourceCombinationProvider",
85 | "UVehicleSpawnStationGFxMovie",
86 | "UWillowGlobals"]
87 |
88 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
89 |
90 | class Function_def():
91 | def __init__(self, return_type, name, params, pointers):
92 | self.return_type = return_type
93 | self.name = name
94 | self.params = params
95 | self.pointers = pointers
96 |
97 | class Pointer():
98 | def __init__(self, pre, t, name):
99 | self.pre = pre
100 | self.type = t
101 | self.name = name
102 |
103 |
104 |
105 | template = '[]({class_name} &self {py_args}) {{ {init} {capture} self.{function_name}({new_args}); return py::make_tuple({returned_args}); }})\n'
106 |
107 | def generate_lambda(fun):
108 | py_args = fun.params
109 | init = ""
110 | replaced = []
111 | for pointer in fun.pointers:
112 | replaced.append(pointer.name)
113 | if pointer.pre:
114 | pointer_arg = "{} {} py{}".format(pointer.pre, pointer.type, pointer.name)
115 | init += pointer_arg + " = 0 ; "
116 | else:
117 | pointer_arg = "{} py{}".format(pointer.type, pointer.name)
118 | if pointer.type == 'char':
119 | init += pointer_arg + " = malloc(sizeof(char) * 0xFF) ; "
120 | else:
121 | init += pointer_arg + " = ({0})malloc(sizeof({1})) ; ".format(pointer.type, pointer.type[:-1])
122 | py_args = py_args.replace(pointer_arg.replace(" py", " "), "")
123 | py_args = py_args.strip()
124 | py_args = py_args.replace(', ,', ',')
125 | py_args = py_args.replace(', ,', ',')
126 | py_args = py_args.replace(', ,', ',')
127 | if py_args.startswith(','):
128 | py_args = py_args[1:]
129 | if py_args.endswith(','):
130 | py_args = py_args[:-1]
131 | py_args = py_args.strip()
132 | if py_args:
133 | py_args = ', ' + py_args
134 | new_args = ', '.join([parm.split(' ')[-1] if parm.split(' ')[-1] not in replaced else "py" + parm.split(' ')[-1] for parm in fun.params.split(', ')])
135 | returned_args = ', '.join(["*py" + parm.name for parm in fun.pointers])
136 | capture = ""
137 | if fun.return_type != "void":
138 | capture = '{} ret = '.format(fun.return_type)
139 | if returned_args:
140 | returned_args = 'ret, ' + returned_args
141 | else:
142 | returned_args = 'ret'
143 |
144 | return(template.format(class_name = clas, py_args = py_args, init = init, capture = capture, function_name = fun.name, new_args = new_args, returned_args = returned_args))
145 |
146 |
147 | funcs = {}
148 | for clas in files:
149 | print(clas)
150 | with open(dir_path_python + clas) as f:
151 | class_name = None
152 | for line in f.readlines():
153 | if line.startswith("class "):
154 | class_name = line.split(" ")[1].strip()
155 | funcs[class_name] = {}
156 | if class_name not in classes:
157 | continue
158 | if ');' in line and '*' in line:
159 | start, end = line.split('(')
160 | function_name = start.split(' ')[-1]
161 | return_type = ' '.join(start.split(' ')[:-1]).strip()
162 | params = line.split('(')[-1].split(')')[0]
163 | pointers = []
164 | for param in params.split(', '):
165 | if 'TArray' in param:
166 | continue
167 | if (param.startswith('struct ') or param.startswith('class ')) and '**' in param:
168 | s = param.split(' ')
169 | pointers.append(Pointer(s[0], s[1], s[2]))
170 | elif not param.startswith('struct ') and not param.startswith('class ') and '*' in param:
171 | s = param.split(' ')
172 | pointers.append(Pointer(None, ' '.join(s[:-1]), s[-1]))
173 | if pointers:
174 | funcs[class_name][function_name] = Function_def(return_type, function_name, params, pointers)
175 |
176 | pydefs = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/pydefs/'
177 |
178 | for clas in funcs.keys():
179 | if not funcs[clas]:
180 | continue
181 | with open(pydefs + '_Classes_{}.cpp'.format(clas)) as f:
182 | lines = []
183 | for line in f.readlines():
184 | if '.def(' in line:
185 | function_name = line.split('"')[1]
186 | if function_name in funcs[clas].keys():
187 | lines += line.split(',')[0] + ', ' + generate_lambda(funcs[clas][function_name])
188 | continue
189 | if '.staticmethod' in line:
190 | continue
191 | lines += line
192 | with open(pydefs + '_Classes_{}.cpp'.format(clas), 'w') as f:
193 | for line in lines:
194 | f.write(line)
--------------------------------------------------------------------------------
/scripts/gen_funcs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | dir_path = os.path.dirname(os.path.realpath(__file__))
5 | funcs = {}
6 |
7 | regex = re.compile(r'^.* (\w+)::(\w+).*\)$')
8 |
9 | for root, dirs, files in os.walk(dir_path):
10 | for file in files:
11 | if file.endswith("_functions.cpp"):
12 | with open(root + '\\' + file) as f:
13 | module = file.split('_')[0]
14 | funcs[module] = []
15 | in_funcs = False
16 | for line in f.readlines():
17 | if not in_funcs and not line.startswith('# Functions'):
18 | continue
19 | in_funcs = True
20 | found = regex.match(line)
21 | if found:
22 | clas, fun = found.groups()
23 | funcs[module].append((clas, fun))
24 |
25 | classFunc_regex = re.compile(r'.*g_classFuncs\["(\w+)"\] = {')
26 |
27 | lua_sdk_path = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Borderlands 2\\Binaries\\plugins\\include\\sdk\\funcs\\"
28 | from os import listdir
29 | from os.path import isfile, join
30 | onlyfiles = [f for f in listdir(lua_sdk_path) if isfile(join(lua_sdk_path, f)) and f.endswith('.lua')]
31 | print(onlyfiles)
32 | for filename in onlyfiles:
33 | index = 0
34 | l = 0
35 | with open(lua_sdk_path + filename) as f:
36 | with open(lua_sdk_path + filename + '.new', 'w') as f_out:
37 | module_name = filename.split('.')[0]
38 | print(module_name)
39 | classes = set()
40 | for fun in funcs[module_name]:
41 | classes.add(fun[0])
42 | for clas in classes:
43 | f_out.write('g_classFuncs["{}"] = {{}}\n'.format(clas))
44 | for line in f.readlines():
45 | l += 1
46 | found = classFunc_regex.match(line)
47 | if found:
48 | func = found.groups()[0]
49 | clas, fun = funcs[module_name][index]
50 | if func.lower() == fun.lower() or func.lower() == 'event' + fun.lower():
51 | f_out.write(line.replace(func, '{}"]["{}'.format(clas, func)))
52 | else:
53 | print(module_name)
54 | print('bad')
55 | print(l, clas, fun, func)
56 | exit()
57 | index += 1
58 | else:
59 | f_out.write(line)
60 |
61 | #
62 | # template = \
63 | # """struct {0};
64 | # struct TArray_{0} {{
65 | # struct {0}* Data;
66 | # int Count;
67 | # int Max;
68 | # }};
69 | # """
70 | #
71 | # for tarray in (used - defined):
72 | # print(template.format(tarray))
73 | #
74 | # for tarray in (used - defined):
75 | # print('table.insert(g_TArrayTypes, "{}")'.format(tarray))
76 |
--------------------------------------------------------------------------------
/scripts/merge.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 |
5 | headers = """class {}(Structure):\n\
6 | pass\n\n"""
7 |
8 | modules = ['Core',
9 | 'Engine',
10 | 'GameFramework',
11 | 'GFxUI',
12 | 'GearboxFramework',
13 | 'WillowGame',
14 | 'AkAudio',
15 | 'IpDrv',
16 | 'WinDrv',
17 | 'XAudio2',
18 | 'OnlineSubsystemSteamworks',
19 | 'Base']
20 |
21 | dir_path_python = 'C:/Program Files (x86)/Steam/steamapps/common/Borderlands 2/Binaries/plugins/python/include/sdk/classes/'
22 | used = set()
23 | defined = set()
24 |
25 |
26 | for module in modules:
27 | top = []
28 | middle = []
29 | bottom = []
30 | with open(dir_path_python + module + '.py') as f:
31 | in_bottom = False
32 | for line in f.readlines():
33 | if 'BL2SDK.g_loadedClasses += [' in line:
34 | in_bottom = True
35 | if in_bottom:
36 | bottom.append(line)
37 | else:
38 | middle.append(line)
39 | if not in_bottom and '._fields_' in line:
40 | top.append(headers.format(line.split('.')[0]))
41 |
42 | with open(dir_path_python + module + '.py', 'w') as f:
43 | for line in top:
44 | f.write(line)
45 | f.write("def __init__():\n")
46 | for line in middle:
47 | f.write(" " + line)
48 | for line in bottom:
49 | f.write(" " + line)
--------------------------------------------------------------------------------
/scripts/prune.py:
--------------------------------------------------------------------------------
1 | from os import listdir, remove
2 |
3 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\pydefs\\'
4 |
5 | relationships = {}
6 | empty = {}
7 |
8 | for filename in listdir(sdk_dir):
9 | is_empty = False
10 | if '_Class' in filename:
11 | with open(sdk_dir + filename) as f:
12 | class_name = None
13 | count = 0
14 | for line in f.readlines():
15 | if 'py::class_' in line:
16 | count = 0
17 | parent_name = None
18 | obj_def = line.split('<')[1].split('>')[0].strip()
19 | class_name = obj_def.split(',')[0].strip()
20 | if len(obj_def.split(',')) > 1:
21 | parent_name = obj_def.split(',')[1].strip()
22 | if parent_name not in relationships.keys():
23 | relationships[parent_name] = []
24 | relationships[parent_name].append(class_name)
25 | if class_name and ';' in line:
26 | if count <= 2:
27 | empty[class_name] = parent_name
28 | is_empty = True
29 | if class_name and line.strip():
30 | count = count + 1
31 |
32 | actual_empty = {}
33 | for class_name in empty.keys():
34 | if class_name not in relationships.keys():
35 | actual_empty[class_name] = empty[class_name]
36 | remove(sdk_dir + "_Classes_{}.cpp".format(class_name))
37 |
38 | empty = actual_empty
39 |
40 | typemap = "C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/TypeMap.h"
41 | lines = ""
42 | with open(typemap) as f:
43 | for line in f.readlines():
44 | if line.startswith(" {"):
45 | class_name = line.split('(')[1].split(')')[0]
46 | if class_name in empty.keys():
47 | parent = empty[class_name]
48 | if parent != 'UObject':
49 | line = line.replace('({})'.format(class_name), '({})'.format(parent))
50 | else:
51 | line = ""
52 | lines += line
53 |
54 | with open(typemap, 'w') as f:
55 | f.write(lines)
56 |
--------------------------------------------------------------------------------
/scripts/readd_bitfields.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | top = """#include "stdafx.h"
5 | // Using =======================================================================
6 | namespace py = pybind11;
7 |
8 | // Module ======================================================================
9 | void Export_pystes_{}(py::module &m)
10 | {{
11 | """
12 |
13 | bottom = """
14 | }"""
15 |
16 | files = ['gamedefines.cpp']
17 |
18 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/pydefs/'
19 |
20 | dir_path_h = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
21 |
22 | classes = {}
23 | for filename in os.listdir(dir_path_h):
24 | if '_classes.h' in filename or '_structs.h' in filename:
25 | objs = {}
26 | with open(dir_path_h + filename) as f:
27 | c = None
28 | fields = []
29 | for line in f.readlines():
30 | if line.startswith('class ') or line.startswith('struct '):
31 | c = line.split(' ')[1].strip()
32 | if ' : 1;' in line:
33 | field = line.split(' : 1;')[0].split(' ')[-1]
34 | fields.append(field)
35 | if line == '};\n':
36 | objs[c] = fields
37 | c = None
38 | fields = []
39 | classes[filename.split('.')[0]] = objs
40 |
41 | bitfield_def = '\t\t.def_property("{field_name}", []({class_name} &self){{return self.{field_name};}}, []({class_name} &self, bool value){{self.{field_name} = value ? 1 : 0;}})\n'
42 |
43 | for s in classes.keys():
44 | lines = []
45 | with open(dir_path_python + s + '.cpp') as f:
46 | for line in f.readlines():
47 | lines.append(line)
48 | if 'class_<' in line:
49 | c = line.split('class_<')[1].strip().split(' ')[0].split(',')[0].strip()
50 | if c in classes[s].keys():
51 | for field in classes[s][c]:
52 | lines.append(bitfield_def.format(field_name=field, class_name=c))
53 | with open(dir_path_python + s + '.cpp', 'w') as f:
54 | for line in lines:
55 | f.write(line)
56 |
57 |
58 | # for filename in new_files:
59 | # print('void Export_pystes{}(py::module &m);'.format(filename))
60 | # with open(dir_path_python + filename + '.cpp', 'w') as f:
61 | # f.write(top.format(filename))
62 | # for line in new_files[filename]:
63 | # f.write(line)
64 | # f.write(bottom)
--------------------------------------------------------------------------------
/scripts/regen_pydefs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | top = """#include "stdafx.h"
5 | // Using =======================================================================
6 | namespace py = pybind11;
7 |
8 | // Module ======================================================================
9 | void Export_pystes_{}(py::module &m)
10 | {{
11 | """
12 |
13 | bottom = """
14 | }"""
15 |
16 | class_def = '\tpy::class_< {class_with_parent} >(m, "{class_name}")\n'
17 | bitfield_def = '\t\t.def_property("{field_name}", []({class_name} &self){{return self.{field_name};}}, []({class_name} &self, bool value){{self.{field_name} = value ? 1 : 0;}})\n'
18 | staticclass_def = '\t\t.def_static("StaticClass", &{class_name}::StaticClass, py::return_value_policy::reference)\n'
19 | variable_def = '\t\t.def_readwrite("{var_name}", &{class_name}::{var_name}{policy})\n'
20 | function_def = '\t\t.def{static_def}("{func_name}", &{class_name}::{func_name}{policy})\n'
21 | policy_def = ', py::return_value_policy::reference'
22 | reference_def = '\t\t.def{static_def}("{func_name}", {l}'
23 | reference_template = '[]({class_name} &self {py_args}) {{ {init} {capture} self.{function_name}({new_args}); return py::make_tuple({returned_args}); }})\n'
24 |
25 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/pydefs/'
26 |
27 | dir_path_h = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
28 |
29 | def generate_lambda(clas, fun):
30 | py_args = fun.params
31 | init = ""
32 | replaced = []
33 | for pointer in fun.pointers:
34 | replaced.append(pointer.name)
35 | if pointer.pre:
36 | pointer_arg = "{} {} py{}".format(pointer.pre, pointer.type, pointer.name)
37 | init += pointer_arg + " = 0 ; "
38 | else:
39 | pointer_arg = "{} py{}".format(pointer.type, pointer.name)
40 | if pointer.type == 'char':
41 | init += pointer_arg + " = malloc(sizeof(char) * 0xFF) ; "
42 | else:
43 | init += pointer_arg + " = ({0})malloc(sizeof({1})) ; ".format(pointer.type, pointer.type[:-1])
44 | py_args = py_args.replace(pointer_arg.replace(" py", " "), "")
45 | py_args = py_args.strip()
46 | py_args = py_args.replace(', ,', ',')
47 | py_args = py_args.replace(', ,', ',')
48 | py_args = py_args.replace(', ,', ',')
49 | if py_args.startswith(','):
50 | py_args = py_args[1:]
51 | if py_args.endswith(','):
52 | py_args = py_args[:-1]
53 | py_args = py_args.strip()
54 | if py_args:
55 | py_args = ', ' + py_args
56 | new_args = ', '.join([parm.split(' ')[-1] if parm.split(' ')[-1] not in replaced else "py" + parm.split(' ')[-1] for parm in fun.params.split(', ')])
57 | returned_args = ', '.join(["*py" + parm.name for parm in fun.pointers])
58 | capture = ""
59 | if fun.return_type != "void":
60 | capture = '{} ret = '.format(fun.return_type)
61 | if returned_args:
62 | returned_args = 'ret, ' + returned_args
63 | else:
64 | returned_args = 'ret'
65 |
66 | return(reference_template.format(class_name = clas, py_args = py_args, init = init, capture = capture, function_name = fun.name, new_args = new_args, returned_args = returned_args))
67 |
68 | class Function_def():
69 | def __init__(self, return_type, name, params, pointers, static = False):
70 | self.return_type = return_type
71 | self.name = name
72 | self.params = params
73 | self.pointers = pointers
74 | self.static = static
75 |
76 | class Pointer():
77 | def __init__(self, pre, t, name):
78 | self.pre = pre
79 | self.type = t
80 | self.name = name
81 |
82 | classes = {}
83 | for filename in os.listdir(dir_path_h):
84 | if '_classes.h' in filename or ('_structs.h' in filename and not '_f_' in filename):
85 | objs = {}
86 | with open(dir_path_h + filename) as f:
87 | c = None
88 | fields = []
89 | variables = []
90 | functions = []
91 | static_functions = []
92 | reference_functions = {}
93 | for line in f.readlines():
94 | if line.startswith('class ') or line.startswith('struct '):
95 | c = line.split(' ')[1].strip()
96 | objs[c] = {}
97 | if ':' in line:
98 | objs[c]['parent'] = line.split(' ')[-1].strip()
99 | if '[' in line.split('//')[0]:
100 | continue
101 | if ' : 1;' in line:
102 | field = line.split(' : 1;')[0].split(' ')[-1]
103 | fields.append(field)
104 | elif line.startswith('\t') and not line.startswith('\t\t') and line.strip() != '};':
105 | line = line.split('//')[0].strip()
106 | if line.endswith(';') and line[-2] != ')' and not 'pClassPointer' in line:
107 | needs_reference = '*' in line or 'class ' in line or 'struct ' in line or 'TArray' in line
108 | name = line.split(' ')[-1].split('\t')[-1][:-1].strip()
109 | variables.append((name, needs_reference))
110 | elif line.endswith(');') and not line.startswith('virtual') and not line.startswith('template') and not (line.startswith('static') and 'StaticClass' in line):
111 | static = line.startswith('static')
112 | if not '*' in line:
113 | needs_reference = line.startswith('TArray') or line.startswith('class') or line.startswith('struct')
114 | name = line.split('(')[0].split(' ')[-1].split('\t')[-1]
115 | functions.append((name, needs_reference, static))
116 | else:
117 | start, end = line.split('(')
118 | function_name = start.split(' ')[-1]
119 | return_type = ' '.join(start.split(' ')[:-1]).strip()
120 | params = line.split('(')[-1].split(')')[0]
121 | pointers = []
122 | for param in params.split(', '):
123 | if 'TArray' in param:
124 | continue
125 | if (param.startswith('struct ') or param.startswith('class ')) and '**' in param:
126 | s = param.split(' ')
127 | pointers.append(Pointer(s[0], s[1], s[2]))
128 | elif not param.startswith('struct ') and not param.startswith('class ') and '*' in param:
129 | s = param.split(' ')
130 | pointers.append(Pointer(None, ' '.join(s[:-1]), s[-1]))
131 | if pointers:
132 | reference_functions[function_name] = Function_def(return_type, function_name, params, pointers, static)
133 | else:
134 | needs_reference = line.startswith('TArray') or line.startswith('class') or line.startswith('struct')
135 | name = line.split('(')[0].split(' ')[-1].split('\t')[-1]
136 | functions.append((name, needs_reference, static))
137 | elif line.startswith('static') and 'StaticClass' in line:
138 | objs[c]['static'] = True
139 | if line == '};\n' or line.endswith(' {};\n'):
140 | objs[c]['bitfields'] = fields
141 | objs[c]['variables'] = variables
142 | objs[c]['functions'] = functions
143 | objs[c]['reference_functions'] = reference_functions
144 | c = None
145 | fields = []
146 | variables = []
147 | functions = []
148 | reference_functions = {}
149 | classes[filename.split('.')[0]] = objs
150 |
151 |
152 | # print('void Export_pystes{}(py::module &m);'.format(filename))
153 | for module in classes.keys():
154 | with open(dir_path_python + module + '.cpp', 'w') as f:
155 | f.write(top.format(module))
156 | objs = classes[module]
157 | for ck in objs.keys():
158 | c = objs[ck]
159 | name = ck
160 | if 'parent' in c.keys():
161 | name = '{}, {}'.format(name, c['parent'])
162 | f.write(class_def.format(class_with_parent=name, class_name=ck))
163 | if ck.startswith('F'):
164 | f.write('\t\t.def(py::init<>())\n')
165 | else:
166 | print('\t{{"{0}", &typeid({1})}},'.format(ck[1:], ck))
167 | if 'static' in c.keys():
168 | f.write(staticclass_def.format(class_name=ck))
169 | if c['bitfields']:
170 | for field in c['bitfields']:
171 | f.write(bitfield_def.format(class_name=ck, field_name=field))
172 | if c['variables']:
173 | for variable in c['variables']:
174 | policy = policy_def if variable[1] else ''
175 | f.write(variable_def.format(class_name=ck, var_name=variable[0], policy=policy))
176 | if c['functions']:
177 | for function in c['functions']:
178 | policy = policy_def if function[1] else ''
179 | f.write(function_def.format(static_def='' if not function[2] else '_static', class_name=ck, func_name=function[0], policy=policy))
180 | if c['reference_functions']:
181 | for reference_function in c['reference_functions'].keys():
182 | f.write(reference_def.format(static_def='' if not c['reference_functions'][reference_function].static else '_static', func_name=reference_function, l=generate_lambda(ck, c['reference_functions'][reference_function])))
183 | f.write('\t\t;\n')
184 | f.write(bottom)
185 |
186 | # for s in classes.keys():
187 | # lines = []
188 | # with open(dir_path_python + s + '.cpp') as f:
189 | # for line in f.readlines():
190 | # lines.append(line)
191 | # if 'class_<' in line:
192 | # c = line.split('class_<')[1].strip().split(' ')[0].split(',')[0].strip()
193 | # if c in classes[s].keys():
194 | # for field in classes[s][c]:
195 | # lines.append(bitfield_def.format(field_name=field, class_name=c))
196 | # with open(dir_path_python + s + '.cpp', 'w') as f:
197 | # for line in lines:
198 | # f.write(line)
199 |
200 |
--------------------------------------------------------------------------------
/scripts/regen_statics.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | top = """#include "stdafx.h"
5 | // Using =======================================================================
6 | namespace py = pybind11;
7 |
8 | // Module ======================================================================
9 | void Export_pystes_{}(py::module &m)
10 | {{
11 | """
12 |
13 | bottom = """
14 | }"""
15 |
16 | class_def = '\tpy::class_< {class_with_parent} >(m, "{class_name}")\n'
17 | bitfield_def = '\t\t.def_property("{field_name}", []({class_name} &self){{return self.{field_name};}}, []({class_name} &self, bool value){{self.{field_name} = value ? 1 : 0;}})\n'
18 | staticclass_def = '\t\t.def_static("StaticClass", &{class_name}::StaticClass, py::return_value_policy::reference)\n'
19 | variable_def = '\t\t.def_readwrite("{var_name}", &{class_name}::{var_name}{policy})\n'
20 | function_def = '\t\t.def{static_def}("{func_name}", &{class_name}::{func_name}{policy})\n'
21 | policy_def = ', py::return_value_policy::reference'
22 | reference_def = '\t\t.def{static_def}("{func_name}", {l}'
23 | reference_template = '[]({class_name} &self {py_args}) {{ {init} {capture} self.{function_name}({new_args}); return py::make_tuple({returned_args}); }})\n'
24 |
25 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/pydefs/'
26 |
27 | dir_path_h = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
28 |
29 | def generate_lambda(clas, fun):
30 | py_args = fun.params
31 | init = ""
32 | replaced = []
33 | for pointer in fun.pointers:
34 | replaced.append(pointer.name)
35 | if pointer.pre:
36 | pointer_arg = "{} {} py{}".format(pointer.pre, pointer.type, pointer.name)
37 | init += pointer_arg + " = 0 ; "
38 | else:
39 | pointer_arg = "{} py{}".format(pointer.type, pointer.name)
40 | if pointer.type == 'char':
41 | init += pointer_arg + " = malloc(sizeof(char) * 0xFF) ; "
42 | else:
43 | init += pointer_arg + " = ({0})malloc(sizeof({1})) ; ".format(pointer.type, pointer.type[:-1])
44 | py_args = py_args.replace(pointer_arg.replace(" py", " "), "")
45 | py_args = py_args.strip()
46 | py_args = py_args.replace(', ,', ',')
47 | py_args = py_args.replace(', ,', ',')
48 | py_args = py_args.replace(', ,', ',')
49 | if py_args.startswith(','):
50 | py_args = py_args[1:]
51 | if py_args.endswith(','):
52 | py_args = py_args[:-1]
53 | py_args = py_args.strip()
54 | if py_args:
55 | py_args = ', ' + py_args
56 | new_args = ', '.join([parm.split(' ')[-1] if parm.split(' ')[-1] not in replaced else "py" + parm.split(' ')[-1] for parm in fun.params.split(', ')])
57 | returned_args = ', '.join(["*py" + parm.name for parm in fun.pointers])
58 | capture = ""
59 | if fun.return_type != "void":
60 | capture = '{} ret = '.format(fun.return_type)
61 | if returned_args:
62 | returned_args = 'ret, ' + returned_args
63 | else:
64 | returned_args = 'ret'
65 |
66 | return(reference_template.format(class_name = clas, py_args = py_args, init = init, capture = capture, function_name = fun.name, new_args = new_args, returned_args = returned_args))
67 |
68 | class Function_def():
69 | def __init__(self, return_type, name, params, pointers, static = False):
70 | self.return_type = return_type
71 | self.name = name
72 | self.params = params
73 | self.pointers = pointers
74 | self.static = static
75 |
76 | class Pointer():
77 | def __init__(self, pre, t, name):
78 | self.pre = pre
79 | self.type = t
80 | self.name = name
81 |
82 | classes = {}
83 | for filename in os.listdir(dir_path_h):
84 | if '_classes.h' in filename or ('_structs.h' in filename and not '_f_' in filename):
85 | objs = {}
86 | with open(dir_path_h + filename) as f:
87 | c = None
88 | fields = []
89 | variables = []
90 | functions = []
91 | static_functions = []
92 | reference_functions = {}
93 | for line in f.readlines():
94 | if line.startswith('class ') or line.startswith('struct '):
95 | c = line.split(' ')[1].strip()
96 | objs[c] = {}
97 | if ':' in line:
98 | objs[c]['parent'] = line.split(' ')[-1].strip()
99 | if '[' in line.split('//')[0]:
100 | continue
101 | if ' : 1;' in line:
102 | field = line.split(' : 1;')[0].split(' ')[-1]
103 | fields.append(field)
104 | elif line.startswith('\t') and not line.startswith('\t\t') and line.strip() != '};':
105 | line = line.split('//')[0].strip()
106 | if line.endswith(';') and line[-2] != ')' and not 'pClassPointer' in line:
107 | needs_reference = '*' in line or 'class ' in line or 'struct ' in line or 'TArray' in line
108 | name = line.split(' ')[-1].split('\t')[-1][:-1].strip()
109 | variables.append((name, needs_reference))
110 | elif line.endswith(');') and not line.startswith('virtual') and not line.startswith('template') and not (line.startswith('static') and 'StaticClass' in line):
111 | static = line.startswith('static')
112 | if static:
113 | line = line[len('static '):]
114 | if not '*' in line:
115 | needs_reference = line.startswith('TArray') or line.startswith('class') or line.startswith('struct')
116 | name = line.split('(')[0].split(' ')[-1].split('\t')[-1]
117 | functions.append((name, needs_reference, static))
118 | else:
119 | start, end = line.split('(')
120 | function_name = start.split(' ')[-1]
121 | return_type = ' '.join(start.split(' ')[:-1]).strip()
122 | params = line.split('(')[-1].split(')')[0]
123 | pointers = []
124 | for param in params.split(', '):
125 | if 'TArray' in param:
126 | continue
127 | if (param.startswith('struct ') or param.startswith('class ')) and '**' in param:
128 | s = param.split(' ')
129 | pointers.append(Pointer(s[0], s[1], s[2]))
130 | elif not param.startswith('struct ') and not param.startswith('class ') and '*' in param:
131 | s = param.split(' ')
132 | pointers.append(Pointer(None, ' '.join(s[:-1]), s[-1]))
133 | if pointers:
134 | reference_functions[function_name] = Function_def(return_type, function_name, params, pointers, static)
135 | else:
136 | needs_reference = line.startswith('TArray') or line.startswith('class') or line.startswith('struct')
137 | name = line.split('(')[0].split(' ')[-1].split('\t')[-1]
138 | functions.append((name, needs_reference, static))
139 | elif line.startswith('static') and 'StaticClass' in line:
140 | objs[c]['static'] = True
141 | if line == '};\n' or line.endswith(' {};\n'):
142 | objs[c]['bitfields'] = fields
143 | objs[c]['variables'] = variables
144 | objs[c]['functions'] = functions
145 | objs[c]['reference_functions'] = reference_functions
146 | c = None
147 | fields = []
148 | variables = []
149 | functions = []
150 | reference_functions = {}
151 | classes[filename.split('.')[0]] = objs
152 |
153 |
154 | # print('void Export_pystes{}(py::module &m);'.format(filename))
155 | for module in classes.keys():
156 | objs = classes[module]
157 | replacements = {}
158 | for ck in objs.keys():
159 | c = objs[ck]
160 | name = ck
161 | if 'parent' in c.keys():
162 | name = '{}, {}'.format(name, c['parent'])
163 | if c['functions']:
164 | for function in c['functions']:
165 | policy = policy_def if function[1] else ''
166 | if function[2] and policy:
167 | replacements['&{}::{})'.format(ck, function[0])] = (function_def.format(static_def='' if not function[2] else '_static', class_name=ck, func_name=function[0], policy=policy))
168 | if c['reference_functions']:
169 | for reference_function in c['reference_functions'].keys():
170 | if c['reference_functions'][reference_function].static:
171 | replacements['&{}::{})'.format(ck, c['reference_functions'][reference_function].name)] = (reference_def.format(static_def='' if not c['reference_functions'][reference_function].static else '_static', func_name=reference_function, l=generate_lambda(ck, c['reference_functions'][reference_function])))
172 | # print('&{}::{})'.format(ck, c['reference_functions'][reference_function].name), replacements['&{}::{})'.format(ck, c['reference_functions'][reference_function].name)])
173 | with open(dir_path_python + module + '.cpp', 'r') as f:
174 | lines = f.readlines()
175 | with open(dir_path_python + module + '.cpp', 'w') as f:
176 | for line in lines:
177 | for key in replacements.keys():
178 | if key in line:
179 | line = replacements[key]
180 | f.write(line)
--------------------------------------------------------------------------------
/scripts/setup_polymorphism.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | files = ['Core_classes.h',
5 | 'Engine_classes.h',
6 | 'GameFramework_classes.h',
7 | 'GFxUI_classes.h',
8 | 'GearboxFramework_classes.h',
9 | 'WillowGame_classes.h',
10 | 'AkAudio_classes.h',
11 | 'IpDrv_classes.h',
12 | 'WinDrv_classes.h',
13 | 'XAudio2_classes.h',
14 | 'OnlineSubsystemSteamworks_classes.h']
15 | import os
16 |
17 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
18 |
19 | template = 'map["{0}"] = &typeid({0});'
20 |
21 | pointers = {}
22 | for clas in files:
23 | with open(dir_path_python + clas) as f:
24 | class_name = None
25 | for line in f.readlines():
26 | if line.startswith('class '):
27 | class_name = line.split(" ")[1].strip()
28 | if line.startswith(' pClassPointer = '):
29 | num = line.split('[')[-1].split(']')[0]
30 | pointers[int(num)] = class_name
31 |
32 | for num in sorted(pointers.keys()):
33 | print(template.format(pointers[num]))
--------------------------------------------------------------------------------
/scripts/sort_relationships.py:
--------------------------------------------------------------------------------
1 | from os import listdir
2 |
3 | sdk_dir = 'C:\\Users\\abahb\\source\\repos\\BL2-SDK\\bl2-sdk\\pydefs\\'
4 |
5 | relationships = {}
6 |
7 | for filename in listdir(sdk_dir):
8 | with open(sdk_dir + filename) as f:
9 | for line in f.readlines():
10 | if 'py::class_' in line:
11 | obj_def = line.split('<')[1].split('>')[0].strip()
12 | objs = obj_def.split(',')
13 | if len(objs) > 1:
14 | child = objs[0].strip()
15 | parent = objs[1].strip()
16 | if parent not in relationships.keys():
17 | relationships[parent] = []
18 | relationships[parent].append(child)
19 | if objs[0] not in relationships.keys():
20 | relationships[objs[0]] = []
21 |
22 | to_check = ['UObject']
23 | while to_check:
24 | current = to_check.pop()
25 | to_check += relationships[current]
26 | del relationships[current]
27 | print(" Export_pystes_{}(m);".format(current))
28 | for key in relationships.keys():
29 | print(" Export_pystes_{}(m);".format(key))
30 |
--------------------------------------------------------------------------------
/scripts/split_sdk.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | top = """#include "stdafx.h"
5 | // Using =======================================================================
6 | namespace py = pybind11;
7 |
8 | // Module ======================================================================
9 | void Export_pystes_{}(py::module &m)
10 | {{
11 | """
12 |
13 | bottom = """
14 | }"""
15 |
16 | files = ['gamedefines.cpp']
17 |
18 | dir_path_python = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/pydefs/'
19 |
20 | dir_path_h = 'C:/Users/abahb/source/repos/BL2-SDK/bl2-sdk/'
21 |
22 | structs = {}
23 | classes = {}
24 | for filename in os.listdir(dir_path_h):
25 | if '_classes.h' in filename or '_structs.h' in filename:
26 | objs = []
27 | with open(dir_path_h + filename) as f:
28 | for line in f.readlines():
29 | if line.startswith('class ') or line.startswith('struct '):
30 | name = line.split(' ')[1].strip()
31 | objs.append(name)
32 | if '_classes.h' in filename:
33 | classes[filename.split('.')[0]] = objs
34 | else:
35 | structs[filename.split('.')[0]] = objs
36 |
37 |
38 |
39 |
40 | for s in structs.keys():
41 | with open(dir_path_python + s + '.cpp', 'w') as f:
42 | f.write(top.format(s))
43 | for sf in structs[s]:
44 | try:
45 | with open(dir_path_python + '_Structs_{}.cpp'.format(sf)) as fs:
46 | lines = fs.readlines()
47 | write = False
48 | for line in lines:
49 | if 'class_' in line:
50 | write = True
51 | if write:
52 | f.write(line)
53 | if ';' in line:
54 | write = False
55 | except Exception as e:
56 | print(e)
57 | f.write(bottom)
58 |
59 | for s in classes.keys():
60 | with open(dir_path_python + s + '.cpp', 'w') as f:
61 | f.write(top.format(s))
62 | for sf in classes[s]:
63 | try:
64 | with open(dir_path_python + '_Classes_{}.cpp'.format(sf)) as fs:
65 | lines = fs.readlines()
66 | write = False
67 | for line in lines:
68 | if 'class_' in line:
69 | write = True
70 | if write:
71 | f.write(line)
72 | if line.strip() == ';':
73 | write = False
74 | except Exception as e:
75 | print(e)
76 | f.write(bottom)
77 |
78 |
79 | # for filename in new_files:
80 | # print('void Export_pystes{}(py::module &m);'.format(filename))
81 | # with open(dir_path_python + filename + '.cpp', 'w') as f:
82 | # f.write(top.format(filename))
83 | # for line in new_files[filename]:
84 | # f.write(line)
85 | # f.write(bottom)
--------------------------------------------------------------------------------
/scripts/tarray_finder.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | dir_path = os.path.dirname(os.path.realpath(__file__))
5 | used = set()
6 | defined = set()
7 |
8 | used_regex = re.compile(r'.* TArray_(\w+).*')
9 | defined_regex = re.compile(r'.*struct TArray_(\w+) {')
10 |
11 | for root, dirs, files in os.walk(dir_path):
12 | for file in files:
13 | if file.endswith(".lua"):
14 | with open(root + '\\' + file) as f:
15 | for line in f.readlines():
16 | found = used_regex.match(line)
17 | if found:
18 | used.add(found.groups()[0])
19 |
20 | found = defined_regex.match(line)
21 | if found:
22 | defined.add(found.groups()[0])
23 |
24 |
25 | template = \
26 | """struct {0};
27 | struct TArray_{0} {{
28 | struct {0}* Data;
29 | int Count;
30 | int Max;
31 | }};
32 | """
33 |
34 | for tarray in (used - defined):
35 | print(template.format(tarray))
36 |
37 | for tarray in (used - defined):
38 | print('table.insert(g_TArrayTypes, "{}")'.format(tarray))
39 |
--------------------------------------------------------------------------------
/src/AntiDebug.cpp:
--------------------------------------------------------------------------------
1 | #include "stdafx.h"
2 | #include "CSimpleDetour.h"
3 | #include "Logging.h"
4 | #include "Exceptions.h"
5 | #include "Util.h"
6 |
7 | #include
8 | #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
9 | #define STATUS_PORT_NOT_SET ((NTSTATUS)0xC0000353L)
10 |
11 | namespace UnrealSDK
12 | {
13 | typedef NTSTATUS (WINAPI* tNtSIT)(HANDLE, THREAD_INFORMATION_CLASS, PVOID, ULONG);
14 | tNtSIT pNtSetInformationThread = nullptr;
15 |
16 | NTSTATUS NTAPI hkNtSetInformationThread(
17 | __in HANDLE ThreadHandle,
18 | __in THREAD_INFORMATION_CLASS ThreadInformationClass,
19 | __in PVOID ThreadInformation,
20 | __in ULONG ThreadInformationLength)
21 | {
22 | if (ThreadInformationClass == 17) // ThreadHideFromDebugger
23 | {
24 | Logging::Log("[AntiDebug] NtSetInformationThread called with ThreadHideFromDebugger\n");
25 | return STATUS_SUCCESS;
26 | }
27 |
28 | return pNtSetInformationThread(ThreadHandle, ThreadInformationClass, ThreadInformation,
29 | ThreadInformationLength);
30 | }
31 |
32 | typedef NTSTATUS (WINAPI* tNtQIP)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
33 | tNtQIP pNtQueryInformationProcess = nullptr;
34 |
35 | NTSTATUS WINAPI hkNtQueryInformationProcess(
36 | __in HANDLE ProcessHandle,
37 | __in PROCESSINFOCLASS ProcessInformationClass,
38 | __out PVOID ProcessInformation,
39 | __in ULONG ProcessInformationLength,
40 | __out_opt PULONG ReturnLength)
41 | {
42 | if (ProcessInformationClass == 30) // ProcessDebugObjectHandle
43 | {
44 | return STATUS_PORT_NOT_SET;
45 | }
46 |
47 | return pNtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation,
48 | ProcessInformationLength, ReturnLength);
49 | }
50 |
51 | void HookAntiDebug()
52 | {
53 | HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
54 | if (!hNtdll)
55 | {
56 | throw FatalSDKException(7000, Util::Format("GetModuleHandle failed for ntdll.dll (Error = 0x%X)",
57 | GetLastError()));
58 | }
59 |
60 | pNtSetInformationThread = (tNtSIT)GetProcAddress(hNtdll, "NtSetInformationThread");
61 | if (!pNtSetInformationThread)
62 | {
63 | throw FatalSDKException(
64 | 7001, Util::Format("GetProcAddress failed for NtSetInformationThread (Error = 0x%X)", GetLastError()));
65 | }
66 |
67 | SETUP_SIMPLE_DETOUR(detNtSIT, pNtSetInformationThread, hkNtSetInformationThread);
68 | detNtSIT.Attach();
69 | Logging::Log("[AntiDebug] Hook added for NtSetInformationThread\n");
70 |
71 | pNtQueryInformationProcess = (tNtQIP)GetProcAddress(hNtdll, "NtQueryInformationProcess");
72 | if (!pNtQueryInformationProcess)
73 | {
74 | throw FatalSDKException(
75 | 7002, Util::Format("GetProcAddress failed for NtQueryInformationProcess (Error = 0x%X)",
76 | GetLastError()));
77 | }
78 |
79 | SETUP_SIMPLE_DETOUR(detNtQIP, pNtQueryInformationProcess, hkNtQueryInformationProcess);
80 | detNtQIP.Attach();
81 | Logging::Log("[AntiDebug] Hook added for NtQueryInformationProcess\n");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/CHookManager.cpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "stdafx.h"
3 | #include "CHookManager.h"
4 |
5 | void CHookManager::Register(const std::string& FuncName, const std::string& HookName,
6 | const std::function& FuncHook)
7 | {
8 | char funcNameChar[255];
9 | strcpy(funcNameChar, FuncName.c_str());
10 |
11 | // Create pair to insert
12 | tHookPair hookPair = std::make_pair(HookName, FuncHook);
13 |
14 | auto iHooks = hooks.find(FuncName);
15 | if (iHooks != hooks.end())
16 | iHooks->second.insert(hookPair);
17 | else
18 | {
19 | tHookMap newMap;
20 | newMap.insert(hookPair);
21 | hooks.emplace(FuncName, newMap);
22 | }
23 |
24 | Logging::LogD("[HookManager] (%s) Hook \"%s\" added as hook for \"%s\"\n", this->debugName.c_str(),
25 | hookPair.first.c_str(), FuncName.c_str());
26 | }
27 |
28 | bool CHookManager::Remove(const std::string& FuncName, const std::string& HookName)
29 | {
30 | auto iHooks = hooks.find(FuncName);
31 | if (iHooks == hooks.end() || iHooks->second.find(HookName) == iHooks->second.end())
32 | {
33 | Logging::LogD("[HookManager] (%s) ERROR: Failed to remove hook \"%s\" for \"%s\"\n", this->debugName.c_str(),
34 | HookName.c_str(), FuncName.c_str());
35 | return false;
36 | }
37 |
38 | iHooks->second.erase(HookName);
39 | return true;
40 | }
41 |
42 | bool CHookManager::ProcessHooks(const std::string& FuncName, const UObject* Caller, const UFunction* Func,
43 | const FStruct* Params)
44 | {
45 | auto iHooks = hooks.find(FuncName);
46 |
47 | if (iHooks != hooks.end())
48 | {
49 | tHookMap matchedHooks = iHooks->second;
50 |
51 | for (auto& hook : matchedHooks)
52 | if (!hook.second(const_cast(Caller), const_cast(Func), const_cast(Params)))
53 | return false;
54 | }
55 |
56 | iHooks = hooks.find(const_cast(Caller)->GetObjectName() + "." + const_cast(Func)->GetName());
57 |
58 | if (iHooks != hooks.end())
59 | {
60 | tHookMap matchedHooks = iHooks->second;
61 |
62 | for (auto& hook : matchedHooks)
63 | if (!hook.second(const_cast(Caller), const_cast(Func), const_cast(Params)))
64 | return false;
65 | }
66 | return true;
67 | }
68 |
69 |
70 | bool CHookManager::HasHook(UObject* Caller, UFunction* Func)
71 | {
72 | auto iHooks = hooks.find(Func->GetObjectName());
73 | if (iHooks != hooks.end() && !iHooks->second.empty())
74 | return true;
75 | iHooks = hooks.find(Caller->GetObjectName() + "." + Func->GetName());
76 | return (iHooks != hooks.end() && !iHooks->second.empty());
77 | }
78 |
79 | bool CHookManager::ProcessHooks(UObject* Caller, FFrame& Stack, void* const Result, UFunction* Function)
80 | {
81 | const auto iHooks = hooks.find(Function->GetObjectName());
82 |
83 | // Even though we check in the next function, check here to avoid messing with the stack when we don't need to
84 | if (iHooks != hooks.end() || hooks.find(Caller->GetObjectName() + "." + Function->GetName()) != hooks.end())
85 | {
86 | UProperty* ReturnParm;
87 | char* Frame = static_cast(calloc(1, Function->ParamsSize));
88 | for (auto* Property = static_cast(Function->Children); Stack.Code[0] != 0x16; Property = static_cast(Property->
89 | Next))
90 | {
91 | const bool bIsReturnParam = ((Property->PropertyFlags & 0x400) != 0);
92 | if (bIsReturnParam)
93 | {
94 | ReturnParm = Property;
95 | continue;
96 | }
97 | UnrealSDK::pFrameStep(&Stack, Stack.Object, Frame + Property->Offset_Internal);
98 | }
99 | auto FrameStruct = FStruct{ Function, (void*) Frame };
100 | const bool ret = ProcessHooks(Function->GetObjectName(), Caller, Function, &FrameStruct);
101 | //if (!ret) {
102 | // if (ReturnParm)
103 | // {
104 | // memcpy(Result, Frame + ReturnParm->Offset_Internal, ReturnParm->ElementSize);
105 | // Stack.Outparams = (FOutParmRec *)malloc(sizeof(FOutParmRec));
106 | // Stack.Outparams->Property = ReturnParm;
107 | // Stack.Outparams->PropAddr = (unsigned char *)Result;
108 | // }
109 | // FOutParmRec** LastOut = &Stack.Outparams;
110 | // for (UProperty* Property = (UProperty *)Function->Children; Property; Property = (UProperty*)Property->Next) {
111 | // const bool bIsReturnParam = ((Property->PropertyFlags & 0x400) != 0);
112 | // if (bIsReturnParam)
113 | // continue;
114 | // if (Property->PropertyFlags & 0x100) {
115 | // FOutParmRec *NewOutParm = (FOutParmRec *)malloc(sizeof(FOutParmRec));
116 | // NewOutParm->Property = Property;
117 | // NewOutParm->PropAddr = (unsigned char *)malloc(sizeof(Property->ElementSize));
118 | // memcpy(NewOutParm->PropAddr, Frame + Property->Offset_Internal, Property->ElementSize);
119 | // if (*LastOut) {
120 | // (*LastOut)->NextOutParm = NewOutParm;
121 | // LastOut = &(*LastOut)->NextOutParm;
122 | // }
123 | // else {
124 | // *LastOut = NewOutParm;
125 | // }
126 | // }
127 | // }
128 | //}
129 | //LogOutParams(Stack);
130 | memset(Frame, 0, Function->ParamsSize);
131 | free(Frame);
132 | return ret;
133 | }
134 | return true;
135 | }
136 |
--------------------------------------------------------------------------------
/src/CPythonInterface.cpp:
--------------------------------------------------------------------------------
1 | #include "stdafx.h"
2 | #include "CPythonInterface.h"
3 | #include "Logging.h"
4 | #include "Settings.h"
5 | #include "Util.h"
6 | #include "UnrealSDK.h"
7 | #include
8 | #include
9 | #include
10 |
11 | bool VerifyPythonFunction(py::object funcHook) {
12 | PyObject* obj = funcHook.ptr();
13 | if (!obj) {
14 | Logging::LogF("[Error] Object passed to hook is null\n");
15 | return false;
16 | }
17 | if (!PyFunction_Check(obj)) {
18 | Logging::LogF("[Error] Object passed to hook is not a function\n");
19 | return false;
20 | }
21 | PyCodeObject* code = (PyCodeObject*)PyFunction_GetCode(obj);
22 | if (!code) {
23 | Logging::LogF("[Error] Unable to retrive code from object passed to hook\n");
24 | return false;
25 | }
26 | if (code->co_argcount != 3) {
27 | Logging::LogF("[Error] Function passed to hook must have exactly 3 arguments\n");
28 | return false;
29 | }
30 | return true;
31 | }
32 |
33 | void RegisterHook(const std::string& funcName, const std::string& hookName, py::object funcHook) {
34 | if (!VerifyPythonFunction(funcHook)) {
35 | return;
36 | }
37 | UnrealSDK::RegisterHook(funcName, hookName, [funcHook](UObject* caller, UFunction* function, FStruct* params) {
38 | try {
39 | py::object py_caller = cast(caller, py::return_value_policy::reference);
40 | py::object py_function = cast(function, py::return_value_policy::reference);
41 | py::object py_params = cast(params, py::return_value_policy::reference);
42 | py::object ret = funcHook(py_caller, py_function, py_params);
43 | return ret.cast();
44 | } catch (std::exception e) {
45 | Logging::LogF(e.what());
46 | }
47 | return true;
48 | });
49 | }
50 |
51 | namespace py = pybind11;
52 |
53 | PYBIND11_EMBEDDED_MODULE(unrealsdk, m)
54 | {
55 | Export_pystes_gamedefines(m);
56 | Export_pystes_Core_structs(m);
57 | Export_pystes_Core_classes(m);
58 | Export_pystes_TArray(m);
59 |
60 | m.def("GetVersion", []() { return py::make_tuple(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); });
61 | m.def("Log", [](py::args args) {
62 | std::ostringstream msg;
63 | for (py::size_t i = 0; i < args.size(); i++) {
64 | if (i > 0) {
65 | msg << " ";
66 | }
67 | msg << py::str(args[i]);
68 | }
69 | Logging::LogPy(msg.str());
70 | });
71 | m.def("LoadPackage", &UnrealSDK::LoadPackage, py::arg("filename"), py::arg("flags") = 0, py::arg("force") = false);
72 | m.def("KeepAlive", &UnrealSDK::KeepAlive);
73 | m.def("GetPackageObject", &UObject::GetPackageObject, py::return_value_policy::reference);
74 | m.def("FindAll", &UObject::FindAll, py::arg("InStr"), py::arg("IncludeSubclasses") = false,
75 | py::return_value_policy::reference);
76 | m.def("FindClass", &UObject::FindClass, py::arg("ClassName"), py::arg("Lookup") = false,
77 | py::return_value_policy::reference);
78 | m.def("FindObject", [](char* ClassName, char* ObjectFullName) { return UObject::Find(ClassName, ObjectFullName); },
79 | py::return_value_policy::reference);
80 | m.def("FindObject", [](UClass* Class, char* ObjectFullName) { return UObject::Find(Class, ObjectFullName); },
81 | py::return_value_policy::reference);
82 | m.def("LoadObject", [](char* ClassName, char* ObjectFullName) { return UObject::Load(ClassName, ObjectFullName); },
83 | py::return_value_policy::reference);
84 | m.def("LoadObject", [](UClass* Class, char* ObjectFullName) { return UObject::Load(Class, ObjectFullName); },
85 | py::return_value_policy::reference);
86 | //m.def("LoadTexture", &UnrealSDK::LoadTexture, py::return_value_policy::reference);
87 | m.def("SetLoggingLevel", &Logging::SetLoggingLevel);
88 | m.def("ConstructObject", &UnrealSDK::ConstructObject, "Construct Objects", py::arg("Class"),
89 | py::arg("Outer") = UnrealSDK::GetEngine()->Outer, py::arg("Name") = FName(), py::arg("SetFlags") = 0x1,
90 | py::arg("InternalSetFlags") = 0x00, py::arg("Template") = (UObject*)nullptr,
91 | py::arg("Error") = (FOutputDevice *)nullptr, py::arg("InstanceGraph") = (void*)nullptr,
92 | py::arg("bAssumeTemplateIsArchetype") = (int)0, py::return_value_policy::reference);
93 | m.def("ConstructObject", [](char* ClassName, UObject* Outer, FName Name, unsigned int SetFlags,
94 | unsigned int InternalSetFlags, UObject* Template, FOutputDevice* Error,
95 | void* InstanceGraph, int bAssumeTemplateIsArchetype)
96 | {
97 | return UnrealSDK::ConstructObject(UObject::FindClass(ClassName), Outer, Name, SetFlags, InternalSetFlags,
98 | Template,
99 | Error, InstanceGraph, bAssumeTemplateIsArchetype);
100 | }, "Construct Objects", py::arg("Class"), py::arg("Outer") = UnrealSDK::GetEngine()->Outer,
101 | py::arg("Name") = FName(),
102 | py::arg("SetFlags") = 0x1, py::arg("InternalSetFlags") = 0x00, py::arg("Template") = (UObject*)nullptr,
103 | py::arg("Error") = (FOutputDevice *)nullptr, py::arg("InstanceGraph") = (void*)nullptr,
104 | py::arg("bAssumeTemplateIsArchetype") = (int)0, py::return_value_policy::reference);
105 | m.def("RegisterHook", &RegisterHook);
106 | m.def("GetEngine", &UnrealSDK::GetEngine, py::return_value_policy::reference);
107 | m.def("RemoveHook", [](const std::string& funcName, const std::string& hookName)
108 | {
109 | UnrealSDK::RemoveHook(funcName, hookName);
110 | });
111 | m.def("RunHook", [](const std::string& funcName, const std::string& hookName, py::object funcHook)
112 | {
113 | UnrealSDK::RemoveHook(funcName, hookName);
114 | RegisterHook(funcName, hookName, funcHook);
115 | });
116 | m.def("DoInjectedCallNext", &UnrealSDK::DoInjectedCallNext);
117 | m.def("LogAllCalls", &UnrealSDK::LogAllCalls);
118 | m.def("CallPostEdit", [](bool NewValue) { UnrealSDK::gCallPostEdit = NewValue; });
119 | }
120 |
121 | void AddToConsoleLog(UConsole* console, FString input)
122 | {
123 | int prev = (console->HistoryTop - 1) % 16;
124 | if (!console->History[prev].Data || strcmp(input.AsString(), console->History[prev].AsString()))
125 | {
126 | console->PurgeCommandFromHistory(input);
127 | console->History[console->HistoryTop] = input;
128 | console->HistoryTop = (console->HistoryTop + 1) % 16;
129 | if ((console->HistoryBot == -1) || console->HistoryBot == console->HistoryTop)
130 | console->HistoryBot = (console->HistoryBot + 1) % 16;
131 | }
132 | console->HistoryCur = console->HistoryTop;
133 | console->SaveConfig();
134 | }
135 |
136 | bool CheckPythonCommand(UObject* caller, UFunction* function, FStruct* params)
137 | {
138 | FString* command = ((FHelper*)params->base)->GetStrProperty(
139 | (UProperty*)params->structType->FindChildByName(FName("command")),
140 | 0
141 | );
142 | char* input = command->AsString();
143 | if (strncmp("py ", input, 3) == 0)
144 | {
145 | AddToConsoleLog((UConsole *)caller, *command);
146 | Logging::LogF("\n>>> %s <<<\n", input);
147 | UnrealSDK::Python->DoString(input + 3);
148 | }
149 | else if (strncmp("pyexec ", input, 7) == 0)
150 | {
151 | AddToConsoleLog((UConsole *)caller, *command);
152 | Logging::LogF("\n>>> %s <<<\n", input);
153 | UnrealSDK::Python->DoFile(input + 7);
154 | }
155 | else
156 | ((UConsole *)caller)->ConsoleCommand(*command);
157 | return false;
158 | }
159 |
160 | CPythonInterface::CPythonInterface()
161 | {
162 | m_modulesInitialized = false;
163 | InitializeState();
164 |
165 | UnrealSDK::RegisterHook("Engine.Console.ShippingConsoleCommand", "CheckPythonCommand", &CheckPythonCommand);
166 | }
167 |
168 | CPythonInterface::~CPythonInterface()
169 | {
170 | if (m_modulesInitialized)
171 | {
172 | CallShutdownFuncs();
173 | }
174 |
175 | CleanupState();
176 | }
177 |
178 | void CPythonInterface::InitializeState()
179 | {
180 | try
181 | {
182 | py::initialize_interpreter();
183 | m_mainNamespace = py::module::import("__main__");
184 | }
185 | catch (std::exception e)
186 | {
187 | Logging::LogF("%s", e.what());
188 | }
189 | }
190 |
191 | void CPythonInterface::CleanupState()
192 | {
193 | py::finalize_interpreter();
194 | }
195 |
196 | std::vector getSubdirs(const std::wstring& path)
197 | {
198 | WIN32_FIND_DATA findfiledata;
199 | HANDLE hFind = INVALID_HANDLE_VALUE;
200 |
201 | wchar_t fullpath[MAX_PATH];
202 | GetFullPathName(path.c_str(), MAX_PATH, fullpath, nullptr);
203 | std::wstring fp(fullpath);
204 |
205 | std::vector output{};
206 | hFind = FindFirstFile((fp + L"\\*").c_str(), &findfiledata);
207 | if (hFind != INVALID_HANDLE_VALUE)
208 | {
209 | do
210 | {
211 | if ((findfiledata.dwFileAttributes | FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY
212 | && (findfiledata.cFileName[0] != '.') && wcscmp(L"__pycache__", findfiledata.cFileName))
213 | {
214 | output.push_back(findfiledata.cFileName);
215 | }
216 | }
217 | while (FindNextFile(hFind, &findfiledata) != 0);
218 | }
219 | return output;
220 | }
221 |
222 | PythonStatus CPythonInterface::InitializeModules()
223 | {
224 | m_modulesInitialized = false;
225 | SetPaths();
226 | Logging::LogPy(Util::Format("[Python] Version: %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH));
227 |
228 | try
229 | {
230 | py::module::import("unrealsdk");
231 | py::module::import("Mods");
232 |
233 | // Also make these accessable on console
234 | DoString("import unrealsdk, Mods");
235 | }
236 | catch (std::exception e)
237 | {
238 | Logging::LogF(e.what());
239 | Logging::Log("[Python] Failed to initialize Python modules\n");
240 | return PYTHON_MODULE_ERROR;
241 | }
242 |
243 | Logging::Log("[Python] Python initialized (" PYTHON_ABI_STRING ")\n");
244 | m_modulesInitialized = true;
245 | return PYTHON_OK;
246 | }
247 |
248 | void CPythonInterface::SetPaths()
249 | {
250 | m_PythonPath = Util::Narrow(Settings::GetPythonFile(L""));
251 | }
252 |
253 | int CPythonInterface::DoFile(const char* filename)
254 | {
255 | return DoFileAbsolute(Util::Format("%s\\%s", m_PythonPath.c_str(), filename).c_str());
256 | }
257 |
258 | int CPythonInterface::DoString(const char* command)
259 | {
260 | try
261 | {
262 | py::exec(command);
263 | }
264 | catch (std::exception e)
265 | {
266 | Logging::LogF("%s", e.what());
267 | }
268 | return 0;
269 | }
270 |
271 | int CPythonInterface::DoFileAbsolute(const char* path)
272 | {
273 | try
274 | {
275 | py::eval_file(path);
276 | }
277 | catch (std::exception e)
278 | {
279 | Logging::LogF("%s", e.what());
280 | }
281 | return 0;
282 | }
283 |
284 | void CPythonInterface::CallShutdownFuncs()
285 | {
286 | }
287 |
288 | py::object CPythonInterface::GetPythonNamespace()
289 | {
290 | return m_mainNamespace;
291 | }
292 |
--------------------------------------------------------------------------------
/src/CSigScan.cpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "stdafx.h"
3 | #include "MemorySignature.h"
4 | #include "CSigScan.h"
5 | #include "Util.h"
6 | #include "Exceptions.h"
7 | // Based off CSigScan from AlliedModders
8 |
9 | CSigScan::CSigScan(const wchar_t* moduleName)
10 | {
11 | m_moduleHandle = GetModuleHandle(moduleName);
12 | if (m_moduleHandle == nullptr)
13 | {
14 | throw FatalSDKException(3000, Util::Format("Sigscan failed (GetModuleHandle returned NULL, Error = %d)",
15 | GetLastError()));
16 | }
17 |
18 | void* pAddr = m_moduleHandle;
19 |
20 | MEMORY_BASIC_INFORMATION mem;
21 |
22 | if (!VirtualQuery(pAddr, &mem, sizeof(mem)))
23 | {
24 | throw FatalSDKException(3001, Util::Format("Sigscan failed (VirtualQuery returned NULL, Error = %d)",
25 | GetLastError()));
26 | }
27 |
28 | m_pModuleBase = (char*)mem.AllocationBase;
29 | if (m_pModuleBase == nullptr)
30 | {
31 | throw FatalSDKException(3002, "Sigscan failed (mem.AllocationBase was NULL)");
32 | }
33 |
34 | IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)mem.AllocationBase;
35 | IMAGE_NT_HEADERS* pe = (IMAGE_NT_HEADERS*)((unsigned long)dos + (unsigned long)dos->e_lfanew);
36 |
37 | if (pe->Signature != IMAGE_NT_SIGNATURE)
38 | {
39 | throw FatalSDKException(3003, "Sigscan failed (pe points to a bad location)");
40 | }
41 |
42 | m_moduleLen = (size_t)pe->OptionalHeader.SizeOfImage;
43 | }
44 |
45 | void* CSigScan::Scan(const MemorySignature& sigStruct)
46 | {
47 | return Scan(sigStruct.Sig, sigStruct.Mask, sigStruct.Length);
48 | }
49 |
50 | void* CSigScan::Scan(const char* sig, const char* mask)
51 | {
52 | int sigLength = strlen(mask);
53 | return Scan(sig, mask, sigLength);
54 | }
55 |
56 | void* CSigScan::Scan(const char* sig, const char* mask, int sigLength)
57 | {
58 | char* pData = m_pModuleBase;
59 | char* pEnd = m_pModuleBase + m_moduleLen;
60 |
61 | while (pData < pEnd)
62 | {
63 | int i;
64 | for (i = 0; i < sigLength; i++)
65 | {
66 | if (mask[i] != '?' && sig[i] != pData[i])
67 | break;
68 | }
69 |
70 | // The for loop finished on its own accord
71 | if (i == sigLength)
72 | {
73 | return (void*)pData;
74 | }
75 |
76 | pData++;
77 | }
78 | Logging::LogF("Sigscan failed (Signature not found, Mask = %s)", Util::StringToHex(sig, sigLength).c_str());
79 | return nullptr;
80 | }
81 |
--------------------------------------------------------------------------------
/src/CSimpleDetour.cpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "stdafx.h"
3 | #include "CSimpleDetour.h"
4 | #include "Util.h"
5 | #include "Exceptions.h"
6 |
7 | CSimpleDetour::CSimpleDetour(void** old, void* replacement)
8 | {
9 | m_fnOld = old;
10 | m_fnReplacement = replacement;
11 | }
12 |
13 | void CSimpleDetour::Attach()
14 | {
15 | DetourTransactionBegin();
16 | //DetourUpdateThread(GetCurrentThread());
17 |
18 | DetourAttach(m_fnOld, m_fnReplacement);
19 |
20 | const LONG result = DetourTransactionCommit();
21 | if (result != NO_ERROR)
22 | {
23 | throw FatalSDKException(4000, Util::Format("Failed to attach detour (Old = 0x%p, Hook = 0x%p, Result = 0x%X)",
24 | m_fnOld, m_fnReplacement, result));
25 | }
26 |
27 | m_bAttached = true;
28 | }
29 |
30 | void CSimpleDetour::Detach()
31 | {
32 | if (!m_bAttached)
33 | return;
34 |
35 | DetourTransactionBegin();
36 | DetourUpdateThread(GetCurrentThread());
37 |
38 | DetourDetach(m_fnOld, m_fnReplacement);
39 |
40 | LONG result = DetourTransactionCommit();
41 | if (result != NO_ERROR)
42 | {
43 | throw FatalSDKException(4001, Util::Format("Failed to detach detour (Old = 0x%p, Hook = 0x%p, Result = 0x%X)",
44 | m_fnOld, m_fnReplacement, result));
45 | }
46 |
47 | m_bAttached = false;
48 | }
49 |
--------------------------------------------------------------------------------
/src/Logging.cpp:
--------------------------------------------------------------------------------
1 | #define WIN32_LEAN_AND_MEAN
2 | #include
3 | #include
4 | #include "stdafx.h"
5 | #include "UnrealSdk.h"
6 | #include "Logging.h"
7 | #include "Util.h"
8 | #include "Exceptions.h"
9 |
10 | namespace Logging
11 | {
12 | HANDLE gLogFile = nullptr;
13 | bool gLogToExternalConsole = true;
14 | bool gLogToFile = true;
15 | bool gLogToGameConsole = false;
16 |
17 | void LogToFile(const char* Buff, const int Len)
18 | {
19 | if (gLogFile != INVALID_HANDLE_VALUE)
20 | {
21 | DWORD bytesWritten = 0;
22 | WriteFile(gLogFile, Buff, Len, &bytesWritten, nullptr);
23 | }
24 | }
25 |
26 | void LogWinConsole(const char* Buff, const int Len)
27 | {
28 | const HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
29 | DWORD bytesWritten = 0;
30 | WriteFile(output, Buff, Len, &bytesWritten, nullptr);
31 | }
32 |
33 | void Log(const char* Formatted, int Length)
34 | {
35 | std::string out = Formatted;
36 | if (Formatted[strlen(Formatted) - 1] != '\n')
37 | {
38 | out += "\n";
39 | }
40 | const char* outc = out.c_str();
41 |
42 | if (Length == 0)
43 | Length = strlen(outc);
44 |
45 | if (gLogToExternalConsole)
46 | LogWinConsole(outc, Length);
47 |
48 | if (gLogToFile)
49 | LogToFile(outc, Length);
50 |
51 | if (UnrealSDK::gameConsole != nullptr)
52 | {
53 | // It seems that Unreal will automatically put a newline on the end of a
54 | // console output, but if there's already a \n at the end, then it won't
55 | // add this \n onto the end. So if we're printing just a \n by itself,
56 | // just don't do anything.
57 | if (!(Length == 1 && outc[0] == '\n'))
58 | {
59 | std::wstring wfmt = Util::Widen(outc);
60 | const bool doInjectedNext = UnrealSDK::gInjectedCallNext;
61 | UnrealSDK::DoInjectedCallNext();
62 | UnrealSDK::gameConsole->OutputText(FString(wfmt.c_str()));
63 | if (doInjectedNext)
64 | UnrealSDK::DoInjectedCallNext();
65 | }
66 | }
67 | }
68 |
69 | void LogW(wchar_t* Formatted, const signed int Length)
70 | {
71 | char* output = (char *)calloc(Length + 1, sizeof(char));
72 | wcstombs(output, Formatted, Length);
73 | Log(output, 0);
74 | }
75 |
76 | void LogPy(std::string formatted)
77 | {
78 | Log(formatted.c_str(), 0);
79 | }
80 |
81 | void LogF(const char* fmt, ...)
82 | {
83 | va_list args;
84 | va_start(args, fmt);
85 | std::string formatted = Util::FormatInternal(fmt, args);
86 | va_end(args);
87 |
88 | Log(formatted.c_str(), formatted.length());
89 | }
90 |
91 | enum LogLevel
92 | {
93 | DEBUG,
94 | INFO,
95 | WARNING,
96 | EXCEPTION,
97 | CRITICAL
98 | };
99 |
100 | LogLevel Level = WARNING;
101 |
102 | void LogD(const char* fmt, ...)
103 | {
104 | if (Level == DEBUG)
105 | {
106 | va_list args;
107 | va_start(args, fmt);
108 | std::string formatted = "[DEBUG] " + Util::FormatInternal(fmt, args);
109 | va_end(args);
110 |
111 | Log(formatted.c_str(), formatted.length());
112 | }
113 | }
114 |
115 | void SetLoggingLevel(const char* NewLevel)
116 | {
117 | std::string str = NewLevel;
118 | std::transform(str.begin(), str.end(), str.begin(), toupper);
119 | if (str == "DEBUG")
120 | {
121 | Level = DEBUG;
122 | }
123 | else if (str == "INFO")
124 | {
125 | Level = INFO;
126 | }
127 | else if (str == "WARNING")
128 | {
129 | Level = WARNING;
130 | }
131 | else if (str == "EXCEPTION")
132 | {
133 | Level = EXCEPTION;
134 | }
135 | else if (str == "CRITICAL")
136 | {
137 | Level = CRITICAL;
138 | }
139 | else
140 | {
141 | LogF("Unknown logging level '%s'\n", NewLevel);
142 | }
143 | }
144 |
145 | void InitializeExtern()
146 | {
147 | BOOL result = AllocConsole();
148 | if (result)
149 | {
150 | gLogToExternalConsole = true;
151 | }
152 | }
153 |
154 | // Everything else can fail, but InitializeFile must work.
155 | void InitializeFile(const std::wstring& fileName)
156 | {
157 | gLogFile = CreateFile(fileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS,
158 | FILE_ATTRIBUTE_NORMAL, nullptr);
159 | if (gLogFile == INVALID_HANDLE_VALUE)
160 | {
161 | std::string errMsg = Util::Format("Failed to initialize log file (INVALID_HANDLE_VALUE, LastError = %d)",
162 | GetLastError());
163 | throw FatalSDKException(1000, errMsg);
164 | }
165 |
166 | gLogToFile = true;
167 | }
168 |
169 | void PrintLogHeader()
170 | {
171 | LogF("======== UnrealEngine PythonSDK Loaded (Version %d) ========\n", UnrealSDK::EngineVersion);
172 | }
173 |
174 | void Cleanup()
175 | {
176 | if (gLogFile != INVALID_HANDLE_VALUE)
177 | {
178 | FlushFileBuffers(gLogFile);
179 | CloseHandle(gLogFile);
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/PackageFix.cpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "stdafx.h"
3 | #include "UnrealSDK.h"
4 | #include "PackageFix.h"
5 | #include "gamedefines.h"
6 |
7 | namespace UnrealSDK
8 | {
9 | bool GIsLoadingUDKPackage = false;
10 | DWORD dwTextureFixReturn;
11 |
12 | void WriteJMPHook(unsigned char* hookLocation, unsigned char* jumpTo, int hookLength)
13 | {
14 | DWORD dwOldProtect;
15 | VirtualProtect(hookLocation, hookLength, PAGE_EXECUTE_READWRITE, &dwOldProtect);
16 |
17 | // Write jmp opcode
18 | *hookLocation = 0xE9;
19 |
20 | // Write jump offset
21 | DWORD dwRelAddr = (DWORD)(jumpTo - hookLocation - 5);
22 | *((DWORD*)(hookLocation + 0x1)) = dwRelAddr;
23 |
24 | // Overwrite the rest with nop
25 | for (int i = 5; i < hookLength; i++)
26 | *(hookLocation + i) = 0x90;
27 |
28 | DWORD dwDummy;
29 | VirtualProtect(hookLocation, hookLength, dwOldProtect, &dwDummy);
30 | }
31 |
32 | __declspec(naked) void hkTexture2DSerialize()
33 | {
34 | __asm
35 | {
36 | pushad
37 | pushfd
38 | }
39 |
40 | // If we're loading a UDK package, skip over the source art data
41 | FixTextureLoad();
42 |
43 | __asm
44 | {
45 | popfd
46 | popad
47 |
48 | mov eax, [esi + 16]
49 | test eax, eax
50 |
51 | jmp[dwTextureFixReturn]
52 | }
53 | }
54 |
55 | void InitializePackageFix()
56 | {
57 | dwTextureFixReturn = (DWORD)pTextureFixLocation + 5;
58 | WriteJMPHook((unsigned char*)pTextureFixLocation, (unsigned char*)hkTexture2DSerialize, 5);
59 | }
60 |
61 | void SetIsLoadingUDKPackage(bool loadingUDKPkg)
62 | {
63 | GIsLoadingUDKPackage = loadingUDKPkg;
64 | }
65 | }
--------------------------------------------------------------------------------
/src/Settings.cpp:
--------------------------------------------------------------------------------
1 | #include "stdafx.h"
2 | #include "Settings.h"
3 | #include "Exceptions.h"
4 | #include "Logging.h"
5 |
6 | namespace Settings
7 | {
8 | std::wstring gBinPath;
9 | bool gDeveloperMode;
10 | bool gDisableAntiDebug;
11 |
12 | void Initialize(wchar_t* BinPath/*LauncherStruct* args*/)
13 | {
14 | /*
15 | if (args == nullptr || args->BinPath == nullptr)
16 | {
17 | throw FatalSDKException(6000, "Launcher settings struct was invalid, did you use the launcher?");
18 | }
19 | */
20 |
21 | gBinPath = BinPath; //args->BinPath;
22 | gDeveloperMode = true; //args->DeveloperMode;
23 | gDisableAntiDebug = true; // args->DisableAntiDebug;
24 | }
25 |
26 | std::wstring GetLogFilePath()
27 | {
28 | return GetBinFile(L"python-sdk.log");
29 | }
30 |
31 | std::wstring GetConfigFile()
32 | {
33 | return GetBinFile(L"python-sdk.cfg");
34 | }
35 |
36 | std::wstring GetBinFile(const std::wstring& Filename)
37 | {
38 | std::wstring newPath = gBinPath + Filename;
39 | return newPath;
40 | }
41 |
42 | std::wstring GetTextureFile(const std::wstring& Filename)
43 | {
44 | std::wstring newPath = gBinPath + L"textures\\" + Filename;
45 | return newPath;
46 | }
47 |
48 | std::wstring GetPythonFile(const std::wstring& Filename)
49 | {
50 | std::wstring newPath = gBinPath + L"Mods\\" + Filename;
51 | return newPath;
52 | }
53 |
54 | bool DeveloperModeEnabled()
55 | {
56 | return gDeveloperMode;
57 | }
58 |
59 | bool DisableAntiDebug()
60 | {
61 | return gDisableAntiDebug;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Util.cpp:
--------------------------------------------------------------------------------
1 | #define WIN32_LEAN_AND_MEAN
2 | #include "stdafx.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "Util.h"
9 | #include "Logging.h"
10 |
11 | //From McSimp's Borderlands2SDK
12 |
13 | namespace Util
14 | {
15 | static bool gIsInit = false;
16 |
17 | //Get top window
18 | static HWND gTopWnd = nullptr;
19 |
20 | void Initialize()
21 | {
22 | if (gIsInit)
23 | return;
24 | gIsInit = true;
25 | }
26 |
27 | struct EnumWindowsCallbackArgs
28 | {
29 | EnumWindowsCallbackArgs(DWORD p) : pid(p)
30 | {
31 | }
32 |
33 | const DWORD pid;
34 | std::vector handles;
35 | };
36 |
37 | static BOOL CALLBACK EnumWindowsCallback(HWND hnd, LPARAM lParam)
38 | {
39 | EnumWindowsCallbackArgs* args = (EnumWindowsCallbackArgs *)lParam;
40 | DWORD windowPID;
41 | (void)GetWindowThreadProcessId(hnd, &windowPID);
42 | if (windowPID == args->pid)
43 | {
44 | args->handles.push_back(hnd);
45 | }
46 | return TRUE;
47 | }
48 |
49 | HWND getToplevelWindows()
50 | {
51 | if (gTopWnd)
52 | return gTopWnd;
53 | EnumWindowsCallbackArgs args(GetCurrentProcessId());
54 | if (EnumWindows(&EnumWindowsCallback, (LPARAM)&args) == FALSE)
55 | {
56 | return nullptr;
57 | }
58 | return gTopWnd = args.handles[0];
59 | }
60 |
61 | DWORD GetMainThreadId(DWORD DwPid)
62 | {
63 | LPVOID lpTid;
64 | _asm
65 | {
66 | mov eax, fs:[18h]
67 | add eax, 36
68 | mov[lpTid], eax
69 | }
70 | HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, DwPid);
71 | if (hProcess == nullptr)
72 | return NULL;
73 | DWORD dwTid;
74 | if (ReadProcessMemory(hProcess, lpTid, &dwTid, sizeof(dwTid), nullptr) == FALSE)
75 | {
76 | CloseHandle(hProcess);
77 | return NULL;
78 | }
79 | CloseHandle(hProcess);
80 | return dwTid;
81 | }
82 |
83 | HANDLE GetMainThreadHandle(const DWORD DwPid, const DWORD DwDesiredAccess)
84 | {
85 | const DWORD dwTid = GetMainThreadId(DwPid);
86 | if (dwTid == FALSE)
87 | return nullptr;
88 | return OpenThread(DwDesiredAccess, FALSE, dwTid);
89 | }
90 |
91 | std::string FormatInternal(const char* Fmt, const va_list Args)
92 | {
93 | std::string str;
94 |
95 | const int buffSize = _vscprintf(Fmt, Args) + 1;
96 |
97 | if (buffSize <= 1)
98 | return str;
99 |
100 | char* szBuff = (char *)calloc(buffSize, sizeof(char));
101 |
102 | vsprintf_s(szBuff, buffSize, Fmt, Args);
103 |
104 | szBuff[buffSize - 1] = 0;
105 |
106 | str = szBuff;
107 | delete[] szBuff;
108 |
109 | return str;
110 | }
111 |
112 | std::string Format(const char* fmt, ...)
113 | {
114 | va_list args;
115 | va_start(args, fmt);
116 | std::string formatted = FormatInternal(fmt, args);
117 | va_end(args);
118 |
119 | return formatted;
120 | }
121 |
122 | std::wstring FormatInternal(const wchar_t* fmt, va_list args)
123 | {
124 | std::wstring str;
125 |
126 | const int buffSize = _vscwprintf(fmt, args) + 1;
127 |
128 | if (buffSize <= 1)
129 | return str;
130 |
131 | wchar_t* szBuff = (wchar_t *)calloc(buffSize, sizeof(wchar_t));
132 |
133 | vswprintf_s(szBuff, buffSize, fmt, args);
134 |
135 | szBuff[buffSize - 1] = 0;
136 |
137 | str = szBuff;
138 | free(szBuff);
139 |
140 | return str;
141 | }
142 |
143 | std::wstring Format(const wchar_t* fmt, ...)
144 | {
145 | va_list args;
146 | va_start(args, fmt);
147 | std::wstring formatted = FormatInternal(fmt, args);
148 | va_end(args);
149 |
150 | return formatted;
151 | }
152 |
153 | // TODO: Benchmarking and whatnot to see how these perform
154 | std::wstring Widen(const std::string& Input)
155 | {
156 | std::wstring out;
157 | out.assign(Input.begin(), Input.end());
158 | return out;
159 | }
160 |
161 | std::string Narrow(const std::wstring& Input)
162 | {
163 | std::string out;
164 | out.assign(Input.begin(), Input.end());
165 | return out;
166 | }
167 |
168 | void Popup(const std::wstring& StrName, const std::wstring& StrText)
169 | {
170 | MessageBox(nullptr, StrText.c_str(), StrName.c_str(), MB_OK | MB_ICONASTERISK);
171 | }
172 |
173 | void CloseGame()
174 | {
175 | TerminateProcess(GetCurrentProcess(), 1);
176 | }
177 |
178 | // This will convert a string like "Hello World" to "48 65 6C 6C 6F 20 57 6F 72 6C 64"
179 | // Taken mostly from http://stackoverflow.com/questions/3381614/c-convert-string-to-hexadecimal-and-vice-versa
180 | std::string StringToHex(const char* Input, const size_t Len)
181 | {
182 | static const char* const lut = "0123456789ABCDEF";
183 |
184 | std::string output;
185 | output.reserve((2 * Len) + Len);
186 | for (size_t i = 0; i < Len; ++i)
187 | {
188 | const unsigned char c = Input[i];
189 | output.push_back(lut[c >> 4]);
190 | output.push_back(lut[c & 15]);
191 | output.push_back(' ');
192 | }
193 | output.resize(output.size() - 1); // Remove that last space
194 |
195 | return output;
196 | }
197 |
198 | int WaitForModules(std::int32_t Timeout, const std::initializer_list& Modules)
199 | {
200 | bool signaled[32] = {false};
201 | bool success = false;
202 |
203 | std::uint32_t totalSlept = 0;
204 |
205 | if (Timeout == 0)
206 | {
207 | for (auto& mod : Modules)
208 | {
209 | if (GetModuleHandleW(std::data(mod)) == nullptr)
210 | return WAIT_TIMEOUT;
211 | }
212 | return WAIT_OBJECT_0;
213 | }
214 |
215 | if (Timeout < 0)
216 | Timeout = INT32_MAX;
217 |
218 | while (true)
219 | {
220 | for (auto i = 0u; i < Modules.size(); ++i)
221 | {
222 | auto& module = *(Modules.begin() + i);
223 | if (!signaled[i] && GetModuleHandleW(std::data(module)) != nullptr)
224 | {
225 | signaled[i] = true;
226 |
227 | //
228 | // Checks if all modules are signaled
229 | //
230 | bool done = true;
231 | for (auto j = 0u; j < Modules.size(); ++j)
232 | {
233 | if (!signaled[j])
234 | {
235 | done = false;
236 | break;
237 | }
238 | }
239 | if (done)
240 | {
241 | success = true;
242 | goto exit;
243 | }
244 | }
245 | }
246 | if (totalSlept > std::uint32_t(Timeout))
247 | {
248 | break;
249 | }
250 | Sleep(10);
251 | totalSlept += 10;
252 | }
253 |
254 | exit:
255 | return success ? WAIT_OBJECT_0 : WAIT_TIMEOUT;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/include/AntiDebug.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef ANTI_DEBUG_H
3 | #define ANTI_DEBUG_H
4 |
5 | namespace UnrealSDK
6 | {
7 | void HookAntiDebug();
8 | }
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/include/CHookManager.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef C_HOOK_MANAGER_H
3 | #define C_HOOK_MANAGER_H
4 |
5 | #include
6 | #include