[Jongware]
Most recent update (All By Hand(TM)): 30-Dec-2006 19:23

 

Oh yeah I used to know Quentin
He's a real- he's a real jerk

Mr Oizo, Flat Beat

 

The Frontier Galaxy VIII: The Flat Facts

To see the difference between even the most advanced flat shading and the simplest texture mapping, just look at the computer images in the 1984 movie "The Last Starfighter". The CGI were produced on a Cray X-MP supercomputer, and it reportedly took about 4 minutes to render each frame. Nice, huh? Now look at Frontier. Fewer triangles, no advanced shading (just flat, using a 4-4-4 bit RGB palette) but they are textured. Better? (Aww, come on. Say "Better!")

 

The whole difference is that the world you see around you is not flat. It takes a large number of triangles before you can see anything like grains in a non-textured image, and you can get a same effect with just a single textured rectangle. Despite Frontier being a true 3D game, it uses flat images a lot. For example, in modern games the console is rendered in 3D because you can use all the different screen resolutions you want, and because, well, with modern hardware it is just slightly slower than blitting an image to the screen (and an additional trick to speed it up is to render the console once into a buffer and then render that one). For Frontier the console is just a bitmap somewhere in the main executable program; on-off buttons are separate bitmaps, so is the font, and so are the textures on the 3D models.

The Image List

In FE2 the graphics were scattered all around the program, and no doubt the programmers learned from that the hard way. In FFE there is a single list of pointers to all the bitmaps you'd ever want (strictly speaking, not every bitmap -- but the main lot is; also, probably not all the bitmaps you'd ever want, but just those from the game).

There are a couple of essential parameters for bitmaps: width and height spring first to mind. Another thing is a bitmap hotspot coordinate: the bitmap coordinate which is taken to be (0,0) when blitted to the screen. Take, for example, the aiming cross; it is useful if you want to point it to, say, the center of the screen, and you don't have to subtract half its width and half its height of the screen coordinates. Just subtract the values in the hotspot, and presto, you have the correct screen position.

That is a lie. The aiming cross has in fact a hotspot of (0,0), and, come to think of it, so have all the other bitmaps. Even worse is that the only piece of blitting code which actually uses the hotspot is the routine which blits the mouse pointer -- that's how I know what it is supposed to be. And the mouse pointer also has a (0,0) hotspot.
But, hey, I'm trying to tell you how the game works, not what the programmers were thinking of.

It is quite useful to know whether a bitmap is masked is not -- if it uses transparent pixels instead of being a dull and boring square. One well known trick is to create a separate mask of the same size, in which the transparent pixels are black and opaque ones are white (or even shades of gray to perform alpha tricks). Another possibility is to compress the bitmap, where a special code is used for a "run" of transparent pixels (and the compression itself would also be quite useful). FFE uses a third common method: pixels with a certain color value are never drawn on the screen, and, no doubt because of the easy test, this usually is the value "0". It would be a real bummer if all images were always tested for transparent pixels; not only is it a waste of time if you can tell beforehand whether to test or not, but it would eat up a whole color of the precious limited number; the wise thing to do is to store a flag in the bitmap header for transparency.

 

Putting the useful and otherwise bits in the right order, we get this structure definition:

typedef struct {
    unsigned char  Transparent;
    unsigned char  height;
    unsigned short width;
    unsigned short hot_x;
    unsigned short hot_y;
} bitimage_t;

Transparent is a single bit; height is an unsigned char and thus limits the actual bitmap height to <= 255 pixels (I imagine the programmers didn't look to the future then!), but, fortunately, width is a whole short so the images can be as wide as 65535 pixels.

The final shorts are hot_x and hot_y, and if you want to make them useful you'd have to patch the mouse pointer.

Each separate image in the image list points to a structure like this; right after the structure the actual image bytes follow, at least width × height, and followed by padding zeros where necessary to force the next structure to a 4-byte boundary (the compiler does that).

 

The images are all palette-indexed, so another useful thing would be the actual color palette. Frontier does some amazing tricks with the limited number of colors, and rather a large part of the code is devoted to shifting just the palette around, using all kinds of in-use and update flags. It appears the first 128 colors of the palette are fixed to the colors used in the UI; the top half is used for 3D modelling, and can be changed when necessary for every single frame.

If you must know. The internal palette for the 3D models is 16x16x16 values; only four bits per color channel, where the actual VGA palette supports 6 bits and true color supports 8. The program maintains an array of 4096 bytes to translate between a "real" RGB value and the palette index used for that color; a large number of routines and flags select the "optimal" 128 actual colors out of those. It appears the 128 fixed colors also have their place in this array (though they probably have a flag saying don't change me).

 

The bottom of the 128 color palette looks like this -- you'll need it if you want to display the graphics yourself:


Although the image contains the corrects RGB values, the palette order in the GIF is
messed up. The order as shown in the image is correct. I'll try to make a better one...

 

To peep into the colorful guts of FFE I wrote a small(ish) program, the Frontier Image Viewer. It's for Windows only, but if you're lucky to use that (... not even a joke intended...) you can see just about all images in the file firstenc.dat (the one supplied with your original FFE; it should be 2,101,248 bytes large). An additional Texture toggle switches to showing the texture bitmaps in the appropriate colors. Don't do that yet -- the story on the textures comes right after this.

 

If you look at the different images using the Image Viewer, you'll see they fall broadly into two categories. The "properly" colored images are those used in the UI, the dull grey ones are used for textures.

Among the UI images you can find the journal headings, the different stars and planets from the System info screen, and an entire console in its "off" state (# 43). On top of this console the various buttons and indicators are drawn in either "off" or "on" state (they start at image #102), as well as the center scanner (images 254 to 261) and the different missile types (#276 and onwards). There is a special structure in the game, defining which bitmaps are on which x and y coordinates; this structure also defines the width and height of the "clickable" region for that button and which key press to send to the game. The structure gets updated every time the console needs to be redefined for a different function; fairly regular stuff all.

The Textures List

The dull grey images are used for 3D texture mapping. They appear dull grey because they only use the first 7 colors in the palette, and they get their (initial!) color from yet another structure:

typedef struct {
    int image;
    int number_of_colors;
    unsigned char rgb[7][3];
    unsigned char padding;
} texture_t;

image is the image number in the huge image list. There is a single texture (#21) where the image value is out of range: 0x303F; when looking at the texturing code I found that the image number gets masked with 0xfff to get rid of the extra bits, but I couldn't find where and why they might be used. This value might as well be a remnant of some earlier version of the code.

number_of_colors appears to indicate how many valid colors there are in the next item, rgb, but it actually is the value 7 in every texture definition (well, apart from texture #0).

rgb is a simple array, mapping RGB values in the range 0..8 to each pixel in the original texture. Why this isn't in the full available range of 0..15, as I stated above? These are the initial colors of the texture, before applying any lighting effects. Lighting is done by adding a constant RGB value to these ones; this constant color is taken from the color of the nearest star. There are 7 rgb values because the textures only use pixel numbers 0 to 7 (in textures with transparency the color for number 0 is not used).

The padding byte aligns the structure to 4-byte-values in memory.

Ambient color

Every object should have some ambient coloring applied, that's the color of the "surroundings". In Frontier, this is bright white (when examining ships in the Shipyard), or the color of the main star in your current system.

Recall that every 3D model has a Dot color (see Frontier Objects for more about this). The Red component of the dot color may have, apart from the actual dot color, a few extra bits set. Bit #7 (DotColor.Red & 0x80) selects if the object is 'shiny', i.e., if it is displayed, when very small, as a star or as a dot. The remaining 3 bits (DotColor.Red & 0x70) set the ambient color the model emits. In most models this is 0, and is not used -- your Cobra does not light up in the dark. For stars, however, this value is used to select the ambient color.

The extra lighting is taken from this array:

unsigned char Ambient[6][8][3] = {
    7,7,7, 7,7,7, 7,7,7, 6,6,6, 5,5,5, 4,4,4, 3,3,3, 2,2,2,  // 0 (default)
    7,6,3, 7,5,2, 7,4,2, 6,3,1, 5,2,0, 4,1,0, 3,0,0, 2,0,0,  // Red
    7,7,5, 7,6,4, 7,5,2, 6,4,0, 5,3,0, 4,2,0, 3,2,0, 2,1,0,  // Orange
    7,7,7, 7,7,7, 6,6,6, 5,5,5, 4,4,4, 3,3,3, 2,2,2, 1,1,1,  // White
    7,7,7, 7,7,7, 6,7,7, 6,6,7, 5,5,7, 4,4,7, 3,3,6, 2,2,4,  // Cyan
    0,6,7, 0,5,7, 0,4,7, 0,3,7, 0,2,6, 0,1,5, 0,0,4, 0,0,2   // Blue
};

The default color is used to display ships in the shipyard and status screens. The other 5 values are set with the Dotcolor.Red bits.

The default white cannot be selected in the model color bits (a value of '0' here means "don't use it"). The only models which have this extra ambient data are the Brown dwarf substellar object and the regular stars.

Only the ambient color of the primary star is used. If you visit Omicron Eridani (2,0) -- a system with a bright yellow 'G' star, a white dwarf, a type 'M' red star and a brown substellar object -- you will see that, no matter how close you get to one of the red stars, everything will be lighted in white. You may also be blasted out of the skies by a squadron of Imperial Traders long before you reach it.

 

Each color set has 8 brightness levels; they are used for flat shading. Coloring is simple: the values here are added to the current color and clipped when they are over 15, so the entire line for each texture pixel reads like

actual_red = max (Ambient[CurrentAmbient][CurrentShade][0]
                   + texture[CurrentTexture].rgb[texture_pixel][0], 15)

... for red, and the same for green and blue. Then this RGB color has to be looked up in the current color array but I'll spare you that one.

Looking up the color index to use is the easy part. It is the index on red * 256 + green * 16 + blue in the 4096 byte array. But ask yourself, how did that color index get there?

 

The texture list consists of 147 texture_t items. Number 0 is not shown in the Image Viewer because, for reasons unknown, its values are all zeros. Whereever a 3D model command uses a texture in its MATERIAL parameter, the texture to use is the number in this list.

If you want to experiment with the textures, first make sure you know how to read arbitrary images from the image list. The list of pointers to the images start in firstenc.dat at offset 0x19A9EC, the start of the first image is at offset 0x19AF5C (right after the image list -- it'll take some calculations to get the proper offset per image, but nothing too difficult). Next, download the texture list and plug in your personal graphics routines. If that works you can try for yourself what happens when you add the different ambient colors to the textures. See the images below for a few examples.

 

If you run the Image Viewer you can see that there are a few duplicates; for example, textures 1 and 2 appear to be the same. That is not an error in the program! The structures are defined exactly the same, but texture #1 is used in the 3D models, and #2 is not...

There are a few same textures with different rgb color sets; this example shows the camouflage pattern and its variations:

.. where the image is the same (#50) but the rgb color array differs.

The same four textures under (top) the brightest "Red Star" light and (bottom) a medium "Cyan Star" light look something like this:

The colors look quite different than in the game, these images aren't remapped to 6 levels of RGB and so use the full range possible.

An example of textures with transparency is the background galaxy. The 3D model is sort of wrap-around poster, divided into squares, and uses these textures:

The entire Times font consists of textures with transparency. The 3D model for each character is a simple rectangle with the appropriate texture mapped onto it. Some characters look strangely stretched in the Image Viewer, but that's just the texture itself; the width of the destination rectangle is adjusted so the character looks normal again.

The white line visible at the bottom in the images for characters "Z" and "/" does not appear on the textured characters -- it may well be an artefact of the texture code itself, causing the bottom-most line never to be used.

 

The texturing code itself is pretty straightforward. It examines whether the texture image is 64x64 or 128x128, and whether it contains transparent pixels or not. Based on that, one of four optimized routines is selected and fed with the source and destination coordinates; for a 64x64 pattern, the source coordinates are always (0,0), (63,0), (0,63) and (63,63) and for a 128x128 pattern always (0,0), (127,0), (0,127) and (127,127). It follows from this the texturizer can't draw any random part of a texture, the entire pattern is always used (and, it seems, apart from the bottom line).

Do four different routines look like hyper-optimizing code to you? Not to me. Transparent texturing deserves a routine for itself, but there is not a large difference between using fixed sizes (64 and 128) and 'any' size. But what really drags the whole thing down for me is the restriction on the input coordinates. There is no actual reason to always use the same input coordinates for every texture map operation. The code, as it is now, is able to handle other input values! I confirmed this by changing the data in JJFFE on lines
DATA_007791:
	dw 0x00,0x00, 0x3f,0x00 ; same as yours but rewritten as dw's
	dw 0x00,0x3f, 0x3f,0x3f ; coz that's what they are
to other values; the game still runs fine, but with clipped textures! For example, this change:
DATA_007791:
	dw 0x20,0x00, 0x3f,0x00 ; left in pair is x,right is y
	dw 0x20,0x3f, 0x3f,0x3f
blits only the right half of the textures, as can be seen in the intro text.

The Special case of Planets

Two images in the image list have an unexpected value in their bitimage_t definition: the height of images 94 and 95 is zero, though the structure is followed by valid image data; the image viewer displays them as 64 × 512 pixels. Image 94 is referenced in texture number #63, image 95 is seemingly unused. And that texture number #63 isn't used in any of the 3D models (that is, where the commands that I know use a texture). I guess they are part of the planet tesselation code -- but the entire planet thingy is so far out of my grasp they could just hide anything in there.


Top is #94, bottom is #95; they are shown rotated sideways.

There is a second weird thing about these two. As explained above, textures use only 7 colors, which are defined in the texture_t structure. These two images use no less than 32 different values (a bit less than 32; but my point is, more than 7). As I can't find anything useful on how the display of planets work, I don't know for certain how colors are applied to these two textures. It might be possible that a few different values in the textures are assigned the same colors (thus flattening the image), or a selection is made (for example, only original texture pixels 0 to 7 are used and the rest is discarded). I'll have to compare these strips to actual screen shots of planets before I can tell what happens here.

There is a tantalising section in the data of the game where a triangle order is defined which smells like tesselation -- 0,2,5, then 0,1,2, then 2,3,5, then 3,4,1 and so on. This pattern repeats itself for smaller and smaller sets of triangles -- exactly what one would expect for subdivison of a sphere.

There is also a large section with a number of RGB triplets which look promising. They aren't used in the 'regular' models (I figgered that much out) but I'll have to rewire one of my programs to plug them into these two textures; it might be the planet colors!

Back to the Model Materials

At this point you know everything about the textures, except for one. Where are they used in the 3D models? The answer: just about everywhere!

Fortunately, since you now also know all of the color model in the game, this is a brief section!

The MATERIAL word in the commands line, triangle and square and their double counterparts, polygon, pine, text, cone, curve, ball array and, to a lesser (greater?) extent, subobject, all draw primitives; the command setcolor selects the next material to use in a primitive. In each of these commands you can either select a solid color or a texture, indicating whether to apply lighting or not, or indicate whether its color should be taken from an internal variable. This is all coded into one 16-bit MATERIAL word:

 

1514131211109876543210
0Fh0Eh0Dh0Ch0Bh0Ah9876543210
&8000h&4000h&2000h&1000h
n.u.TexLightLoc--- Texture number ---
n.u.0LightLoc--Red----Green----Blue--

 

n.u. -- This bit is not used in the models. Anywhere. I don't even know if it tested in the code for something, and since I'm not missing anything in the colors, I'm not inclined to find out either.

Tex -- The TEXTURE bit. If this is set the remaining 12 bytes indicate which texture number to use. That means the game supports in principle up to 4095 different textures!

Light -- The UNLIT bit. If set then shading should not be applied. It is usually set for balls to depict lights (they shouldn't be darker in the shadows, get it?), but it is also set for the characters of the fonts, for jet flames and in some models for special effects.

Loc -- The LOCALCOLOR bit. If this bit is set the material as defined previous with a SetColor command should be used. There may be additional data in this word! I don't know if this data is to be combined, or just discarded. The LOCALCOLOR bit is set for each character in the vector font; apparently, that way it uses the color set in the Text command itself.

 

If the TEXTURE bit is not set the remaining 12 bits define an RGB color, where each channel can be 0..15. If there is an overflow in these values, because of the brightest shading or a texture color, the value is just clipped to its maximum of 15.

There are a few borderline cases. Textures are only valid for the primitives triangle and square, but I seem to recall there are several lines, polygons, and/or balls with a valid texture in their definition. I'm not sure what should happen in that case; it might be as easy as using color #0 in the texture definition.

The other borderline case are the jet flames; they are clearly textured but also clearly get their color in their own drawing command (usually the pine primitive). I still don't know what really happens when a pine shape is drawn, I can't imagine the code seeing the difference between a jet flame and a tree... The first is usually UNLIT but shurly that is not the magic difference?

Fun with JJFFE: The Jjagged Edge Cobra

The JJFFE source code is freely available, and if you have the correct compiler setup you can change the actual code of the game at will. Granted, you'd have to be proficient in C and even rate Elite in assembler to make changes to the executable part. Fortunately, the package also includes all data written out in easy-to-adjust ASCII texts; John Jordan made absolutely sure to get all data labels right so the game won't complain a bit if we, shall we say, surreptitiously sneak some structures in, compile, and see what we get...

As mentioned before, there are a few duplicate textures with exactly the same parameters. An example is texture #2 (a green camou pattern); every occurrence, for example, on the Falcon Attack Fighter, uses texture #1. That means this is not used. That means we surely can find a use for it. That means hacking!

The list of texture_t texture definitions in the JJFFE sources can be found in the file ffedat.asm under label DATA_007814. You can clearly see the first, zero-filled, member of the list. JJ inserted -- nicely done! -- blank lines between each structure item, so we can move down to the third (that is texture_t number 2) and see what we got. It looks at the moment like this:

    db 0x32, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0
    db 0x0, 0x4, 0x0, 0x0, 0x6, 0x0, 0x0, 0x5, 0x0, 0x0, 0x3, 0x0
    db 0x0, 0x2, 0x0, 0x0, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0

... all written out in bytes coz it don't matter to the compiler (or the game) if the data is written out in bytes, words, or ASCII strings with escape codes. Remember that the first two items in the texture_t structure refer to dwords, the first 8 bytes all belong to the first dwords '0x32' (image number 50) and '7' (number of colors). Change the first number to 64 (or 0x40 if you want to keep it in hex) -- this is a marble texture. Move to the next line.

The next two lines define 22 bytes, of which the first 21 define 7 RGB color values (3 bytes each); the last one is padding. (Do you spot an error in my original comment on the "padding byte"?) Let's change the colors to something more interesting. Change these two lines to:

    db 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x5, 0x3, 0x0, 0x6, 0x4, 0x0
    db 0x7, 0x5, 0x0, 0x7, 0x6, 0x0, 0x7, 0x7, 0x0, 0x0

... defining the RGB triplets as 000, 100, 530, 640, 750, 760, and 770. When you recompile, the colors of this texture are redefined.

Wait a minute! You can redefine all you want, but this particular texture is still not used anywhere!

Let's find the Cobra Mk I definition. With the ShowMesh program you can find the model number: model #36. The model list can be found in the file ffebmp.asm and starts at DATA_004681 (JJ inserted a comment "Ship object table" in the version I have before me). If you scroll down the list to number 36 (again, the first item is #0) I see a comment "Cobra Mk 3"; that is model #38, the Cobra Mk 1 is two positions above that, with the value DATA_002686. This is the pointer to the actual data.

Search for the data label itself (there should be a line "DATA_002686:" somewhere above this list); on this position, which is a Model_t structure, you'll see two new data pointers, some bytes, another data pointer, and then some more bytes (and more data pointers). Recall from the previous article that the first data pointer points to the 3D mesh: the drawing commands. In my version, this pointer has the value DATA_002681. Search for this data label (again followed by a colon). You will find some 65 lines of db's -- bytes (where the actual values probably should be words).

Now you'll have to use the output of ShowMesh to see what you must look for. In the output you can see this model uses the SetColor command:

L94:  001A 400F 8200 4011 4009 400D 4001 4003 4013 4023 ; SetColor(GLOBAL[2],
        Texture_015_Metal_Red_0,
        Texture_017_Metal_Blue_0,
        Texture_009_Metal_Black_0,
        Texture_013_Metal_Green_0,
        Texture_001_Camouflage_Green_0,
        Texture_003_Camouflage_Gray_0,
        Texture_019_Metal_Cyan_0,
        Texture_035_Camouflage_Orange)

In the previous article I speculated somewhere global variable [2] might be the ship's internal ID; here it should select the main texture for the ship.

I am not too happy about this: to use the result of this SetColor, some of the triangles and/or squares defined afterwards should have their LOCALCOLOUR bit set. Since this is not the case the Cobra always appears the same: with gray camouflage. I seem to recall in FE2 the Cobra did appear with different textures and colors; if that is the case this code is just copied badly (and broken)...

Anyway. You can see a couple of lines below this command a useless (?) FFE6 code, and right after that the following lines:

      0004 4003 050A 0B04 0002 ; Square(Texture_003_Camouflage_Gray_0, 10,11,5,4, Normal(2))
      0007 20EE 0C0E 1002 ; Triangle(UNLIT | COLOR_CYAN, 12,14,16, Normal(2));
        ; Triangle(UNLIT | COLOR_CYAN, 13,15,17, Normal(3))
      0003 4003 0001 0204 ; Triangle(Texture_003_Camouflage_Gray_0, 0,1,2, Normal(4))
      0003 4003 0405 0206 ; Triangle(Texture_003_Camouflage_Gray_0, 4,5,2, Normal(6))
      0008 4004 0602 0400 0008 ; Square(Texture_004_Camouflage_Gray_1, 6,2,4,0, Normal(8));
        ; Square(Texture_004_Camouflage_Gray_1, 7,3,5,1, Normal(9))
      0007 4003 0406 080A ; Triangle(Texture_003_Camouflage_Gray_0, 4,6,8, Normal(10));
        ; Triangle(Texture_003_Camouflage_Gray_0, 5,7,9, Normal(11))
      024B 0186 ; if (DISTANCE > 390) goto L190
      191C 0000 ; Rotate -- default 0000
      833C 0554 ; Rotate -- default 0554
      000A 0888 060A 4646 3016
        ; Text(COLOR_WHITE, Normal(10), Vertex(70), Scale(6), VECTOR_FONT, 3016h)
      191C 0040 ; Rotate -- default 0040
      833C 0554 ; Rotate -- default 0554
      000A 0888 060B 4648 3016
        ; Text(COLOR_WHITE, Normal(11), Vertex(72), Scale(6), VECTOR_FONT, 3016h)

L190: 0007 4004 0408 0A0C ; Triangle(Texture_004_Camouflage_Gray_1, 4,8,10, Normal(12));
        ; Triangle(Texture_004_Camouflage_Gray_1, 5,9,11, Normal(13))
      0004 4003 010A 0B00 000E ; Square(Texture_003_Camouflage_Gray_0, 10,11,1,0, Normal(14))
      0007 4004 0006 0A10 ; Triangle(Texture_004_Camouflage_Gray_1, 0,6,10, Normal(16));
        ; Triangle(Texture_004_Camouflage_Gray_1, 1,7,11, Normal(17))
      0007 4003 0608 0A12 ; Triangle(Texture_003_Camouflage_Gray_0, 6,8,10, Normal(18));
        ; Triangle(Texture_003_Camouflage_Gray_0, 7,9,11, Normal(19))

These lines define the entire body of the Cobra!

You can see the two textures used: #3 (Camouflage Gray) and #4 (also Camouflage Gray). This is a bit tragic, since they appear exactly the same -- AFAIK! What I want to do now, is replace every occurrence of the texture with the new one I re-defined before. Since it is texture #2, and we still want the TEXTURE bit set, the MATERIAL word should be 4002h. We should substitute every occurrence of 4003h and 4004h with this one. Let's go back to ffebmp.asm, shall we.

The data in ffebmp.dat is written out byte-wise, and, while it is quite possible to replace every two db's with a single dw (and reverse the two bytes!), I'm just gonna bung the new values in the hard way. You'll have to examine the exact bytes as they appear in the ShowMesh listing to be able to find the correct ones in the assembler listing. Don't worry too much about that -- you'll see soon enough if you changed the wrong ones! Save a copy of the file if you are uncertain about this...

In my copy the byte set FFE6h can be found twice; the second one is followed by the two bytes 04 00, or 0004 in word form -- defining the first rectangle of the body. It looks like this -- I've underlined the MATERIAL codes to change for you!

db 0x6e, 0x18, 0x1a, 0x6, 0x15, 0xf0, 0xe6, 0xff ; <- useful after all!
db 0x4, 0x0, 0x3, 0x40, 0xa, 0x5, 0x4, 0xb
db 0x2, 0x0, 0x7, 0x0, 0xee, 0x20, 0xe, 0xc
db 0x2, 0x10, 0x3, 0x0, 0x3, 0x40, 0x1, 0x0
db 0x4, 0x2, 0x3, 0x0, 0x3, 0x40, 0x5, 0x4
db 0x6, 0x2, 0x8, 0x0, 0x4, 0x40, 0x2, 0x6
db 0x0, 0x4, 0x8, 0x0, 0x7, 0x0, 0x3, 0x40
db 0x6, 0x4, 0xa, 0x8, 0x4b, 0x2, 0x86, 0x1
db 0x1c, 0x19, 0x0, 0x0, 0x3c, 0x83, 0x54, 0x5
db 0xa, 0x0, 0x88, 0x8, 0xa, 0x6, 0x46, 0x46
db 0x16, 0x30, 0x1c, 0x19, 0x40, 0x0, 0x3c, 0x83
db 0x54, 0x5, 0xa, 0x0, 0x88, 0x8, 0xb, 0x6
db 0x48, 0x46, 0x16, 0x30, 0x7, 0x0, 0x4, 0x40
db 0x8, 0x4, 0xc, 0xa, 0x4, 0x0, 0x3, 0x40
db 0xa, 0x1, 0x0, 0xb, 0xe, 0x0, 0x7, 0x0
db 0x4, 0x40, 0x6, 0x0, 0x10, 0xa, 0x7, 0x0
db 0x3, 0x40, 0x8, 0x6, 0x12, 0xa, 0xd3, 0x0

Change the underlined bytes to the word 4002h (you only have to change the first value 3 or 4 to a 2), and you're done!

Hit compile. My compiler does not automatically rebuild the assembler sources! Imagine this: I wrote all this before actually testing and thought for a moment it didn't work!

... and buy yourself a gold marbled Jjagged Edge Cobra!

 


high-rez screenshot, with thanks to Anton Lindstrom!

 

As stated above the SetColor doesn't work properly for the Cobra's. In principle, you could restore multi-color Cobra's by changing the MATERIAL words for the same drawing commands to 1000h. I tried it but failed miserably... the Cobra's just turned out even gray. I've tried a few permutations of the material setting, and it appears I can change it in whatever I want if I just don't use the LOCALCOLOR bit. Very weird, it appears to work just fine for the missiles.

 

So I tried another of my ideas: adding a new high-resolution image and applying it to a new texture. That worked out fine!

 


The official Jjagged Bbanner Tour Cobra

 

First create an appropriate image. Remember, only values 0 to 7 should be used, and it should be in one of the supported sizes 64x64 or 128x128. You'll need it as an ASCII file, so try this:

  • Draw a Jjagged Bbanner texture in grayscale, 128 x 128 pixels, and save it in raw form (no headers or what).
  • Convert this image to a hex ASCII file (there are tools just to do that -- of course I rolled my own)
  • In this ASCII file, throw away the second half of each hex digit and replace each remaining number 0..F to the range 0..7. Add a db definition before each line.
  • Save this file somewhere.

Then you have to patch in this new image into ffebmp.asm.

  • Add an additional image pointer to the Image List at the end. The list starts at DATA_008240; insert a new line dd DATA_JJAGGED on the line before the end marker db 0x0, 0x0, 0x0, 0x0.
  • Add the following lines somewhere between existing data (right before the first image after the list is fine):
    DATA_JJAGGED:
        db 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0
    The 0x80, 0x80 is its size (128x128).
  • Below this line, insert the entire file you created above.

If you recompile now, you'll have 348 images instead of 347; the program will never know you added it because it does no range checking. You still can't see it because it isn't used anywhere.

In the file ffebmp.asm do the following:

  • Find the start of the texture definitions at DATA_007814 and move to the end of the list, just above the next label DATA_007815.
  • Add a new texture on the line before this new label. Insert these lines (the comments aren't necessary):
    ; extra texture: #148
        dd 347        ; THIS is the new image number
        dd 7          ; Number of colors
        db 7, 7, 7    ; rgb
        db 6, 6, 6
        db 5, 5, 5
        db 4, 4, 4
        db 3, 3, 3
        db 1, 1, 1
        db 0, 0, 0
        db 0        ; padding

You can safely recompile now (it should do so without any errors) but, although the texture is defined, it still isn't used anywhere.

Go back to the file ffebmp.asm and change the textures the same way as done above. Instead of using 4002h, we want to use the additional texture, whose number is 148 or in hex 0x94. With the bit set for a TEXTURE the new material code is 0x4094.

Whereever you changed 0x4003 (in its form 0x3, 0x40) before, change it to 0x4094 (again, in byte form 0x94, 0x40).

Recompile and find yourself a modified Cobra Mk I.

 

A much simpler enhancement (although it requires some work) is to get rid of all 64x64 textures and replace them with 128x128 ones. To do so, copy the original texture from the Image Viewer into your favourite editor, enlarge it and replace the original image with the new one. You must take care to leave the seven color indexes of the original image unchanged. And do not forget to change the size in the data file! The top line of each image originally reads

db 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0

and you must change the 0x40's to 0x80 (I forgot while testing -- you'll soon notice if you did).

This is less work because the game engine will notice the new image size and automatically uses the larger texture routine -- no other patches required.

And it is not necessary to change all textures -- just the ones which get "ugly" when viewed close up. Besides, because you don't need to patch anything else, you can test each new texture immediately!

 


Another Cobra, this one is with the camouflage
pattern enlarged to 128x128 pixels.

 

 

<< Previous :: Next >>

[Jongware]

Based on original data and algorithms from Frontier:Elite 2 and Frontier:First Encounters by David Braben (Frontier Developments)

Original copyright holders:
Elite 4: The Next Encounter David Braben 2011?
First Encounters David Braben 1995
Frontier David Braben 1993
Elite David Braben and Ian Bell 1984

 

All the images you'd ever want... yeah, right! At least you got a nice Cobra -- send your thanks to jongware.