Next thing I’m trying to make sense of is the mesh format. This one is going to be difficult.
Some background to begin with. A 3D model needs to define, at a minimum, a set of polygons defined by three or more coplanar vertices, each of which will have an x, y, and z coordinate. It may also include normal mapping and UV data for each face, though I’m not sure if Earth 2150’s do. I also know that they will have references to texture files embedded in them, as the parameters file didn’t include those, only mesh files.
With that in mind, looking for triplets of values is the order of the day. I also know from other files that there will most likely be an 8-byte magic string at the start of the file to identify the format, followed by some additional headers that may include some sort of type code. The actual values themselves will be 32-bit words. Whether these will be integers or floating point numbers is as yet unknown, but I’d expect floats. I also expect some sort of animation data in here, though not for every model; Earth 2150 is quite an early 3D game so a lot of models are just static. Builders, for example, have animations for building and digging; most tanks don’t other than their rotating turrets, which I believe are handled by placing the turret model on top and just rotating that.
Fun with strings and headers
As is becoming my habit, I start with the LC Moon unit’s model. The file begins with the string
MESH and the number 1, which I was pretty much expecting. A quick scroll through shows
Textures\LCUMO3.tex at offset
Textures\SIDECOLOR.tex at offset
15B4, so I was right about the texture references. I also see something peculiar down at
110D: the characters ! “ # $ % & ‘ ( ) _ and so on all fairly near each other; I don’t think this is an actual string, but rather something with a steadily increasing number.
No other strings present that I can identify, so it’s all binary from here on in.
Past the 8-byte file identifier, the next field is a 32-bit integer that’s either 0 or 1. Possibly a type identifier, if other files are anything to go by.
Following that, from others’ work I know that the first two of the next four bytes are the model’s footprint. These 16 bits represent a 4x4 area, and (at least for buildings) any 1 bits indicate that that square is occupied by the model. Interestingly, units have a value of
00 80, a single square. Maybe multi-tile units were planned at one point?
Next up, there’s a 32-bit block that’s 0 for our friend the Moon, but seems to be made of bitmasked values in other files, though it’s not yet clear what they mean.
The next word is always zero.
What is a model? A miserable little pile of triplets. But enough talk, have at you!
Next I find myself looking at a triplet. That’s a string of 12 (or multiples of 12) bytes without much obvious pattern to them. The first one is at offset
18, right after the header fields I mentioned above. The triplet itself is
8B6CE7BD 6F1203BB CDCCCC3D. That doesn’t look like integers, so I’m guessing these are 32-bit floats. One quick conversion later, and that tells me those are the floating point values (-0.11299999803304672, -0.0020000000949949026, 0.10000000149011612), which are a pretty close approximation to (-0.113, -0.002, 0.100) plus rounding errors. Nice, that’s a 3D coordinate not far from the origin.
That’s followed by a long string of zeros; 36 to be exact. Three more triplets? On a hunch, I load up the model for the LC Crusher which has two turrets. That has two non-zero triplets starting at offset
18. These might be turret attachment coordinates. One more data point, the LC Main Base, which has four turrets. That’s a solid 48-byte block of nonzero values. Right, bytes
47 are turret attachment points.
This is followed by a 96-byte (mostly) nonzero block starting at offset
48, that’ll be 8 more triplets. These read out as
(0.348, 0.129, 0.028) (0.549, 0.549, 1.000) (1.674, 0.000, 0.394) (0.658, 0.002, 1.000) (0.351, -0.131, 0.028) (0.549, 0.549, 1.000) (1.674, 0.000, 0.394) (0.658, 0.002, 1.000)
There are two repeats in there; the second, third and fourth coordinates are repeated as the sixth, seventh, and eighth. The Crusher shows a similar pattern with slightly different values. I’m not sure what these mean, but it’s harder to get a sense of what’s going on from vertex coordinates than it is from RGB colour values. I’m going to need a renderer.
Half an hour of learning the basics of Panda3D later, and I’ve got the five unique points here rendering.
This… doesn’t look much like anything, even when I rotate it a bit. Obviously these aren’t just simple vertices.
I decide to compare it to some other model files, see what I’ve got here. The Lunar (
LCULU1.msh) only has 48 bytes of floats here, exactly half as many. The LC Main Base (
LCBBF.msh) has 192, twice as many as the Moon. So, what does the Lunar have one of, the Moon have two of, and the Main Base have four of?
Let there be light!
So what happens if I try zeroing the second block of 12 floats on the Moon’s model and loading it into the game?
Excuse me, ma’am, but do you know why I pulled you over? One of your lights is out.
All right. Time to systematically break this down and see what each part of this does. Since I can fairly easily change values and see their effects in game, I start copying bits from another model onto our poor, abused Moon and seeing what they change.
First three floats are the coordinates of the light source. Since that’s the only thing that changes between the Moon’s two original lights, this is what I expected.
Next three seem to be colour, based on how the light is white when I replace these values with those from a UCS unit. I’m guessing these are R G B, since the values 0.55,0.55,1.0 give us the LC’s blue lights. Of course, I couldn’t resist being silly with this. I give you Disco Phobos!
I think one of the remaining two triplets should logically be which direction the lights are facing. I’m not sure which it is, but my gut feeling is that it’s the fourth one, just looking at the values. So I try tweaking those - if I flip two of the coordinates, the light should point at a right angle to where it was before.
Not exactly what I thought would happen, but close enough. That just leaves the third triplet. I’ve already found origin coordinates, colour, and direction, so I’m not sure what else there is to define. So I try copying values over from the main base, and…
Huh. Are all six floats for direction? I was assuming it was a simple set of Euler angles, but there’s something else going on here. I’ll come back to this later.
Moving on, we’ve got a big chunk of zeroes extending from offset
108 all the way to offset
178, so 112 bytes of them, or 28 words. Potentially space for another two light definitions. I can’t think of any building or unit with more than four lights on it, but that doesn’t mean there aren’t any, so I write a quick script to check all the mesh files for data in this block.
The only ones that do are the LC mine and its transporter variant. Their lights are a bit unusual; there are no visible light cones, but they do emit a fairly dim glow in all directions.
The data here is
4E 62 80 3F 19 04 76 3F 66 66 06 3F 44 8B 0C 3F 44 8B 0C 3F 00 00 80 3F 00 00 80 3F which is 7 floats. Whatever this is, it’s in a different format to what came before. The values are: 1.003, 0.961, 0.525, 0.555, 0.555, 1.0, 1.0.
The 0.55,0.55,1.0 pattern is familiar, that’s LC blue lights, so the third through sixth floats are likely colour. That points to the first three being point of origin coordinates, as with the others.
Thinking that last float might be intensity, I try bumping it up to 5.0, load up the game, and…
That’s definitely light intensity, yes.
Going back to the earlier light definitions in other files, I’m thinking maybe I misjudged the last six floats. I quickly verify that the float following the colour triple is intensity for those too, which leaves up to five for direction. Probably three if it’s using Euler angles, four if it’s using quaternions, leaving one or two that are something else.
I take another look at the seventh word in the hex editor, and see that I’ve made a mistake interpreting it as a float. It got rounded off to 0 when I did the conversion, but it’s actually
F9 00 00 00, which is an integer, not a float. Specifically, the number 249. That seems a bit off. If it’s an array of signed single bytes, it could also be [-7, 0, 0, 0]. Not sure what that might represent, but it varies considerably from light to light, unit to unit, but always just that first byte.
I initially assume that the next three words will be the light’s Euler angles, but they surprise me. The first one turns out to control the tightness of the beam, with larger numbers focusing the light beam further away.
The next two logically should do something with angle, then, but changing them doesn’t produce any visible effects that I can pick out. Will come back to those, I suppose.
I try playing with a few values for the last one, and quickly determine that it’s also an intensity value. My best guess is that the first intensity value is for the diffuse light emitted by the point source, the second being for the spot cast out on the ground.
So, with lights at least partially figured out, there’s a block of zeroes running from offsets
178. None of the game’s mesh files have anything in those 84 bytes, so they remain a total mystery. Padding or unused feature? No real way to know.
From others’ work, I know that the vertex data proper begins at offset
374 with three ints before it providing metadata, starting at
368. So, from
368, there’s 496 bytes of additional data.
This section is broken up into at least three sections. From
1B7, there are a few odd single-byte values here and there amid a sea of zero bytes. Then, from
1DA, there are a few more odd values amid a load of
FF bytes. Then from
368, by far the largest of the sections, there are a few two-byte values scattered through a repeating pattern of
This pattern is broadly consistent across various model files, with the offsets being the same throughout, though the number of bytes that stand out from the background patterns varies from mesh to mesh. Maybe I’m just tired, but this makes no sense to me at all. I’m coming back to this another time.
Posts in this series:
- Reverse Engineering Earth 2150, Part 1: File Structure
- Reverse Engineering Earth 2150, Part 2: Data Types
- Reverse Engineering Earth 2150, Part 3: Vehicle Data
- Reverse Engineering Earth 2150, Part 4: PAR Decompiler
- Reverse Engineering Earth 2150, Part 5: PAR Compiler
- Reverse Engineering Earth 2150, Part 6: Texture Files
- Reverse Engineering Earth 2150, Part 7: Mesh Files