Skip to content

Commit 18682ed

Browse files
committed
Fully implement "disable-mod-detection" setting
- Catch exceptions in OnPausedChanged - Unhide "disable-mod-detection" setting - Add error message for unsupported modifications - Change memory modification lookup to contain functions - Don't (re)enable mod detection automatically - Add memory.lua library that allows to change the protection of memory regions.
1 parent 28c07df commit 18682ed

File tree

5 files changed

+115
-31
lines changed

5 files changed

+115
-31
lines changed

files/libraries/memory.lua

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- Copyright (c) 2022 David Vogel
2+
--
3+
-- This software is released under the MIT License.
4+
-- https://opensource.org/licenses/MIT
5+
6+
local ffi = require("ffi")
7+
8+
local Memory = {}
9+
10+
if ffi.abi'64bit' then
11+
ffi.cdef([[
12+
typedef uint64_t __uint3264;
13+
]])
14+
else
15+
ffi.cdef([[
16+
typedef uint32_t __uint3264;
17+
]])
18+
end
19+
20+
ffi.cdef([[
21+
typedef void VOID;
22+
typedef VOID *LPVOID;
23+
typedef int BOOL;
24+
typedef __uint3264 ULONG_PTR, *PULONG_PTR;
25+
typedef ULONG_PTR SIZE_T, *PSIZE_T;
26+
typedef unsigned long DWORD;
27+
typedef DWORD *PDWORD;
28+
29+
BOOL VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
30+
]])
31+
32+
Memory.PAGE_NOACCESS = 0x01
33+
Memory.PAGE_READONLY = 0x02
34+
Memory.PAGE_READWRITE = 0x04
35+
Memory.PAGE_WRITECOPY = 0x08
36+
Memory.PAGE_EXECUTE = 0x10
37+
Memory.PAGE_EXECUTE_READ = 0x20
38+
Memory.PAGE_EXECUTE_READWRITE = 0x40
39+
Memory.PAGE_EXECUTE_WRITECOPY = 0x80
40+
Memory.PAGE_GUARD = 0x100
41+
Memory.PAGE_NOCACHE = 0x200
42+
Memory.PAGE_WRITECOMBINE = 0x400
43+
44+
---Changes the protection on a region of committed pages in the virtual address space of the calling process.
45+
---@param addr ffi.cdata*
46+
---@param size integer
47+
---@param newProtect integer
48+
---@return ffi.cdata* oldProtect
49+
function Memory.VirtualProtect(addr, size, newProtect)
50+
local oldprotect = ffi.new('DWORD[1]')
51+
if not ffi.C.VirtualProtect(addr, size, newProtect, oldprotect) then
52+
error(string.format("failed to call VirtualProtect(%s, %s, %s)", addr, size, newProtect))
53+
end
54+
55+
return oldprotect
56+
end
57+
58+
return Memory

files/message.lua

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,21 @@ function Message:ShowGeneralInstallationProblem(...)
176176
Lines = { ... },
177177
}
178178
end
179+
180+
---Tell the user that some modifications couldn't be applied because it is unsupported.
181+
---@param realm "config"|"magicNumbers"|"processMemory"|"filePatches"
182+
---@param name string
183+
---@param value any
184+
function Message:ShowModificationUnsupported(realm, name, value)
185+
self.List = self.List or {}
186+
187+
self.List["ModificationFailed"] = {
188+
Type = "warning",
189+
Lines = {
190+
string.format("Couldn't modify %q in %q realm.", name, realm),
191+
" ",
192+
"This simply means that this modification is not supported for the Noita version you are using.",
193+
"Feel free to open an issue at https://github.com/Dadido3/noita-mapcap.",
194+
},
195+
}
196+
end

files/modification.lua

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
local CameraAPI = require("noita-api.camera")
2020
local Coords = require("coordinates")
2121
local ffi = require("ffi")
22+
local Memory = require("memory")
2223
local NXML = require("luanxml.nxml")
2324
local Utils = require("noita-api.utils")
2425
local Vec2 = require("noita-api.vec2")
@@ -92,38 +93,40 @@ end
9293
---@param memory table
9394
function Modification.SetMemoryOptions(memory)
9495
-- Lookup table with the following hierarchy:
95-
-- DevBuild -> OS -> BuildDate -> Option -> Address.
96+
-- DevBuild -> OS -> BuildDate -> Option -> ModFunc.
9697
local lookup = {
9798
[true] = {
9899
Windows = {
99100
[0x00F77B0C] = { _BuildString = "Build Apr 23 2021 18:36:55", -- GOG dev build.
100-
mPostFxDisabled = 0x010E3B6C,
101-
mGuiDisabled = 0x010E3B6D,
102-
mGuiHalfSize = 0x010E3B6E,
103-
mFogOfWarOpenEverywhere = 0x010E3B6F,
104-
mTrailerMode = 0x010E3B70,
105-
mDayTimeRotationPause = 0x010E3B71,
106-
mPlayerNeverDies = 0x010E3B72,
107-
mFreezeAI = 0x010E3B73,
101+
mPostFxDisabled = function(value) ffi.cast("char*", 0x010E3B6C)[0] = value end,
102+
mGuiDisabled = function(value) ffi.cast("char*", 0x010E3B6D)[0] = value end,
103+
mGuiHalfSize = function(value) ffi.cast("char*", 0x010E3B6E)[0] = value end,
104+
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x010E3B6F)[0] = value end,
105+
mTrailerMode = function(value) ffi.cast("char*", 0x010E3B70)[0] = value end,
106+
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x010E3B71)[0] = value end,
107+
mPlayerNeverDies = function(value) ffi.cast("char*", 0x010E3B72)[0] = value end,
108+
mFreezeAI = function(value) ffi.cast("char*", 0x010E3B73)[0] = value end,
108109
},
109110
[0x00F80384] = { _BuildString = "Build Apr 23 2021 18:40:40", -- Steam dev build.
110-
mPostFxDisabled = 0x010EDEBC,
111-
mGuiDisabled = 0x010EDEBD,
112-
mGuiHalfSize = 0x010EDEBE,
113-
mFogOfWarOpenEverywhere = 0x010EDEBF,
114-
mTrailerMode = 0x010EDEC0,
115-
mDayTimeRotationPause = 0x010EDEC1,
116-
mPlayerNeverDies = 0x010EDEC2,
117-
mFreezeAI = 0x010EDEC3,
111+
mPostFxDisabled = function(value) ffi.cast("char*", 0x010EDEBC)[0] = value end,
112+
mGuiDisabled = function(value) ffi.cast("char*", 0x010EDEBD)[0] = value end,
113+
mGuiHalfSize = function(value) ffi.cast("char*", 0x010EDEBE)[0] = value end,
114+
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x010EDEBF)[0] = value end,
115+
mTrailerMode = function(value) ffi.cast("char*", 0x010EDEC0)[0] = value end,
116+
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x010EDEC1)[0] = value end,
117+
mPlayerNeverDies = function(value) ffi.cast("char*", 0x010EDEC2)[0] = value end,
118+
mFreezeAI = function(value) ffi.cast("char*", 0x010EDEC3)[0] = value end,
118119
},
119120
},
120121
},
121122
[false] = {
122123
Windows = {
123124
[0x00E1C550] = { _BuildString = "Build Apr 23 2021 18:44:24", -- Steam build.
124-
--enableModDetection = 0x0063D8AD, -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
125-
-- The page this is in is not writable, so this will crash Noita.
126-
-- This modification can be applied manually with some other tool that changes the permission prior to writing, like Cheat Engine.
125+
enableModDetection = function(value)
126+
local ptr = ffi.cast("char*", 0x0063D8AD)
127+
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
128+
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
129+
end,
127130
},
128131
},
129132
},
@@ -132,10 +135,10 @@ function Modification.SetMemoryOptions(memory)
132135
-- Look up the tree and set options accordingly.
133136

134137
local level1 = lookup[DebugGetIsDevBuild()]
135-
if level1 == nil then return end
138+
level1 = level1 or {}
136139

137140
local level2 = level1[ffi.os]
138-
if level2 == nil then return end
141+
level2 = level2 or {}
139142

140143
local level3
141144
for k, v in pairs(level2) do
@@ -146,9 +149,11 @@ function Modification.SetMemoryOptions(memory)
146149
end
147150

148151
for k, v in pairs(memory) do
149-
local address = level3[k]
150-
if address ~= nil then
151-
ffi.cast("char*", address)[0] = v
152+
local modFunc = level3[k]
153+
if modFunc ~= nil then
154+
modFunc(v)
155+
else
156+
Message:ShowModificationUnsupported("processMemory", k, v)
152157
end
153158
end
154159
end
@@ -243,7 +248,8 @@ function Modification.RequiredChanges()
243248
if ModSettingGet("noita-mapcap.disable-mod-detection") then
244249
memory["enableModDetection"] = 0
245250
else
246-
memory["enableModDetection"] = 1
251+
-- Don't actively (re)enable mod detection.
252+
--memory["enableModDetection"] = 1
247253
end
248254

249255
-- Disables or hides most of the UI.

init.lua

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,13 @@ end
150150
---@param isPaused boolean
151151
---@param isInventoryPause boolean
152152
function OnPausedChanged(isPaused, isInventoryPause)
153-
-- Set some stuff based on mod settings.
154-
-- Normally this would be in `OnModSettingsChanged`, but that doesn't seem to be called.
155-
local config, magic, memory, patches = Modification.RequiredChanges()
156-
Modification.SetMemoryOptions(memory)
153+
Message:CatchException("OnPausedChanged", function()
154+
-- Set some stuff based on mod settings.
155+
-- Normally this would be in `OnModSettingsChanged`, but that doesn't seem to be called.
156+
local config, magic, memory, patches = Modification.RequiredChanges()
157+
Modification.SetMemoryOptions(memory)
158+
159+
end)
157160
end
158161

159162
---Will be called when the game is unpaused, if player changed any mod settings while the game was paused.

settings.lua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ modSettings = {
318318
id = "disable-mod-detection",
319319
ui_name = " Disable mod detection",
320320
ui_description = "If enabled, Noita will behave as if no mods are enabled.\nTherefore secrets like the cauldron will be generated.",
321-
hidden = true,
322321
value_default = false,
323322
scope = MOD_SETTING_SCOPE_RUNTIME,
324323
},

0 commit comments

Comments
 (0)