Show older

so the obvious thing to assume is that the things in that list are 26 bytes each, since we're sticking one in now.

but the code that iterates through the list doesn't seem to make sense if you have 26-byte objects! something weirder is going on

questioning if my 26-byte-malloc is off. perhaps it's more like "allocate packet with header and data" and it's only taking the size of the data in the argument, so it allocates sizeof(header)+data_length

if the objects you stuff into the resource_linked_list collection are 26 bytes long, WHY ARE YOU REFERENCING THE 28th BYTE?
CMP word ptr [ESI + 0x1c ],0x5

0x1C is 28! that's 28! IS THIS JUST A MEMORY BUG IN THE ORIGINAL GAME?

oh fuck, I think this is entirely my fault. I think there's two linked lists that are used separately by this function, but both are related to resources so I named them both basically "resource_linked_list" and didn't notice the function switches between which one it is using

so the second list probably has the 34-byte objects I originally thought this function used.
So it's "ResourceNode" vs "ResourceNode3".

very bad names, I need to change them immediately, but I don't know what to.

I just made them resources_26byte_list and resources_34byte_list

I should find all the global linked lists and systematically find out how big they are.
They always use the same UnsafeNodeGetNext/SafeNodeGetNext functions to deal with them, I can just look up all call sites for those functions and see what linked list they're being used on. then I track where that linked list is used to where someone calls a malloc

eww, the list definitely is heterogeneous. I just realized that the list always has an "end of list" marker that's just an instance of the base LinkedList struct, so it's only 8 bytes (two pointers). So every list has to contain two different types of objects, because some objects are the special end-of-list object.

I'm sure there's some benefit to having an end-of-list marker instead of just having the last object have a next pointer that's NULL, but I'm not sure what it is right now, too tired to think about linked lists in that much detail.

yup found the allocate, it's a 34 after all. Maybe I should just look at this allocator, is this a linked-list only allocator?

the reverse engineer who worked on this before me* has helpfully named the allocator "allocate_something" which doesn't tell me what exactly it allocates.

* that would be me

nah. I went through the 51 calls to allocate_something, but most don't look like they're for linked lists. this is just a generic malloc

oh nasty, this function picks which linked list to use based on a flag.
I sure hope those are compatible types and they aren't just mixing different lists of different types entirely!

one annoying detail is that I'm pretty sure this program also uses pointers to specific items in global linked lists.
which look in my disassembly just like iterating over a full list from a global head pointer.
so if I see two SafeNodeGetNext calls to addresses A and B, I assume those are two different lists. It might just be that B is a specific item in the actual global list A.

their coding style has more than a few safe_frob(ptr) functions that are just identical to unsafe_frob (ptr) by adding an "if (ptr==null) return;" at the top.

over a half hour I checked all 160ish calls to UnsafeNodeGetNext and SafeNodeGetNext, and my finally tally is:
21 global linked lists!
18 unknowns, the font list, and the two resource ones.

motherfuckers loved them some linked lists apparently.

next search: 26 calls to AddToNodeList. presumably it'll get called on each of these near some code to allocate a new node. So I can nail down how big these linked lists are... and hopefully they're consistent enough to make that a meaningful question to ask.

I'm not sure what unknown lists 9,10, and 11 are for, but they're connected together. There's code to move things from 10 to 11, 11 to 9, and 11 to 10.

and I think they're only ever added into 11?

ahh, they've got an add function that looks like this:

void func(LinkedListNode* list, LinkedListNode* node, void *)

Turns out it takes a comparison function, so you can put things into the list at their sported position.

spotted a function an hour and a half ago and decided not to reverse it, but I guessed I might take a 4-character string and turn it into a 4-byte DWORD of it.

finally got around to checking it (by just seeing what goes in and comes out in the debugger), and YEP I was correct

3d Movie Maker does the same thing where they have chunk types that are 4 digit strings, because you can easily pass them around as 32bit values

the game has a hard limit on how many allocations it can do: 500.

that's 500 in use at once. when you free the memory it goes back to the pool

still, 500 is pretty low, especially since they're using linked lists which are one-alloc-per-node. 21 of those lists, in fact.

although hardcoded it's adjustable by changing a line in the main() equivalent. I bet they didn't start with 500 and had to tune it up to bigger numbers

WHAT THE PIXEL ART FUCK IS GOING ON IN THIS SCREENSHOT?

the pixel art itself is inconsistently stretched and then some windows DPI scaling nonsense is blurring the whole thing to make it even worse

The DOS game this is a port of, for comparison.
The lack of DPI stretch is just because it's DOS and I don't have my DOSBox-X misconfigured, but HEY LOOK CONSISTENT PIXEL WIDTHS

this project just added a new stretch goal: unfuck the graphics for the win32 version so you can run this on modern computers without it looking like 7 types of ass

GetAsyncKeyState considers the mouse to be a keyboard?!

oh good and they're "sticky" in a way that only works reliably on windows 16bit

I need to start remembering to check if a function is ever called before I waste 5 minutes reverse engineering it. I know exactly how this GetMouseButtonsDown function works and I've spotted at least one minor bug, but now I just found out it's dead code that's never called

JUST DID IT AGAIN RIGHT AFTER THIS POST

I'm a trusting soul, I assume code is in a binary because it's needed for that binary to run. I'm not so used to these early-90s compilers who leave everything in the C file in the binary

else if ((uMsg != WM_NCACTIVATE) && (uMsg == WM_KEYDOWN)) {

amazing comparison folding here, compiler. so it doesn't match WM_NCACTIVATE, but at the same time it does match WM_KEYDOWN? amazing. almost like you could ignore that first test

hah. It takes all the keypresses from the WM_CHAR/WM_KEYDOWN messages and puts them in a circular buffer.

You know, how keypresses worked on DOS... which this game started as.

WHY DOES IT HAVE A SECOND CIRCULAR KEYBOARD BUFFER

there might be a cheat code no one has found on the capslock key but I can't test it because I don't have a capslock key

Sign in to participate in the conversation
Pixietown

Small server part of the pixie.town infrastructure. Registration is closed.