SCRMaps
/
Sign in

The 222 triggers that run when the map starts

A compiled EUD map's trigger list is not empty - it holds a heap of identical-looking SetMemory triggers, and they are the loader.

11 min read

We have seen that an EUD map's code goes into the STR section. The TRIG section is not quite empty, though: every eudplib map carries much the same bundle of about 222 triggers, nearly all of them SetMemory ... Add. They are not the map's logic - they are its loader.

The first part asks whether this is an environment where EUD works at all, and the way it asks is neat.

SetMemory(0x58A364, SetTo, 1);                        // P1 마린 데스 := 1
if (Deaths(P1, Exactly, 0, "Terran Marine")) Draw();  // 여전히 0이면 무승부
0x58A364 is the first cell of the death table - P1's Marine deaths. The write and the condition are the same cell reached two different ways. If raw memory writes work the count is 1 and the condition is false; if they do not, it is still 0, and rather than run a payload it cannot run, the map draws the game.

Two copies of a malformed Condition(0,0,0,0,0,115,0,0) follow. Under 1.16.1 executing that halted trigger execution on the spot - a nail driven in so nothing goes further.

The second part is the heart of the arrangement. The code is in STR, and STR is allocated at run time, so its address is unknown at compile time. MRGN - the location table - sits at a fixed 0x58DC60. So eudplib writes two triggers into the MRGN section itself.

This is why a compiled EUD map's location list is full of garbage. Nothing is hidden: those bytes are trigger bytecode, not locations.

The two triggers in MRGN
PRTAdds payload address / 4 to every field that holds an EPD.
ORTAdds the payload address to every field that holds a pointer.

The third part is the one with the most to learn from. The address to add sits at 0x5993D4, the pointer to the map's STR section. But a trigger action can only add a constant, never a variable - so the value is taken apart one bit at a time.

if (Memory(0x5993D4, AtLeast, 0x40000000)) {
    // PRT 자리 15개에 0x10000000 더하고   (÷4)
    // ORT 자리 15개에 0x40000000 더한 다음
    SetMemory(0x5993D4, Subtract, 0x40000000);
}
if (Memory(0x5993D4, AtLeast, 0x20000000)) { /* ... */ }   // 그 다음 비트
// ... 1까지 계속
One trigger per bit: ask about the top remaining bit, add the matching constant to both tables, subtract it from the running value. Thirty or so passes consume the whole address. The PRT constant is always a quarter of the ORT one - the EPD-versus-pointer distinction, again.

The shape is worth recognising. “Add a value I cannot know at compile time” is solved in classic triggers by binary decomposition, one trigger per bit, and the same shape turns up in save-code readers and in any hand-written relocation.

It is also worth knowing where the edge of EUD's reach is. Ordinary EUD reads and writes the data region allocated when the game starts; one narrow channel into StarCraft.exe's own image has been found as well.

It works because Wait does not range-check CurrentPlayer. The wait timers sit at StarCraft.exe + 0xDB8714, four bytes each for eight slots, so setting CurrentPlayer to 0xFFFFFFFF and calling Wait writes into the slot before them - a region ordinary EUD cannot touch at all, holding things like the screen tint and text line spacing.

Look at the limits before reaching for it. Each address can be written once - there is a one-byte lock per slot and out of range it never clears - and that lock byte is itself live data somewhere, so the write can crash. There is no read, no add, only Set, and it works on 32-bit clients only. And a write into a region the game does not re-initialise survives the game: the worked example turns the screen green and warns that forgetting to restore it leaves the lobby green too.

Everything is a trigger
Sign in to track your progress