Does the order of the lines matter, or are you looking just for individual line matches between the files?
You don't get better if you don't practice. :P
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?
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.
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
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)
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.
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.
Joined: 4/17/2010
Posts: 11480
Location: Lake Chargoggagoggmanchauggagoggchaubunagungamaugg
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.
Joined: 4/7/2015
Posts: 331
Location: Porto Alegre, RS, Brazil
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
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
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'))
From the ARM Manual:
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?
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 creditsI will finish this ACE soon as possible
(or will I?)
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?
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 reshighrepresents 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 creditsI will finish this ACE soon as possible
(or will I?)
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?
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.
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():
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
Joined: 11/21/2019
Posts: 247
Location: Washington
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.
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.
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
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!
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