forked from UE4SS-RE/RE-UE4SS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.lua
387 lines (327 loc) · 13.8 KB
/
main.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
-- THIS IS A TEMPORARY FIX FOR LOGICMODS NOT LOADING ON SERVERS UNTIL A NEW UPDATE TO UE4SS IS BEING PUSHED (SOONTM)
-- DO NOT USE THIS OUTSIDE OF DEDICATED SERVERS, IT IS ONLY RELIABLE FOR SERVER USE
-- ONCE THE ISSUE IS FIXED IN UE4SS ITSELF, YOU WONT NEED THIS WORKAROUND ANYMORE SO MAKE SURE TO REPLACE THIS FILE WITH THE ORIGINAL FILE
local UEHelpers = require("UEHelpers")
local VerboseLogging = false
local function Log(Message, OnlyLogIfVerbose)
if not VerboseLogging and OnlyLogIfVerbose then return end
print(Message)
end
package.path = '.\\Mods\\ModLoaderMod\\?.lua;' .. package.path
package.path = '.\\Mods\\ModLoaderMod\\BPMods\\?.lua;' .. package.path
local Mods = {}
local OrderedMods = {}
-- Contains mod names from Mods/BPModLoaderMod/load_order.txt and is used to determine the load order of BP mods.
local ModOrderList = {}
local DefaultModConfig = {}
DefaultModConfig.AssetName = "ModActor_C"
DefaultModConfig.AssetNameAsFName = UEHelpers.FindOrAddFName("ModActor_C")
-- Checks if the beginning of a string contains a certain pattern.
local function StartsWith(String, StringToCompare)
return string.sub(String,1,string.len(StringToCompare))==StringToCompare
end
local function FileExists(file)
local f = io.open(file, "rb")
if f then f:close() end
return f ~= nil
end
-- Reads lines from the specified file and returns a table of lines read.
-- Second argument takes a string that can be used to exclude lines starting with that string. Default ;
local function LinesFrom(file, ignoreLinesStartingWith)
if not FileExists(file) then return {} end
if ignoreLinesStartingWith == nil then
ignoreLinesStartingWith = ";"
end
local lines = {}
for line in io.lines(file) do
if not StartsWith(line, ignoreLinesStartingWith) then
lines[#lines + 1] = line
end
end
return lines
end
-- Loads mod order data from load_order.txt and pushes it into ModOrderList.
local function LoadModOrder()
local file = 'Mods/BPModLoaderMod/load_order.txt'
local lines = LinesFrom(file)
local entriesAdded = 0
for _, line in pairs(lines) do
ModAlreadyExists = false
for _, ModName in pairs(ModOrderList) do
if ModName == line then
ModAlreadyExists = true
end
end
-- Checks for double mod entries in the file and if a mod was already included, skip it.
if not ModAlreadyExists then
table.insert(ModOrderList, line)
entriesAdded = entriesAdded + 1
end
end
if entriesAdded <= 0 then
Log(string.format("Mods/BPModLoaderMod/load_order.txt not present or no matching mods, loading all BP mods in random order."))
end
end
local function SetupModOrder()
local Priority = 1
-- Adds priority mods first by their respective order as specified in load_order.txt
for _, ModOrderEntry in pairs(ModOrderList) do
for ModName, ModInfo in pairs(Mods) do
if type(ModInfo) == "table" then
if ModOrderEntry == ModName then
OrderedMods[Priority] = ModInfo
OrderedMods[Priority].Name = ModName
OrderedMods[Priority].Priority = Priority
Priority = Priority + 1
end
end
end
end
-- Adds the remaining mods in a random order after the prioritized mods.
for ModName, ModInfo in pairs(Mods) do
ModAlreadyIncluded = false
for _, OrderedModInfo in ipairs(OrderedMods) do
if type(OrderedModInfo) == "table" then
if OrderedModInfo.Name == ModName then
ModAlreadyIncluded = true
end
end
end
if not ModAlreadyIncluded then
ModInfo.Name = ModName
table.insert(OrderedMods, ModInfo)
end
end
end
local function LoadModConfigs()
-- Load configurations for mods.
local Dirs = IterateGameDirectories();
if not Dirs then
error("[BPModLoader] UE4SS does not support loading mods for this game.")
end
local LogicModsDir = Dirs.Game.Content.Paks.LogicMods
if not Dirs then error("[BPModLoader] IterateGameDirectories failed, cannot load BP mod configurations.") end
if not LogicModsDir then
CreateLogicModsDirectory();
Dirs = IterateGameDirectories();
LogicModsDir = Dirs.Game.Content.Paks.LogicMods
if not LogicModsDir then error("[BPModLoader] Unable to find or create Content/Paks/LogicMods directory. Try creating manually.") end
end
for ModDirectoryName,ModDirectory in pairs(LogicModsDir) do
Log(string.format("Mod: %s\n", ModDirectoryName))
for _,ModFile in pairs(ModDirectory.__files) do
Log(string.format(" ModFile: %s\n", ModFile.__name))
if ModFile.__name == "config.lua" then
dofile(ModFile.__absolute_path)
if type(Mods[ModDirectoryName]) ~= "table" then break end
if not Mods[ModDirectoryName].AssetName then break end
Mods[ModDirectoryName].AssetNameAsFName = UEHelpers.FindOrAddFName(Mods[ModDirectoryName].AssetName)
break
end
end
end
-- Load a default configuration for mods that didn't have their own configuration.
for _, ModFile in pairs(LogicModsDir.__files) do
local ModName = ModFile.__name
local ModNameNoExtension = ModName:match("(.+)%..+$")
local FileExtension = ModName:match("^.+(%..+)$");
if FileExtension == ".pak" and not Mods[ModNameNoExtension] then
--Log("--------------\n")
--Log(string.format("ModFile: '%s'\n", ModFile.__name))
--Log(string.format("ModNameNoExtension: '%s'\n", ModNameNoExtension))
--Log(string.format("FileExtension: %s\n", FileExtension))
Mods[ModNameNoExtension] = {}
Mods[ModNameNoExtension].AssetName = DefaultModConfig.AssetName
Mods[ModNameNoExtension].AssetNameAsFName = DefaultModConfig.AssetNameAsFName
Mods[ModNameNoExtension].AssetPath = string.format("/Game/Mods/%s/ModActor", ModNameNoExtension)
end
end
LoadModOrder()
SetupModOrder()
end
LoadModConfigs()
for _,v in ipairs(OrderedMods) do
Log(string.format("%s == %s\n", v.Name, v))
if type(v) == "table" then
for k2,v2 in pairs(v) do
Log(string.format(" %s == %s\n", k2, v2))
end
end
end
local AssetRegistryHelpers = nil
local AssetRegistry = nil
local ModsToRevalidate = {}
local function LoadMod(ModName, ModInfo, World)
local ExistingModActor = ModRef:GetSharedVariable("BPModLoaderMod_" .. ModName)
if ExistingModActor ~= nil and ExistingModActor:IsValid() then
print("ModActor " .. ModName .. " already exists.\n")
local OnReload = ExistingModActor.OnReload
if OnReload:IsValid() then
Log(string.format("Executing 'OnReload' for mod '%s', with path: '%s'\n", ModName, ExistingModActor:GetFullName()))
OnReload()
else
Log(string.format("OnReload not implemented for mod %s, skipping...\n", ModName), true)
end
return true
end
if ModInfo.Priority ~= nil then
Log(string.format("Loading mod [Priority: #%i]: %s\n", ModInfo.Priority, ModName))
else
Log(string.format("Loading mod: %s\n", ModName))
end
if ModInfo.AssetPath == nil or ModInfo.AssetPath == nil then
Log(string.format("Could not load mod '%s' because it has no asset path or name.\n", ModName))
return true
end
local AssetData = nil
if UnrealVersion.IsBelow(5, 1) then
AssetData = {
["ObjectPath"] = UEHelpers.FindOrAddFName(string.format("%s.%s", ModInfo.AssetPath, ModInfo.AssetName)),
}
else
AssetData = {
["PackageName"] = UEHelpers.FindOrAddFName(ModInfo.AssetPath),
["AssetName"] = UEHelpers.FindOrAddFName(ModInfo.AssetName),
}
end
local ModClass = AssetRegistryHelpers:GetAsset(AssetData)
if not ModClass:IsValid() then
local ObjectPath = AssetData.ObjectPath and AssetData.ObjectPath:ToString() or ""
local PackageName = AssetData.PackageName and AssetData.PackageName:ToString() or ""
local AssetName = AssetData.AssetName and AssetData.AssetName:ToString() or ""
Log(string.format("ModClass for '%s' is not valid\nObjectPath: %s\nPackageName: %s\nAssetName: %s", ModName, ObjectPath,PackageName, AssetName))
return true
end
if not World:IsValid() then Log(string.format("World is not valid for '%s' to spawn in", ModName)) return false end
local Actor = World:SpawnActor(ModClass, {}, {})
if not Actor:IsValid() then
Log(string.format("Actor for mod '%s' is not valid, retrying...\n", ModName))
if ModsToRevalidate[ModName] == nil then
ModsToRevalidate[ModName] = ModInfo
end
return false
else
if ModsToRevalidate[ModName] ~= nil then
ModsToRevalidate[ModName] = nil
end
ModRef:SetSharedVariable("BPModLoaderMod_" .. ModName, Actor)
Log(string.format("Actor: %s\n", Actor:GetFullName()))
local PreBeginPlay = Actor.PreBeginPlay
if PreBeginPlay:IsValid() then
Log(string.format("Executing 'PreBeginPlay' for mod '%s', with path: '%s'\n", ModName, Actor:GetFullName()))
PreBeginPlay()
else
Log(string.format("PreBeginPlay not valid for mod %s\n", ModName), true)
end
return true
end
end
local function CacheAssetRegistry()
if AssetRegistryHelpers and AssetRegistry then return end
AssetRegistryHelpers = StaticFindObject("/Script/AssetRegistry.Default__AssetRegistryHelpers")
if not AssetRegistryHelpers:IsValid() then print("AssetRegistryHelpers is not valid\n") end
if AssetRegistryHelpers then
AssetRegistry = AssetRegistryHelpers:GetAssetRegistry()
if AssetRegistry:IsValid() then return end
end
AssetRegistry = StaticFindObject("/Script/AssetRegistry.Default__AssetRegistryImpl")
if AssetRegistry:IsValid() then return end
error("AssetRegistry is not valid\n")
end
print("NOTE: This is intended to be a temporary workaround for Dedicated Server LogicMods issues\n")
-- Keep in mind this will only work for Palworld.
-- If for whatever reason this is adapted for something else, use LiveView and find the first valid actor instance in the world and replace BP_PalMapObjectManager_C with that.
local function GetWorldFromWorldObject()
local WorldObject = FindFirstOf("BP_PalMapObjectManager_C")
if WorldObject ~= nil and WorldObject:IsValid() then
local World = WorldObject:GetWorld()
if World ~= nil and World:IsValid() then
return World
end
end
return nil
end
local LoadDelayTime = 2500
local function LoadModsDelayed()
ExecuteInGameThread(function ()
WasSuccessful = true
local World = GetWorldFromWorldObject()
if World ~= nil and World:IsValid() then
print("[BPModLoaderMod] Failed to load LogicMods, retrying...\n")
for ModName, ModInfo in pairs(ModsToRevalidate) do
if type(ModInfo) == "table" then
if LoadMod(ModName, ModInfo, World) == false then
WasSuccessful = false
end
end
end
else
print("[BPModLoaderMod] World was invalid, retrying...\n")
WasSuccessful = false
end
if WasSuccessful == false then
ExecuteWithDelay(LoadDelayTime, function ()
LoadModsDelayed()
end)
else
print("Finished loading LogicMods!\n")
end
end)
end
local function LoadMods(World)
CacheAssetRegistry()
-- Added ExecuteInGameThread here instead so it's easier to get the final result from LoadMod
ExecuteInGameThread(function()
local WasSuccessful = true
for _, ModInfo in ipairs(OrderedMods) do
if type(ModInfo) == "table" then
if LoadMod(ModInfo.Name, ModInfo, World) == false then
WasSuccessful = false
end
end
end
if WasSuccessful == false then
ExecuteWithDelay(LoadDelayTime, function ()
LoadModsDelayed()
end)
else
print("Finished loading mods!\n")
end
end)
end
local function TryLoadMods()
local World = GetWorldFromWorldObject()
if World ~= nil and World:IsValid() then
LoadMods(World)
else
print("[BPModLoaderMod] World was invalid, retrying...\n")
ExecuteWithDelay(LoadDelayTime, function ()
TryLoadMods()
end)
end
end
-- The workaround is to add a manual delay for loading the mods because the typical 'RegisterLoadMapPostHook' is most likely being missed from UE4SS due to the map loading before the hook is even registered.
ExecuteWithDelay(LoadDelayTime, function ()
TryLoadMods()
end)
local function LoadModsManual()
LoadMods(UEHelpers.GetWorld())
end
-- Commented out for now since we're doing a delayed load instead
RegisterLoadMapPostHook(function(Engine, World)
--LoadMods(World:get())
end)
RegisterBeginPlayPostHook(function(ContextParam)
local Context = ContextParam:get()
for _,ModConfig in ipairs(OrderedMods) do
if Context:GetClass():GetFName() ~= ModConfig.AssetNameAsFName then return end
local PostBeginPlay = Context.PostBeginPlay
if PostBeginPlay:IsValid() then
Log(string.format("Executing 'PostBeginPlay' for mod '%s'\n", Context:GetFullName()))
PostBeginPlay()
else
Log(string.format("PostBeginPlay not valid for mod %s\n", Context:GetFullName(), true))
end
end
end)
RegisterKeyBind(Key.INS, LoadModsManual)