Ok, so guns and bullets *DO* have vector tables! I know, it's surprising! Thing is, though, they aren't like the NPC table, where it's a bunch of function pointers. They're jump tables - my best guess is that the compiler optimized a case somewhere (hence the invalid value checks)
48F8C0+(eax*4) : effects according to compendium, yet 4047B0 is (very) indirectly called by it? EDIT: Ok, wait, that's a *return* address. So it seems there's a bit of pipelining going on here. 41056E is the call to the AB50 "handle effect" code. The previous thing it calls is 105AB, which is... the bullet code???
Bad news. If you're using Windows XP, N.I.C.E doesn't seem to like it. I have no idea why - it just *REFUSES* to load game.dll, not giving any reason - and without game.dll using N.I.C.E. becomes impossible. My debugging is continuing, and I hope GetLastError will return a good result... but given Windows's attitude to error messages (undescriptive), I am not optimistic about the likely results.
PSA to Cave Story modders who use The GIMP and Wine during development: They're colluding! The GIMP produces incompatible BMPs... which Cave Story on Wine accepts. And Cave Story on "real" Windows does not. Thanks to zxin for alerting me to this.
GO TO /FORUMS/ DO NOT PRESS HOME DEFINITELY COLLECT 200 MEMES (EDIT3: Inspired by my tendency to click the "Home" button and end up on the entirely wrong part of the site instead of clicking the obvious and moving sky dragon.)
05AB is a routine to handle *every bullet on the screen*.
AB50 is a routine to handle *every effect on the screen*.
A bullet is 0x80 bytes long, or a *shift left of 7* - this is important because the compiler optimized what usually shows up as obvious imuls into SHL 7.
You can learn the bullet length from the compendium, the important part is that from there you find out how the loop is set up.
The compendium lists the addresses of the *first bullet*. This doesn't quite make sense till you see the actual code, and then it's really useful for translation and understanding.
(Yes, I'm using this as a journal as I go through the code)
Next, if ShotID - 1 is > 0x2C, it jumps to a bouncepad which then jumps back to the start of the loop (as if the bullet didn't exist). This code is at 8FFD. This prevents having more than the maximum amount of bullet types - any bullet hook will be placed *there*, since the code is more or less useless (and easily emulated in Lua without using up precious ASMspace). (The bullet disambiguation will get put in C-space anyway, and worst case scenario the InUse flag check can be pulled in for a total of ~72 bytes.)