1 2
11 12 13 14
Editor, Player (69)
Joined: 6/22/2005
Posts: 1050
MUGG wrote:
I need a script that reads lines in text file A.txt, and checks if they are present in text file B.txt. If not, the lines are deleted from A.txt.
Does the order of the lines matter, or are you looking just for individual line matches between the files?
MUGG wrote:
I figured someone good can come up with a solution in 1 min so I don't have to spend 30 mins working on it. :P
You don't get better if you don't practice. :P
Current Projects: TAS: Wizards & Warriors III.
Editor, Expert player (2330)
Joined: 5/15/2007
Posts: 3933
Location: Germany
In A.txt, each line is a string and I want to search if that string exists anywhere in B.txt. I perfectly understand what you're saying. I just figured it's better this way for this one case. I've been at this for over an hour and I can't figure it out. EDIT: When using string.find(s, pattern), there are characters that when used in 'pattern', they will have a special meaning ( https://www.lua.org/pil/20.2.html ). How can I disable these characters from having a special meaning, without using the escape '%' method?
Editor, Player (69)
Joined: 6/22/2005
Posts: 1050
Probably not the most elegant solution, but this works with some test files I made:
Language: lua

local file_name_A = "C:\\path\\to\\A.txt" local file_name_B = "C:\\path\\to\\B.txt" local file_A, file_B, line_A, line_B local matched_lines = {} file_A = io.open(file_name_A, "r") file_B = io.open(file_name_B, "r") if (file_A ~= nil) and (file_B ~= nil) then file_A:close() file_B:close() for line_A in io.lines(file_name_A) do for line_B in io.lines(file_name_B) do if line_A == line_B then table.insert(matched_lines, line_A) end end end file_A = io.open(file_name_A, "w") for i = 1, #matched_lines do file_A:write(matched_lines[i]) file_A:write("\n") end file_A:close() end
Also, please be aware of this issue regarding file reading.
Current Projects: TAS: Wizards & Warriors III.
Editor, Expert player (2330)
Joined: 5/15/2007
Posts: 3933
Location: Germany
Thank you Dacicus. Your script works when comparing lines from A with B. It does not work if lines in B have stuff appended to it. Also it outputs the matched lines in A, whereas I want to output the lines not found in B. So I have adjusted part of your code. This will work if lines in B are expected to have gibberish appended at their end.
Language: Lua

for line_A in io.lines(file_name_A) do for line_B in io.lines(file_name_B) do lengthA = string.len(line_A) if line_A ~= string.sub(line_B,1,lengthA) then table.insert(matched_lines, line_A) break end end end
Thank you again!
Editor, Player (69)
Joined: 6/22/2005
Posts: 1050
MUGG wrote:
As said, I'm checking if lines of A exist anywhere in file B. I tried with string.find(s, pattern) but pattern misbehaves if I want to search for characters like - * % $ [ ] etc.
Have you tried the additional arguments to string.find from the official documentation? I think that the plain argument might do what you want: string.find(s, pattern, 1, true)
Current Projects: TAS: Wizards & Warriors III.
Editor, Expert player (2330)
Joined: 5/15/2007
Posts: 3933
Location: Germany
Dacicus, thanks. Now it works perfectly.
Lil_Gecko
He/Him
Player (98)
Joined: 4/7/2011
Posts: 520
I wanted to draw some text on screen. Like gui.drawText(0,0,"blablabla random text"); Now let's say that "blablabla" reaches the far right of the screen. Is there a way for the rest of the text to go automatically underneath blablabla or do I have to manually type : gui.drawText(0,0,"blablabla"); gui.drawText(0,15,"random text"); ? I don't know if my question is clear.
Editor, Player (69)
Joined: 6/22/2005
Posts: 1050
Lil_Gecko wrote:
Now let's say that "blablabla" reaches the far right of the screen. Is there a way for the rest of the text to go automatically underneath blablabla
It sounds like you want the text to wrap to the next line automatically, but the reference for the current built-in Lua functions does not seem to have such an option.
Current Projects: TAS: Wizards & Warriors III.
Site Admin, Skilled player (1254)
Joined: 4/17/2010
Posts: 11480
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Not automatically. Just use linebreaks.
Language: lua

gui.drawText(0,0,"blablabla\nrandom text")
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Editor, Player (175)
Joined: 4/7/2015
Posts: 331
Location: Porto Alegre, RS, Brazil
Lil_Gecko wrote:
Is there a way for the rest of the text to go automatically underneath blablabla
I made a function, give it a try:
Language: lua

-- Font settings local BIZHAWK_FONT_WIDTH = 10 local BIZHAWK_FONT_HEIGHT = 14 -- Text wrap function local function draw_text_wrap(x, y, text) local text_table = {} for word in string.gmatch(text, "%a+") do table.insert(text_table, word) end local x_pos, y_pos = x, y local limit = client.screenwidth() -- Fit 1st word if x_pos + string.len(text_table[1])*BIZHAWK_FONT_WIDTH > limit then x_pos = limit - string.len(text_table[1])*BIZHAWK_FONT_WIDTH ; y_pos = y_pos + BIZHAWK_FONT_HEIGHT end gui.text(x_pos, y_pos, text_table[1]) x_pos = x_pos + (string.len(text_table[1]) + 1)*BIZHAWK_FONT_WIDTH -- Fit the other words for i = 2, #text_table do if x_pos + string.len(text_table[i])*BIZHAWK_FONT_WIDTH > limit then x_pos = x y_pos = y_pos + BIZHAWK_FONT_HEIGHT if x_pos + string.len(text_table[i])*BIZHAWK_FONT_WIDTH > limit then x_pos = limit - string.len(text_table[i])*BIZHAWK_FONT_WIDTH end end gui.text(x_pos, y_pos, text_table[i]) x_pos = x_pos + (string.len(text_table[i]) + 1)*BIZHAWK_FONT_WIDTH end end
There's probably other [better] ways to do that, this is the one that first came to mind
Games are basically math with a visual representation of this math, that's why I make the scripts, to re-see games as math. My things: YouTube, GitHub, Pastebin, Twitter
Lil_Gecko
He/Him
Player (98)
Joined: 4/7/2011
Posts: 520
Thank you brunovalads !!! Works like a charm !
Editor, Player (175)
Joined: 4/7/2015
Posts: 331
Location: Porto Alegre, RS, Brazil
Just to make clear for anyone else interested: You use this function with a x,y coordinate and a text as arguments, and you'll probably will put it inside your script loop. This is for BizHawk and its default gui.text font, but can easily be adapted to other fonts and emus. Example:
Language: lua

local test_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam rhoncus, mauris quis dapibus maximus, felis mi dapibus mauris, quis pulvinar lectus risus sed metus. Suspendisse id libero sed metus scelerisque maximus id sed est. Mauris iaculis justo vel neque euismod, a semper sapien consectetur. Nam vitae consequat tortor, nec efficitur purus. Proin porttitor libero tellus, sed mattis diam rutrum et. Curabitur ut dui ex. Sed nec nulla nec sapien tempus viverra. Ut ut sollicitudin nisi. Sed in sem ut erat ullamcorper interdum id et diam. Nulla eu tempus nisl. Phasellus eu nisi eget neque placerat viverra eu quis leo. Morbi vehicula est massa, sit amet sodales ligula blandit ut. Phasellus egestas ipsum eu vulputate tincidunt. Mauris gravida risus in malesuada malesuada." while true do draw_text_wrap(128, 0, test_text) emu.frameadvance() end
You can see the result of this example in this image. Now I realised it doesn't consider any symbol (like commas), but the fix should be easy.
Games are basically math with a visual representation of this math, that's why I make the scripts, to re-see games as math. My things: YouTube, GitHub, Pastebin, Twitter
Amaraticando
It/Its
Editor, Player (159)
Joined: 1/10/2012
Posts: 673
Location: Brazil
MUGG wrote:
I need a script that reads lines in text file A.txt, and checks if they are present in text file B.txt. If not, the lines are deleted from A.txt. I figured someone good can come up with a solution in 1 min so I don't have to spend 30 mins working on it. :P Thanks in advance.
Too late, but: EDIT: I'm afraid the previous solution had quadratic time. This one is linear:
Language: lua

local function do_shit_with_file(name, shit) local handle, err = io.open(name) if (handle) then local foo = shit(handle) handle:close() return foo else error(err) end end local function get_lines(handle) local ram = {} for line in handle:lines() do ram[line] = true end return ram end local function erase_if_not_in(other_file) return function(handle) local buffer = {} for line in handle:lines() do if (other_file[line]) then table.insert(buffer, line) end end return buffer end end local function overwrite_file(name, content) local handle = assert(io.open(name, 'w+')) handle:write(content) handle:close() end local B = do_shit_with_file('B.txt', get_lines) local A = do_shit_with_file('A.txt', erase_if_not_in(B)) overwrite_file('A.txt', table.concat(A, '\n'))
Skilled player (1742)
Joined: 9/17/2009
Posts: 4984
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
From the ARM Manual:
MUL (Multiply) multiplies two signed or unsigned 32-bit values. The least significant 32 bits of the result are written to the destination register.
This is my attempt to implement that in BizHawk's lua: Download MUL.lua
Language: lua

function MUL(A,B) local result = A * B local temp = result local result = 0 for i = 0,31 do result = (temp%2 == 1) and result+2^i or result temp = math.floor(temp/2) --can't use bit shift due to being 64 bit end return result end
Since the bitwise library in Lua for BizHawk is 32 bit, I cannot use bit.band(result,0xFFFFFFFF00000000). Is this the best way to extract the lower 32 bits from the result of a multiply without using bit library, or can it be done better?
Masterjun
He/Him
Site Developer, Skilled player (1988)
Joined: 10/12/2010
Posts: 1185
Location: Germany
There are several things wrong here: 1. The "least significant 32 bits" mean the rightmost bits. A theoretical bit.band(result,0xFFFFFFFF00000000) would give you the most significant 32 bits of a 64 bit number, not the least significant bits. 2. Despite that misunderstanding, your code does give you the least significant 32 bits of result. But: 3. In your code, result is created by multiplying two 32 bit numbers, which in BizHawk's Lua (5.1) are doubles, meaning once the result is over 2^53, you will have inaccuracies. Example: 0xFFFFFFFF*0x00200001 equals 0x200000FFDFFFFF. But when using doubles that multiplication gives you 0x200000FFE00000. Since your goal is the least significant 32 bits, it can easily be done using the mod operator (%): number % 0x100000000. The bigger problem is an accurate 32 bit multiplication. The result of that multiplication can reach 64 bits, so the 53 bits of doubles is not enough. Luckily, you can simply split one of the numbers into two 16 bit parts, giving you two 32 bit with 16 bit multiplications, where the result is only 48 bits.
Language: lua

function MUL(A,B) local reslow = A * (B%0x10000) -- A multiplied with lower 16 bits of B local reshigh = A * (math.floor(B/0x10000)%0x10000) -- A multiplied with higher 16 bits of B (shifted down) reshigh = reshigh%0x10000 -- only 16 bits can matter here if result is 32 bits return (reshigh*0x10000 + reslow)%0x100000000 -- recombine and cut off to 32 bits end
(The final recombining step adds a 32 bit and a 48 bit number, so technically requires 49 bits. But we have 53 bits so we're good.)
Warning: Might glitch to credits I will finish this ACE soon as possible (or will I?)
Skilled player (1742)
Joined: 9/17/2009
Posts: 4984
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
Thanks very much! If I wanted 64 bit multiplication for implementing UMULL (The lower 32 bits of the 64 bit result are written to RdLo, the upper 32 bits of the result are written to RdHi.), I can still use your approach right? Just for the upper 32 bits don't use %, and right shift reslow/reshigh at the end? Edit: Going to do the 32 bit multiplication by hand to see how this works.
Language: lua

--[[ 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 A 4,294,967,295 x 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0101 1010 1010 1010 B 4,294,924,970 1111 1111 1111 1111 0101 1010 1010 1001 0000 0000 0000 0000 1010 0101 0101 0110 Res -181,793,080,695,466 RdHi should be 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0101 1010 1010 1001 Lower 32 bits, and thus Answer and RdLo should be 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1010 0101 0101 0110 Answer 42,326 --------------------------------------------------------------------------------- Break B to 2 parts 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 B1 Upper 16 65,535 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0101 1010 1010 1010 B2 Lower 16 23,210 A x B1 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 A 4,294,967,295 x 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 B1 65,535 0000 0000 0000 0000 1111 1111 1111 1110 1111 1111 1111 1111 0000 0000 0000 0001 C1 281,470,681,677,825 Do bits 16 to 31 in C1 corresponds to bits 48 to 63 in "Answer"? That is: RdHi_1 = C1 Mod 4,294,967,296 shift right 16 0000 0000 0000 0000 1111 1111 1111 1110 1111 1111 1111 1111 0000 0000 0000 0001 C1 281,470,681,677,825 Mod 4,294,967,296 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0001 4,294,901,761 Shift 16 right 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 RdHi_1 Mod 4,294,967,296 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0001 4,294,901,761 Shift left 16 times 0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0001 0000 0000 0000 0000 Mod 4,294,967,296 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 C1 65,536 A x B2 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 A 4,294,967,295 x 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0101 1010 1010 1010 B2 23,210 0000 0000 0000 0000 0101 1010 1010 1001 1111 1111 1111 1111 1010 0101 0101 0110 C2 99,686,190,916,950 Do bits 32 to 47 in C2 corresponds to bits 32 to 47 in "Answer"? That is: RdHi_2 = C2 shift right 32 0000 0000 0000 0000 0101 1010 1010 1001 1111 1111 1111 1111 1010 0101 0101 0110 C2 99,686,190,916,950 Shift right 32 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0101 1010 1010 1001 RdHi_2 23,209 Combining C1 + C2 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 C1 281,470,681,808,896 + 0000 0000 0000 0000 0101 1010 1010 1001 1111 1111 1111 1111 1010 0101 0101 0110 C2 99,686,190,916,950 0000 0000 0000 0000 0101 1010 1010 1010 0000 0000 0000 0000 1010 0101 0101 0110 D 381,156,872,725,846 D Mod 4,294,967,296 gives RdLo (lower 32 bits) RdHi_1 x 65,536 + RdHi_2 gives RdHi (upper 32 bits) ]]-- function MUL(A,B) --The lower 32 bits of the 64 bit result are written to RdLo, the upper 32 bits of the result are written to RdHi. local reslow = A * (B%0x10000) -- A multiplied with lower 16 bits of B local reshigh = A * (math.floor(B/0x10000)%0x10000) -- A multiplied with higher 16 bits of B (shifted down) local RdHi = math.floor(reshigh)/0x10000 --Shift right RdHi = RdHi * 0x10000 --Shift left RdHi = RdHi + math.floor(reslow/0x10000) reshigh = reshigh%0x10000 -- only 16 bits can matter here if result is 32 bits local RdLo = (reshigh*0x10000 + reslow)%0x100000000 -- recombine and cut off to 32 bits return RdHi, RdLo end
Is this correct on bits 16 to 31 and bits 32 to 47 comment?
Masterjun
He/Him
Site Developer, Skilled player (1988)
Joined: 10/12/2010
Posts: 1185
Location: Germany
A 64 bit multiplication is harder than that. In my code, reslow and reshigh are initially 48 bit numbers, in the form: reslow --> 0x0000LLLLLLLLLLLL reshigh -> 0xHHHHHHHHHHHH0000 (note that reshigh represents the 64 bit number but itself simply contains a 48 bit value) And that's why I could just do reshigh%0x10000 turning it into: reslow --> 0x0000LLLLLLLLLLLL reshigh -> 0x00000000HHHH0000 So that adding them stays under 53 bits.
If you want 64 bit multiplication, you need to split reslow into 16bit|32bit, and reshigh into 32bit|16bit, then add the formers and the latters (correctly shifted) with carry over, and then return low32 and high32 separately (since Lua 5.1 can't represent 64 bit numbers accurately).
Language: lua

function MUL64(A,B) local reslow = A * (B%0x10000) -- 0x0000LLLLLLLLLLLL local reshigh = A * math.floor(B/0x10000) -- 0xHHHHHHHHHHHH0000 local reslow_lo = reslow%0x100000000 -- 0x00000000LLLLLLLL local reslow_hi = math.floor(reslow/0x100000000) -- 0x0000LLLL00000000 local reshigh_lo = reshigh%0x10000 -- 0x00000000HHHH0000 local reshigh_hi = math.floor(reshigh/0x10000) -- 0xHHHHHHHH00000000 local low32 = reshigh_lo*0x10000 + reslow_lo local high32 = reshigh_hi + reslow_hi high32 = high32 + math.floor(low32/0x100000000) -- add what carries over low32 = low32%0x100000000 -- 32 bit high32 = high32%0x100000000 -- 32 bit return low32,high32 end
Warning: Might glitch to credits I will finish this ACE soon as possible (or will I?)
Skilled player (1742)
Joined: 9/17/2009
Posts: 4984
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
I'm trying to implement flags for the CPSR (CPU Status Register) in ARM. Does anyone know what's the best way to do that? Right now, I have 3 ideas: Plan 1
set_flags(CPSR, flag0, flag1, flag2, ..., flag31)
	if flag0 > 0 then 
	if flag0 > 0 then CPSR = bit.set(CPSR, 0) else CPSR = bit.clear(CPSR,31) end
	if flag1 > 0 then CPSR = bit.set(CPSR, 1) else CPSR = bit.clear(CPSR,31) end
	...
	if flag31 > 0 then CPSR = bit.set(CPSR, 31) else CPSR = bit.clear(CPSR,31) end
end
The in every function that changes the CPSR, just check if flag should be 0 or 1, and at the end write CPSR = set_flags(CPSR,N,Z,C,V,Q, ... ,E, A, I, F, T, M) where those letters correspond to the flags in https://www.keil.com/pack/doc/cmsis/Core_A/html/group__CMSIS__CPSR.html That's easy to check, but has tons of redundant arguments given most functions never touch all the flags at once. Plan 2
set_flags(CPSR, new_CPSR)
	CPSR = new_CPSR
	return CPSR
end
This is super short, but it makes it really easy to mess up setting up the new CPSR value, since it's now just 32 bit number = another 32 bit number with no way to control individual bits Plan 3
set_flags(CPSR, table_CPSR)
	for i = 0, 31 do
		if table_CPSR[i] = 1 then CPSR = bit.set(CPSR, i) else CPSR = bit.clear(CPSR, i) end
	end
end
table_CPSR can also be a number using bit.shifts; This would basically be like plan 1, but as either a table, or a number where you shift the bits. Unfortunately that's a for loop every function call that changes the CPSR, even if it's only a single flag changed. Anyone know a smarter way to set flags, while also making sure the flags are correct?
Zinfidel
He/Him
Player (206)
Joined: 11/21/2019
Posts: 247
Location: Washington
If what you are trying to do is be clever with arguments so you don't have consider all possible arguments every time, you could do something like this:
Language: lua

registers = {C=2, V=3}; function set_flags(CSPR, flagPairs) for k, v in pairs(flagPairs) do local mask = bit.lshift(1,registers[k]); CSPR = bit.bor(bit.band(CSPR, bit.bnot(mask)), bit.band(bit.lshift(v, registers[k]), mask)); end return CSPR; end while true do local CSPR = 8; t = {C=1,V=0}; print(set_flags(CSPR, t)); emu.frameadvance(); end
This would output "4". Ignoring the bit hacking fuckery, the main gist here is that you set up a table with the names of your registers and their associated position in your integer. Then when you want to set or clear specific bits, you pass in a table with keys that are the register names and either a 0 or a 1 depending on what you're doing. This way you only pass in pairs of registers and values that you want to modify. I literally only tested this to see that it compiles and the sample output is correct, so make sure to test yourself. EDIT: Code inside the for loop that is actually human-readable would look like:
Language: lua

if v then CSPR = bit.set(CSPR, registers[k]); else CSPR = bit.clear(CSPR, registers[k]); end
and you would pass in bools instead of integer 1s and 0s.
Skilled player (1742)
Joined: 9/17/2009
Posts: 4984
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
Note: Replied to the above on discord, so if any future person thinks I was ignoring them, oops! Thanks again! For memory addresses that uses "System Bus" memory domain in GB, how does one convert them to SGB, given it doesn't seem to have "System Bus"? GB memory.getmemorydomainlist():
"0": "WRAM"
"1": "ROM"
"2": "VRAM"
"3": "CartRAM"
"4": "OAM"
"5": "HRAM"
"6": "System Bus"
SGB memory.getmemorydomainlist():
"0": "WRAM"
"1": "ROM"
"2": "VRAM"
"3": "CartRAM"
"4": "OAM"
"5": "HRAM"
"6": "IO"
"7": "BOOTROM"
"8": "BGP"
"9": "OBP"
I'm trying to convert a script for Wario Land to work on GB, SGB, GBC, and no idea how to make it work for System Bus.
Joined: 10/20/2019
Posts: 29
Location: UK
I just started writing a post up asking for help but I see my problem has been solved in lua for quite some time it seems :) Does anyone know when event.onmemoryread/write started hitting for N64 actually at the moment the memory is read/written? I'd got the idea in my head that it didn't run your code until the frame was complete. I have mostly been using 1.11 - just out of habit! This is very good news because I'm just starting work on a bot for a Goldeneye 00 agent full run, and I've found the guards' hitboxes in memory but they are only available briefly before they are overwritten. Sometimes the game syncs up well with the 1/60th frame advances and you just can't read them. More generally I thought it there was some 'atomicity' to the 1/60th frame advance which was going to be a huge pain for TASing - I'm glad it seems this isn't true at all :)
whiteted#2289 on discord Goldeneye 00a TAS due to be completed circa 2030
Zinfidel
He/Him
Player (206)
Joined: 11/21/2019
Posts: 247
Location: Washington
Whiteted wrote:
This is very good news because I'm just starting work on a bot for a Goldeneye 00 agent full run, and I've found the guards' hitboxes in memory but they are only available briefly before they are overwritten. Sometimes the game syncs up well with the 1/60th frame advances and you just can't read them.
Can you share a general overview of your strategy for finding the hitbox data? I don't mean like a detailed breakdown or anything (unless you really want to). I had the idea of writing a wallhack in Lua to make TASing easier, and the part of the process that I figure would just be horrendously tedious and difficult would be figuring out where hitbox data is stored (and what format it might even be in), so that a bounding box could be drawn around the enemy. For Armored Core I know where each enemy's data structure is, but considering how complex collision functions can be, I doubted that vertices or something like that would be conveniently laying around near health values etc.
Joined: 10/20/2019
Posts: 29
Location: UK
Zinfidel wrote:
Can you share a general overview of your strategy for finding the hitbox data? I don't mean like a detailed breakdown or anything (unless you really want to). I had the idea of writing a wallhack in Lua to make TASing easier, and the part of the process that I figure would just be horrendously tedious and difficult would be figuring out where hitbox data is stored (and what format it might even be in), so that a bounding box could be drawn around the enemy.
Great minds eh :) For sure I can share. I'm a big fan of making paper notes which I number before they leave my desk so it's kinda documented. My method was pretty haphazard tbh because it's the first major bit of reversing I've done on a game, but I've definitely come out of the other side with something more definitive. Also bear in mind that I got pretty lucky. Starting out I had some knowledge of the standard bits of memory courtesy of Wyster which I've extended slightly in my travels, and I'd done some reversing for a mod that I made recently. Crucially I had already made & improved & debugged & remembered-that-lua-is-one-based-for a lua HUD. My plan was to find some kind of boxes and throw it in this to make sense of it. Getting started Unfortunately my 'in' isn't so documented because it was doing reversing in ghidra, so we have to do a little speculation. In the-elite (Goldeneye speedrunning) people call me a TASer but it's not so much actual TASes that I make (until now) but just exploring strategy ideas using emulator. At the end of last year I was fleshing out an old strategy idea which I won't excite you with (TAS here, heartbreaker here). It relies on random shot spread to actually hit the hostage, so I looked into that. I had a neat (though no doubt unoriginal) idea for this. I dumped the rom in virtual memory from Project64, and found all calls to the RNG (I already knew the address from Wyster, so Project64's debugger found the update function). Then I wrote some assembly code to look the RNG's return address up in a table of values at a specific address. If it found a match then it returned the non-random value from the table, else it returned the generated random value. Coupled with a lua script, this lets you take something observable and random (gun spread) and binary search for where it calls the RNG. I quickly found that it made 2 pairs of calls for x and y spread. This debunked my hope that playing in wide/cinema would widen your gun spread. Back on topic though, this gave me a starting point - I had found the code which generated a random shot, so the code for testing collisions shouldn't have been too far away. Bear in mind that I'm by no means an expert at reversing, and I've pretty much only used Ghidra against Goldeneye, so I won't lecture you. The decompiler is very impressive imo, and with 0 effort the output can still be really good. One thing I always bear in mind is not to have too much confidence on the signatures that it gives functions, unless you've gone and looked at it. I just focused on the control flow, and at 7f03b15c there's a function which calls the 'makeRandomShot' function at +0x48, and has 2 main loops near the bottom. Highly suspicious :) In summary: Hook RNG -> Find shot code Project64 scripts From here I started 'hitbox_chasing.js', a Project64 script to hook some of these calls and just shout to say that they'd run. I found a function that ran once for each guard that the shot passed near to.. lower .. a function with a jump table .. lower .. and bingo a function which was clearly doing vectory shit and returning a boolean. The main thing which I'd been following was the shot direction vector. The script was really useful for verifying my reversing, and I found it a really good companion because you might have been looking at various values to do with the leg, but then in 5 seconds you can get those same values printed out for the head instead. Or a different guard. I could shoot a guard in the left forearm and it would say I've hit hitbox #10, and describe a box which is long and thin. This function took 4 things: - some origin offset, always (0,0,0) [Perhaps guards also use this to shoot :) ] - shot direction vector - hit box data - hit box matrix So looking at this function, I could parse the hitbox data. It is just Body part #, X min, X max, Y min, Y max, Z min, Z max. But I still needed to be able to find the matrix. I went back and I tried to piece together what was going on. The Project64 scripting was useful again, but really I was going in the wrong direction. Basically these hitboxes are linked from '0xA-type' 'skeleton objects'. The skeleton objects live in a binary tree structure with backlinks. I realised that they had up to 3 pointers but I was thrown off because the backpointers usually go back many steps, rather than just to the parent. If I had just looked at a sibling of the vectory function, I'd have seen code walking up the skeleton looking for a matrix (on a 0x2-type skeleton object normally). My thinking was that at some point I'd have to look back up the call stack and trace my parameters, but the much clearer code was down low. I gave up and went to sleep :) Sunday The next day I was feeling kinda low because we're in corona lockdown and my corona project seemed a bit stuck. I think I must have put a breakpoint on reading these 3 pointers and I was blessed by finding an amazing function at 7f06f0d0. It literally walks the skeleton, following the pointer at 0x14 until it sees a nullptr, then stepping back on 0x8 and looking for 0xC. I did some skeleton-walking myself, and found that type 0xA signified hitboxes. So I just had to see how it got hold of the skeleton root. Then I noticed it came from a familiar value (the 'model data pointer') that I'd seen in the long stack of calls earlier. I suddenly realised that this is actually kinda familiar, and I knew how to find it from Wyster's "GuardData". The sibling function that I mentioned explained how to find the matrix within a list linked off of this model data pointer. Finishing up So profit. I had the hitboxes, but with 2 issues: (a) The memory that the matrices are written to is reused even before the next frame is drawn (b) The matrices were for the view projection, whereas I also wanted their global coordinates (a) has been 'solved' using lua to hook the end of some matrix multiplication code, looking for writes to guards' matrix lists. Currently I'm getting a crash but the code does work for a little while before it runs out of memory. (b) To get them locally I could use the matrix that my HUD uses, but I've always known that to be a bit of a hack - my HUD lags if you spin fast, and sometimes hops about a bit. My bot is going to use this for godlike aiming so it needs to be good. Instead I noticed that the 15 hitboxes didn't use consecutive indices in the matrix list. I just tried multiplying by the inverse of the unused matrices at [16], [13], [9] and [4], and really liked the look of [4]. It's effect was to fix the centre of the gut hitbox, and the direction that the guard was facing (though his animation might still spin or whatever). I was very fortunate that I could correct both of these. The guard's point position is readable from the GuardData and coincides with the center of the 'gut hitbox', and I just happened to have found guards' facing angles in memory a while back. The result shows guard's hitboxes 2 in-game frames ahead of where the guards are visually, which is pretty neat. As a bonus it reveals the view matrix, which I can use on the shot direction if I like.. or track down more reliably in memory. To come back to lua my worst error was hooking the matrix multiply function, because a 1 was written by the delay slot in the bottom right of this 16 cell square matrix. Not to worry, I'll just do this myself in lua:
Ms[offset][3][3] = 1.0
... The end.
I don't mean like a detailed breakdown or anything (unless you really want to).
It's been nice even just for myself to piece this together and look at my mistakes. Hopefully it's useful for you, and at the very least you can see that I got pretty lucky (with a little help from persistence). I'll upload my code once the events.onmemoryexecute is working properly so I can check it's bug free :)
whiteted#2289 on discord Goldeneye 00a TAS due to be completed circa 2030
Zinfidel
He/Him
Player (206)
Joined: 11/21/2019
Posts: 247
Location: Washington
Damn, thanks for that write-up Whiteted! It was entertaining and informative. Unfortunately you've confirmed that you had to go through exactly the process I was hoping to avoid, namely finding the collision functions and then finding matrices and bounding box vertices that way, which is a really huge amount of work. I figured I could save a lot of time by just using enemy position data instead of bounding boxes, and manually coding in dimensions for their bounding box, but I would still need to reverse the view matrices to get them displayed correctly. I'm very interested in the hook code when/if you manage to post it!
Joined: 10/20/2019
Posts: 29
Location: UK
No problem, well done for making your way to the end of it ;) The body part HUD lua is on my github now. If you want to run it then you also need Wyster's /Data library linked in the readme (though looking at it now it's only for 1 thing). I tried it on 2.4.1 and the bug is still present so expect it to crash pretty quickly. I watched your Armored Core TAS - very impressive :) , for sure only in the vault because of all the mission skips. The enemy models look to be of a similar complexity to GE's guards, though maybe with no movement of the head / neck? Atleast if you want to include the limbs as well then it looks like it won't be easier than GE.
whiteted#2289 on discord Goldeneye 00a TAS due to be completed circa 2030
1 2
11 12 13 14