Staredit Network > Forums > Modding Discussion > Topic: Stuff that is wrong with Py suite
Stuff that is wrong with Py suite
Sep 9 2026, 7:46 am
By: NimoStar  

Sep 9 2026, 7:46 am NimoStar Post #1



I will post pyDAT errors and corrections as I discover it. In principle only.

Others should do the code commits if they want to...


Quote
PyDAT correction: portdata.dat fidget animation fields are mislabeled

While investigating why the Dropship portrait would only ever play fid00 and fid01, I traced the portrait-selection code in StarCraft 1.16.1.

The result is that this is primarily a PyDAT field-labeling problem.

PyDAT currently presents the portrait entry approximately as:

Code
Idle Portrait
Talking Portrait
Idle SMK Change
Talking SMK Change
Idle Unknown
Talking Unknown


The last four fields are misleadingly named.

They are not separate "idle change", "talking change", and unknown parameters.

For the portrait fidget selector, StarCraft reads all four of those byte fields consecutively as the probability weights for:

Code
fid00
fid01
fid02
fid03


In other words, the current PyDAT labels:

Code
Idle SMK Change       -> probability/weight of fid00
Talking SMK Change    -> probability/weight of fid01
Idle Unknown          -> probability/weight of fid02
Talking Unknown       -> probability/weight of fid03


A more accurate PyDAT representation would therefore be something like:

Code
Fidget 0 Chance
Fidget 1 Chance
Fidget 2 Chance
Fidget 3 Chance


or:

Code
fid00 Weight
fid01 Weight
fid02 Weight
fid03 Weight


This distinction is important because the existing labels strongly suggest that:
  • "Idle SMK Change" controls idle portrait changes.
  • "Talking SMK Change" controls talking portrait changes.
  • The last two bytes are unidentified values.

That is not how StarCraft actually uses them.

The executable's portrait fidget routine is around 0x44EB90.

It obtains a random value, then processes these four portdata.dat bytes cumulatively. Each byte contributes the probability range belonging to one of the four possible fidget animations.

Conceptually, the logic is approximately:

Code
roll random value

if roll falls inside weight 0:
   use fid00

else if roll falls inside weight 0 + weight 1:
   use fid01

else if roll falls inside weight 0 + weight 1 + weight 2:
   use fid02

else if roll falls inside weight 0 + weight 1 + weight 2 + weight 3:
   use fid03


The corresponding runtime portdata.dat arrays are at:

Code
0x655E10
0x6560A8
0x656038
0x656118


So the ordering in the DAT is unambiguously:

Code
byte 0 -> fid00
byte 1 -> fid01
byte 2 -> fid02
byte 3 -> fid03


This explains the Dropship behavior.

If one edits the portrait in PyDAT according to the current field descriptions, it is natural to modify only "Idle SMK Change" and perhaps "Talking SMK Change".

Doing that only gives probability to:

Code
fid00
fid01


If the two "Unknown" values remain zero, then:

Code
fid02 = 0 chance
fid03 = 0 chance


and those animations will consequently never be selected.

This can easily look like a StarCraft hardcoded limitation to two fidget animations, but it is not.

StarCraft 1.16.1 explicitly supports four portrait fidget animations:

Code
fid00.smk
fid01.smk
fid02.smk
fid03.smk


The filename-generation/loading code is also capable of requesting all four.

There is additionally a fallback mechanism: if a requested fidXX file cannot be opened, StarCraft tries lower-numbered fidget files. So a missing fid02/fid03 can also make the problem less obvious by causing an earlier animation to appear instead.

For example, to give all four fidget animations approximately equal probability, the portdata.dat entry can use:

Code
fid00 Weight = 25
fid01 Weight = 25
fid02 Weight = 25
fid03 Weight = 25


Which, using PyDAT's current labels, means:

Code
Idle SMK Change       = 25
Talking SMK Change    = 25
Idle Unknown          = 25
Talking Unknown       = 25


The important correction for PyDAT is therefore:

Current:

Code
Idle SMK Change
Talking SMK Change
Idle Unknown
Talking Unknown


Should represent:

Code
fid00 Weight
fid01 Weight
fid02 Weight
fid03 Weight


The words "Idle" and "Talking" in the current four field names are especially problematic, because all four values participate in selection of the idle/fidget fidXX portrait animations.

The talking portrait SMKs are handled separately and should not be inferred from these labels.

So this is not merely a case where two fields are still unknown: all four fields have a coherent known function, and PyDAT's current descriptions are incorrect.




Ultimate StarCraft: Age of Darkness stand-alone no-cd download (no false positives)

Sep 9 2026, 2:05 pm DarkenedFantasies Post #2

Roy's Secret Service

I provide a reasonably accurate description of the DAT files and what each field does here.




Sep 9 2026, 8:29 pm NimoStar Post #3



While the description there is indeed reasonably correct (yet less detailed) , it isn't how PyDAT implements it or describes it.


Another one related to units.dat this time (see attached image, also replicated at the bottom to know what is the part I'm talking about):


Quote
PyDAT audit: the "Unknown Flags" field in units.dat is not behaving as eight independent flags

As part of the ongoing effort to audit, improve, and where necessary correct PyDAT's representation of StarCraft's DAT files, I traced the field PyDAT currently exposes as:

Code
Other Properties:
0x01 0x02 0x04 0x08
0x10 0x20 0x40 0x80


This is the byte called unknown_flags in PyMS/PyDAT's units.dat implementation.

The important finding is that, at least in StarCraft 1.16.1, the executable does not appear to treat these as eight independently meaningful boolean properties.

Instead, this is fundamentally a single 8-bit unit property, and the confirmed engine code compares the entire byte against the specific value 0xC1.

That makes the current PyDAT UI somewhat misleading.

1. Where the field actually exists in units.dat

The units.dat loader uses a descriptor table beginning around:

Code
00513C30


The relevant entries are:

Code
Field  Destination  Size  Count  units.dat offset

0      006644F8      1    228    0000   Graphics
1      006607C0      2    228    00E4   Subunit 1
2      00660C38      2    228    02AC   Subunit 2
3      00664980      2     96    0474   Infestation
4      006610B0      4    228    0534   Construction image
5      006605F0      1    228    08C4   Start direction
6      006647B0      1    228    09A8   Has shields
7      00660E00      2    228    0A8C   Shields
8      00662350      4    228    0C54   Hit points
9      00663150      1    228    0FE4   Elevation
10     00660FC8      1    228    10C8   <-- PyDAT "Unknown Flags"
11     00663DD0      1    228    11AC   Rank / sublabel


Therefore, for unit ID n:

Code
units.dat file offset:
0x10C8 + n

runtime array:
0x00660FC8 + n


The field is exactly one byte per unit.

The generic DAT loader does not interpret that byte. It simply copies it into the runtime array.

Relevant loader code:

Code
0044F0F9  push 00504114        ; "arr\units.dat"
0044F0FE  mov  eax,00513C30    ; DAT descriptor table
0044F103  call 004D2E80


Inside the generic DAT loader:

Code
4D2EAE  mov edi,[ebx]          ; destination

4D2EC0  mov edx,[ebx+08]       ; entry count
4D2EC3  imul edx,[ebx+04]      ; count * element size

4D2ED3  rep movsd
4D2EDA  rep movsb

4D2EE4  add ebx,0C             ; next descriptor
4D2EEC  add esi,edx            ; next DAT section


So there is no bit interpretation during loading.

2. The important part: how the game actually reads it

Searching the executable for runtime accesses to:

Code
00660FC8


produces two especially important gameplay consumers.

In both cases, StarCraft compares the whole byte against:

Code
0xC1


It does not test the supposed 0x01, 0x40, or 0x80 properties independently.

3. Spider Mine targeting

One consumer appears in the Spider Mine target-selection predicate.

Relevant code:

Code
440EE6  mov eax,[ecx+DC]
440EEC  test eax,04000000
440EF1  jne  440F0A

440EF3  test al,06
440EF5  jne  440F0A

440EF7  movzx ecx,word ptr [ecx+64]  ; unit ID

440EFB  cmp byte ptr [ecx+660FC8],C1
440F02  je  440F0A

440F04  mov eax,1
440F09  ret

440F0A  xor eax,eax
440F0C  ret


The important instruction is:

Code
cmp byte ptr [ecx+660FC8], C1


Not:

Code
test [...],80


Not:

Code
test [...],40


Not:

Code
test [...],01


The value must equal 0xC1 exactly.

Tracing the caller shows this routine is used as a nearby-unit search predicate:

Code
44127D  push esi
44127E  push 00440EC0
441283  mov  edx,esi
441285  call 00476000
...
441293  call 004E87E0


This eventually leads into the Spider Mine's main order routine.

Therefore one confirmed meaning of this units.dat byte is:

Quote
If the value is exactly 0xC1, that unit is rejected as a Spider Mine acquisition target.

This appears to be the real implementation behind the traditional "hovering / mine-safe" interpretation associated with this field.

It is worth being precise here: this prevents the unit from satisfying the mine's target acquisition test. It does not necessarily imply immunity to damage from a mine that was triggered by something else.

4. Zerg Birth also checks exactly 0xC1

The other direct consumer occurs in the Zerg Birth positioning logic:

Code
45DE06  movzx ecx,word ptr [edi+64]  ; unit ID
45DE0A  mov   dl,[ecx+660FC8]

45DE10  xor eax,eax
45DE12  cmp dl,C1
45DE15  jne 45DE1E

45DE17  mov eax,FFFFFFF9             ; -7
45DE1C  jmp 45DE2C

45DE1E  test byte ptr [edi+DC],04
45DE25  je   45DE2C

45DE27  mov eax,FFFFFFD6             ; -42

45DE2C  add eax,esi
45DE2E  mov [ebp-0E],ax


This logic is effectively:

Code
if (units_dat_field == 0xC1)
   yOffset = -7;
else if (unit_is_air)
   yOffset = -42;
else
   yOffset = 0;


Again:

Code
cmp dl,C1


The engine is recognizing a specific class/value, not independently checking the bits that happen to compose that value.

5. Why this matters for PyDAT's checkbox representation

PyDAT currently exposes the byte as:

Code
0x01
0x02
0x04
0x08
0x10
0x20
0x40
0x80


That is technically a valid way of editing an arbitrary byte.

However, it visually suggests something much stronger:

Quote
Each checkbox represents an independent engine flag.

The disassembly does not support that interpretation for the confirmed consumers.

For example:

Code
0xC1 = 0x80 | 0x40 | 0x01


So checking:

Code
0x01
0x40
0x80


produces the recognized value:

Code
0xC1


But checking only:

Code
0x01
0x40


produces:

Code
0x41


And 0x41 does not pass either of the confirmed 0xC1 tests.

Likewise:

Code
0x80 -> not 0xC1
0x40 -> not 0xC1
0x01 -> not 0xC1
0xC0 -> not 0xC1
0x41 -> not 0xC1
0xC3 -> not 0xC1


There is an especially important consequence for experimentation with the currently "unknown" bits.

Suppose a unit currently has:

Code
0xC1


and we enable PyDAT's:

Code
0x20


The result becomes:

Code
0xE1


The confirmed engine checks are equality comparisons, so:

Code
0xE1 != 0xC1


That means adding 0x20 does not simply "add an unknown ability" while retaining the 0xC1 behavior.

For these code paths, it actually removes the special behavior.

This strongly argues against describing these eight bits as eight independent unit properties.

6. PyDAT itself does not currently know their semantics

This is also consistent with PyMS's implementation.

The field is effectively represented as:

Code
unknown_flags


with eight unnamed bits.

In other words, PyDAT is not currently claiming internally that:

Code
0x01 = X
0x02 = Y
0x04 = Z
...


The problem is primarily one of UI presentation and inherited terminology.

A checkbox-based flag editor naturally encourages users to assume that each bit is independently consumed by the game.

The executable evidence currently points instead toward a byte-sized movement/floating classification value.

7. Map unit settings can overwrite this value

Another important part of the pipeline is custom unit data embedded in maps.

The CHK parser contains handlers for:

Code
UNIS
UNIx


and those handlers copy custom unit values into the same runtime array.

Relevant writes include:

Code
4CABE7  mov WORD PTR [eax+660FC8],dx   ; UNIS

4CADA7  mov WORD PTR [eax+660FC8],dx   ; UNIx


The word write processes two adjacent byte entries at once.

So the actual data pipeline is:

Code
arr\units.dat
     |
     v
DAT loader
     |
     v
00660FC8[228]
     |
     +---- possibly overwritten by UNIS / UNIx
     |
     v
gameplay code


This is important when testing the property, since a map using custom unit settings can override the value from the MPQ units.dat.

8. Suggested PyDAT correction

I think the current representation should be changed.

Instead of presenting this primarily as:

Code
Unknown Flags:
[ ] 0x01
[ ] 0x02
[ ] 0x04
[ ] 0x08
[ ] 0x10
[ ] 0x20
[ ] 0x40
[ ] 0x80


I would suggest something closer to:

Code
Movement / Floating Class: 0xC1

Known value:
0xC1 = Floating / Spider-Mine-safe class

Raw bits:
[x] 0x01
[ ] 0x02
[ ] 0x04
[ ] 0x08
[ ] 0x10
[ ] 0x20
[x] 0x40
[x] 0x80


The raw bit checkboxes can still be useful for research and direct DAT editing.

But they should be visually secondary to the fact that the underlying property is one byte, and that the currently confirmed engine behavior recognizes 0xC1 as a complete value.

Something like:

Code
Movement/Floating Class


would probably be a better working name than:

Code
Unknown Flags


until every possible value and consumer is fully mapped.

9. Current confirmed findings

At this point I would consider the following directly established from the executable:
  • units.dat field 0x0A is one byte per unit.
  • Its runtime array begins at 0x00660FC8.
  • The DAT loader performs no per-bit interpretation.
  • Spider Mine target selection compares the entire byte to 0xC1.
  • Zerg Birth positioning also compares the entire byte to 0xC1.
  • Those consumers do not independently test 0x01, 0x40, or 0x80.
  • Changing any bit of 0xC1 causes those exact equality tests to stop matching.
  • UNIS/UNIx custom unit settings can overwrite the runtime value.

What is not yet established is whether every theoretically possible value of the byte has some other consumer elsewhere, or whether some individual bits are interpreted indirectly by code that does not reference the array in an obvious absolute-address form. This sounds unlikely, however.

I would not go as far as declaring every other value permanently meaningless.

I would say is that the existing "eight unknown flags" model has no support from the consumers traced so far, while a single movement/floating class byte model fits the actual machine code substantially better.

This is therefore a good candidate for a PyDAT UI/documentation correction rather than simply assigning speculative names to eight checkboxes.


Attachments:



Ultimate StarCraft: Age of Darkness stand-alone no-cd download (no false positives)

Sep 9 2026, 10:10 pm Heinermann Post #4

memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes memes

Can confirm the first one, good info. Will be able to label these correctly now.

The "Unknown Flags" in units.dat is called gubUnitMoveClass in the EUD PDF.




Yesterday, 3:21 am NimoStar Post #5



GOod. I'm glad it will be of use to fix something.

Also to take into account:

Talking portraits are handled by a completely separate selector from the fidXX animations, and this reveals another useful PyDAT clarification.

In your executable, the talking selector begins at approximately 0x44EC90.

StarCraft supports exactly three talking portrait files in this routine:
Quote
tlk00.smk
tlk01.smk
tlk02.smk

tlk03.smk is not reachable through the normal 1.16.1 talking selector. The limit is explicitly hardcoded:
Quote
0044ECA3 mov ecx, 3
0044ECA8 idiv ecx

The random-number remainder therefore gives only 0, 1, or 2.

This matches ordinary SC portrait packages, which normally provide three tlk files even when they provide four fid files. For example, the standard-style portrait sets documented in the modding community contain fid00–fid03 but only tlk00–tlk02.

More importantly for the PyDAT correction: the four byte fields we identified earlier have nothing to do with choosing the talking animation. [thus it was mislabeled as we have seen]

Talking animation selection is entirely hardcoded.

The selection algorithm is also more interesting than simple random choice. Approximately:
Quote
candidate = random() % 3

if candidate == previous_talking_animation:
candidate = (candidate + 2) % 3

try tlk[candidate]
So StarCraft deliberately avoids immediately playing the same talking animation twice.

Post has been edited 1 time(s), last time on Yesterday, 3:40 am by NimoStar.



Ultimate StarCraft: Age of Darkness stand-alone no-cd download (no false positives)

Options
  Back to forum
Please log in to reply to this topic or to report it.
Members in this topic: None.
[02:29 am]
Symmetry -- TFW you google a problem and find the answer in a post you made yourself
[01:46 am]
Oh_Man -- How can i change my name here to put [TFE] on the end will thr website let me
[06:02 pm]
Zoan -- of course it isn't full understanding. You could ask "is the mechanism by which this counterexample blows up the ONLY mechanism possible for an initial configuration to blow up?"
[05:48 pm]
Zoan -- ppl do it all the time in PDEs
[05:47 pm]
Zoan -- Heinermann
Heinermann shouted: it also didn't solve that problem but hey as long as the investors are pleased
Constructing a counterexample is answering the question: "Is Navier Stokes well posed?" in the negative
[05:44 pm]
NudeRaider -- I only heard about it, didn't any news article, so what did it do then?
[05:43 pm]
Heinermann -- it also didn't solve that problem but hey as long as the investors are pleased
[05:39 pm]
NudeRaider -- I have no idea tbh. All I know no human was ever able to solve it, but there's at least a few that beat portal ;)
[05:32 pm]
Zoan -- Navier Stokes is not that esoteric I thought
[04:32 pm]
NudeRaider -- Zoan
Zoan shouted: I find it kind of sad that on the same day it was announced AI solved Navier Stokes, it was also announced AI beat Portal, and there's a good deal of people who don't know which is more impressive
you find it sad not everyone is a math nerd?
Please log in to shout.


Members Online: anoeth47