SCRMaps
/
Sign in

Three ways to read the struct wrong

The same bytes mean different things per unit kind, a flag is not a number - and the padding is a free per-unit variable.

9 min read

CUnit reads by member name, so it looks like an ordinary struct. One range of it is not, and a unit loop written without knowing that compiles, never crashes, and reads the wrong value forever.

The 32 bytes from 0xC0 to 0xDF are a union: the same bytes overlaid six ways, and which reading is right depends on what kind of unit it is. Nothing inside the struct says which.

All of these are 0xC0
벌처spiderMineCount - how many mines are left.
캐리어 · 리버inHangarChild - the first unit inside the hangar.
인터셉터 · 스캐럽parent - the unit it came out of.
건물addon - the attached add-on.
일꾼powerup - whatever it is carrying.
비콘its own use.

So a loop over every unit that reads unit.addon reads garbage for everything that is not a building - a worker's powerup pointer, a Carrier's hangar child. Filter on the unit type, or on the IsBuilding status flag, before touching anything in this range. The compiler will not stop you: the members all hang off the same class and it has no idea which unit you are holding.

The union ends at 0xDC. statusFlags and the timers after it mean the same thing on every unit again.

The second is flags. A flag member can be one bit or several, and it does not read or write like a number.

How a flag member behaves
읽기1 only when every bit in the mask is set, otherwise 0.
0 대입Clears every bit in the mask.
0이 아닌 값 대입Sets every bit in the mask.

movementFlags.HoverUnit is three bits (0xC1), which makes the point: there is no way to read back a partly-set state, and you cannot store a number in a flag. When you need the raw bits, use check_status_flag(value, mask) and set_status_flag(value, mask), which take a mask and compare or assign that region exactly.

The third is not an error but a missed opportunity. The struct has several holes left by alignment, and shipped maps use them as a per-unit variable - 1,700 of them, with no array and no lookup.

The holes
0x26 (1바이트)Between flingyID and flingyMovementType.
0x52 (2바이트)Alignment padding after orderUnitType.
0x66 (2바이트)Alignment padding after unitType - the one most often used.
0x86 · 0x8C · 0x106 (1~2바이트)Fields nobody has named.
0x125 (1바이트)unusedTimer - possibly meant for afterburner or the Ultralisk roar.

“unknown” means unknown to whoever named it, not proven unused. Test the one you pick with the unit types you actually spawn. The names are eudplib's, too, so another eudplib may not have them.

To give a hole a readable name, you cannot assign a member onto CUnit - the class has __slots__. Subclass it in a .py file and declare Member(0x66, MemberKind.WORD).

Structure offsets
Sign in to track your progress