Group File Format

The binary assets of the game are stored in a handful of files that I’ll collectively refer to as “group files” in this document. Each episode has one STN file and one VOL file; both are required for the game to function:

FileSize (Bytes)DateHashes (MD5, SHA-1)
COSMO1.STN607,004April 15, 19927d57a6c0bda490adc3ede02fbec14793,
4e4ba181972cc15f051844b9acceb20a24936465
COSMO1.VOL1,359,939April 15, 19928d6596e26e54cba818449d0cd774368a,
ff7685781661737c5a9209dd7829fd775287cbcd
COSMO2.STN607,004April 15, 19927d57a6c0bda490adc3ede02fbec14793,
4e4ba181972cc15f051844b9acceb20a24936465
COSMO2.VOL1,308,939April 15, 19924971bb21107571b91261bf44e7a347c9,
ebae0110214947b41673a759d8c409330d2b8cfa
COSMO3.STN607,004April 15, 19927d57a6c0bda490adc3ede02fbec14793,
4e4ba181972cc15f051844b9acceb20a24936465
COSMO3.VOL1,267,305April 15, 199256e30f1949e13875a9b89ce425e3965c,
fc30ab4025d64beb7d40ecbe75d290fc03b3a204

The astute will notice that all of the STN files, regardless of the episode they belong to, are bit-for-bit identical. This seems to be a core part of the rationale for the STN/VOL split – the base functionality that appears in all three games is identical, so the assets that accompany those features can be stored in and fetched from identical STN files. The VOL files, on the other hand, are all very different from episode to episode.

A group file is simply a container that holds one or more entries. Each entry is a blob of data, indexed by a DOS-style (“8.3”) name. Entries within a group file are analogous to files within a directory on a disk.

There is no compression or encoding used anywhere in the group file format. What’s stored on disk is exactly what gets copied into memory.

Location

The group files are always opened relative to the DOS working directory when the game was started. This means that the user must CD to the directory containing the group files before trying to start the game.

If the working directory is not correct (i.e. entering CD \ and then something like C:\COSMO1\COSMO1.EXE), the game will fail to locate the group files. There is no error handling for this condition, and the game will crash with a bit of display garbage.

Each group file contains a header structure listing entry names, offsets, and sizes (sort of like a table of contents or, more properly, a file allocation table).

The structure of one header entry is:

Offset (Bytes)SizeDescription
00hbyte[12]String containing the entry’s name, in 8.3 format. The game assumes that all alphabetical characters are stored as uppercase. Unused trailing bytes are set to null. An entry name that uses all twelve bytes of the 8.3 representation is not null-terminated.
0ChdwordInteger offset to the start of the data, relative to the start of the group file, in bytes.
10hdwordInteger size of the data, in bytes.

Each header entry occupies exactly 20 bytes, and the entries repeat one after another until there are no more entries to list.

Immediately after the last header entry, the total number of entries is encoded as an ASCII string (i.e. 32h 31h for "21"). I do not have enough specimens of this data format to definitively say if the number here is fixed-width, padded, or null-terminated.

Following this, the header is null-padded until offset FA0h. Regardless of the number of entries in each group file, the header is padded to be exactly 4,000 bytes long and, presumably, can hold at most 199 entries. (The 200th entry slot would contain the total count as an ASCII string.) Again, I do not have a specimen with that many entries inside, and it’s not at all clear what should happen in the corner cases that crop up when processing such a large header.

Reading Data

The data for all entries starts immediately after the header padding. The data is stored contiguously without special alignment or inter-entry padding. There is not even a requirement that entries start on an even offset; the evenness of so many of the offsets is a consequence of the fact that almost all the entry sizes are naturally even.

To read the data for any entry, first walk the header looking for an entry whose name matches the one needed. Read the offset and size values from that entry. Seek to offset, and read size bytes starting from there.

If the search arrives at an entry with a null name, or if the end of the 4,000 byte header is reached, all entries have been searched without finding a match.

As a pseudocode example, say we wanted to find the data named my.mni inside the group file COSMO1.STN:

fp = open("COSMO1.STN")
found = false

for (i = 0; i < 4000; i += 20):
    fp.seek(i)
    entryname = fp.read_string(12)

    if entryname == "":  # Put another way, it starts with a null byte
        # There are no more occupied entry slots
        break

    if entryname != uppercase("my.mni"):  # Header names are all-caps
        # Not looking at the right `entryname` yet
        continue

    # The `entryname` matches what we're looking for
    found = true
    offset = fp.read_integer(4)
    size = fp.read_integer(4)
    fp.seek(offset)
    data = fp.read_binary(size)  # `data` contains everything we want
    break

if not found:
    # `my.mni` is not in this group file

This format, while quite simple, is also flexible and immensely abusable. Some things that should be possible:

  • Storing entry data in a different order than the header indexes it.
  • Naming entries as arbitrary 12-byte strings with no dot, or a misplaced dot. The 8.3 naming style is merely a convention.
  • Declaring the same name twice in the index (per the techniques described on this page, only the first entry would be accessible).
  • Pointing multiple header entries to the same data, or partially-overlapping windows into some larger data.
  • Creating slack space between the end of one entry’s data and the start of another. Data could be hidden in this way.

Much to my own disappointment, none of these techniques have been used in any of the group files that shipped with the game.

List of Group File Contents

For anyone who is curious what’s contained inside each group file, here is a dump of the header data. Each row contains a brief description of what that entry is used for in the game.

All offsets and sizes are in decimal bytes, to show the “round” nature of many of the entry sizes.

COSMO{1,2,3}.STN

Entry NameOffset (Bytes)Size (Bytes)Description
MASKTILE.MNI4,00040,000Images for the map tiles. These have a transparency mask.
TILES.MNI44,00064,000Images for the map tiles. These do not have transparency.
ACTRINFO.MNI108,0004,646Index containing widths, heights, and pointers to all sprite frames for actors.
PLYRINFO.MNI112,646386Index containing widths, heights, and pointers to all sprite frames for the player.
CARTINFO.MNI113,032178Index containing widths, heights, and pointers to all cartoon images used in the menus/story.
CARTOON.MNI113,21064,280Images used in the menus/story (cartoons).
FONTS.MNI177,4904,000Images used to draw the UI font. Also contains health status bars.
SOUNDS.MNI181,4903,332PC speaker sound numbers 1-23.
SOUNDS2.MNI184,8223,876PC speaker sound numbers 24-46.
SOUNDS3.MNI188,6984,020PC speaker sound numbers 47-65.
ACTORS.MNI192,718191,910Images used as actor/decoration sprites.
PLAYERS.MNI384,62830,000Images used as player sprites.
PRETITLE.MNI414,62832,000Full-screen image: “Apogee Software Productions Presents - Cosmo’s Cosmic Adventure”
TILEATTR.MNI446,6287,000Behavior flags for map tiles (solid, slippery, can cling to wall, etc.).
BONUS.MNI453,62832,000Full-screen image: “Bonus Stage - Get Ready!”
CREDIT.MNI485,62832,000Full-screen image: “Credits”
ONEMOMNT.MNI517,62832,000Full-screen image: “One Moment”
STATUS.MNI549,6287,296Image for the in-game status bar background.
BDSTAR2.MNI556,92423,040Backdrop image: Small bonus stage stars.
BDSTAR3.MNI579,96423,040Backdrop image: Large bonus stage stars.
NOMEMORY.MNI603,0044,000B800 text screen: “You do not have enough memory…”

Total entries: 21

COSMO1.VOL

Entry NameOffset (Bytes)Size (Bytes)Description
TITLE1.MNI4,00032,000Full-screen image: “Forbidden Planet - Adventure 1 of 3”
END1.MNI36,00032,000Full-screen image: Cosmo falling toward E1L11 Exit Monster.
A1.MNI68,00067,154Map data: E1L1.
A2.MNI135,15467,418Map data: E1L2.
A3.MNI202,57267,262Map data: E1L3.
A5.MNI269,83467,904Map data: E1L5.
A6.MNI337,73866,884Map data: E1L6.
A9.MNI404,62266,752Map data: E1L9.
A10.MNI471,37468,156Map data: E1L10.
A11.MNI539,53065,984Map data: E1L11 (deep pit with E1L11 Exit Monster at bottom).
A7.MNI605,51467,700Map data: E1L7.
A8.MNI673,21467,502Map data: E1L8.
BONUS1.MNI740,71666,794Map data: Bonus stage (tall).
BONUS2.MNI807,51067,580Map data: Bonus stage (wide).
A4.MNI875,09067,004Map data: E1L4.
BDWIERD.MNI942,09423,040Backdrop image: Molecules or neurons.
BDNEWSKY.MNI965,13423,040Backdrop image: Brown mountains and blue sky.
BDFOREST.MNI988,17423,040Backdrop image: Dense, tall trees.
BDSPOOKY.MNI1,011,21423,040Backdrop image: Scraggy trees with magenta background for lightning effect.
BDCLIFF.MNI1,034,25423,040Backdrop image: Brown dirt with roots.
BDICE2.MNI1,057,29423,040Backdrop image: Ice cavern.
BDCLOUDS.MNI1,080,33423,040Backdrop image: Clouds in blue sky.
BDROCKTK.MNI1,103,37423,040Backdrop image: Tech devices mounted to rock, with magenta highlights for color animation effect.
BDMOUNTN.MNI1,126,41423,040Backdrop image: Gray and blue mountains and teal sky.
MHAPPY.MNI1,149,45415,848AdLib music: “Just About Going Wacky”
MDRUMS.MNI1,165,30220,640AdLib music: “Drums”
MRUNAWAY.MNI1,185,94210,892AdLib music: “Run Away”
MZZTOP.MNI1,196,83425,152AdLib music: Rendition of “Tush” by ZZ Top.
MDEVO.MNI1,221,98625,784AdLib music: “Devo”
MBOSS.MNI1,247,77029,584AdLib music: “Boss”
MCAVES.MNI1,277,35417,080AdLib music: “Caves”
MDADODA.MNI1,294,43423,768AdLib music: “Da Do Da”
COSMO1.MNI1,318,2024,000B800 text screen: Normal episode exit screen.
MTECK4.MNI1,322,20214,908AdLib music: “Teck 4”
MEASY2.MNI1,337,11021,060AdLib music: “Easy 2”
PREVDEMO.MNI1,358,1701,769Recorded keyboard input for the demo.

Total entries: 36

COSMO2.VOL

Entry NameOffset (Bytes)Size (Bytes)Description
TITLE2.MNI4,00032,000Full-screen image: “Forbidden Planet - Adventure 2 of 3”
END2.MNI36,00032,000Full-screen image: Cosmo overlooking a city at night.
BONUS3.MNI68,00066,134Map data: Bonus stage (wide).
B1.MNI134,13465,984Map data: E2L1. Identical copy of A11.MNI from COSMO1.VOL.
B2.MNI200,11867,256Map data: E2L2.
B3.MNI267,37467,520Map data: E2L3.
B4.MNI334,89467,004Map data: E2L4.
B5.MNI401,89867,370Map data: E2L5.
B6.MNI469,26867,514Map data: E2L6.
B7.MNI536,78266,896Map data: E2L7.
B8.MNI603,67867,154Map data: E2L8.
BONUS4.MNI670,83267,160Map data: Bonus stage (tall).
MCAVES.MNI737,99217,080AdLib music: “Caves”
B9.MNI755,07266,842Map data: E2L9.
B10.MNI821,91467,616Map data: E2L10.
MSCARRY.MNI889,53017,012AdLib music: “Scarry”
MTEKWRD.MNI906,54225,924AdLib music: “Tek World”
MBELLS.MNI932,46614,284AdLib music: “Bells”
MDRUMS.MNI946,75020,640AdLib music: “Drums”
MEASY2.MNI967,39021,060AdLib music: “Easy 2”
BDWIERD.MNI988,45023,040Backdrop image: Molecules or neurons.
BDGUTS.MNI1,011,49023,040Backdrop image: Various internal organs.
BDSHRUM.MNI1,034,53023,040Backdrop image: Mushrooms at night, with magenta stars for twinkling effect.
BDICE.MNI1,057,57023,040Backdrop image: Diagonal ice spires, with magenta stars for twinkling effect.
BDCRYSTL.MNI1,080,61023,040Backdrop image: Blue and transparent crystals on black background.
BDFOREST.MNI1,103,65023,040Backdrop image: Dense, tall trees.
BDCIRCUT.MNI1,126,69023,040Backdrop image: Panels connected with conduit.
MRUNAWAY.MNI1,149,73010,892AdLib music: “Run Away”
MBANJO.MNI1,160,62214,636AdLib music: “Cosmo’s Foggy Cosmic Breakdown”
BDCAVE.MNI1,175,25823,040Backdrop image: Cave with stalactites and stalagmites.
COSMO2.MNI1,198,2984,000B800 text screen: Normal episode exit screen.
MZZTOP.MNI1,202,29825,152AdLib music: Rendition of “Tush” by ZZ Top.
BDPIPE.MNI1,227,45023,040Backdrop image: Dark gray pipes on black background.
MTECK4.MNI1,250,49014,908AdLib music: “Teck 4”
MROCKIT.MNI1,265,39818,344AdLib music: “Rock It”
PREVDEMO.MNI1,283,7422,157Recorded keyboard input for the demo.
BDMOUNTN.MNI1,285,89923,040Backdrop image: Gray and blue mountains and teal sky.

Total entries: 37

COSMO3.VOL

Entry NameOffset (Bytes)Size (Bytes)Description
TITLE3.MNI4,00032,000Full-screen image: “Forbidden Planet - Adventure 3 of 3”
END3.MNI36,00032,000Full-screen image: Cosmo on a roller coaster with other children.
C4.MNI68,00068,318Map data: E3L4.
C1.MNI136,31866,932Map data: E3L1.
C2.MNI203,25067,202Map data: E3L2.
C3.MNI270,45267,874Map data: E3L3.
C5.MNI338,32667,448Map data: E3L5.
C6.MNI405,77467,478Map data: E3L6.
C7.MNI473,25267,388Map data: E3L7.
C8.MNI540,64067,952Map data: E3L8.
BONUS5.MNI608,59266,248Map data: Bonus stage (contains “HI!” message).
C9.MNI674,84066,812Map data: E3L9.
BONUS6.MNI741,65267,220Map data: Bonus stage (looks like Dig Dug).
BDPIPE.MNI808,87223,040Backdrop image: Dark gray pipes on black background.
BDTECHMS.MNI831,91223,040Backdrop image: Panels with dense conduit, and magenta areas for lighting effect.
BDBRKTEC.MNI854,95223,040Backdrop image: Blue bricks and conduit.
BDFUTCTY.MNI877,99223,040Backdrop image: City skyscrapers at night, with magenta stars for twinkling effect.
BDCRYSTL.MNI901,03223,040Backdrop image: Blue and transparent crystals on black background.
BDCIRCPC.MNI924,07223,040Backdrop image: A fairly accurate rendering of a printed circuit board.
C10.MNI947,11265,918Map data: E3L10.
MEASYLEV.MNI1,013,03014,168AdLib music: “Easy Level”
MZZTOP.MNI1,027,19825,152AdLib music: Rendition of “Tush” by ZZ Top.
MSCARRY.MNI1,052,35017,012AdLib music: “Scarry”
MROCKIT.MNI1,069,36218,344AdLib music: “Rock It”
MTECK2.MNI1,087,70614,972AdLib music: “Teck 2”
MTECK3.MNI1,102,67818,076AdLib music: “Teck 3”
MTECK4.MNI1,120,75414,908AdLib music: “Teck 4”
MCIRCUS.MNI1,135,6627,256AdLib music: “Circus”
BDJUNGLE.MNI1,142,91823,040Backdrop image: Jungle vines with large magenta areas for explosion effect.
COSMO3.MNI1,165,9584,000B800 text screen: Normal episode exit screen.
MBOSS.MNI1,169,95829,584AdLib music: “Boss”
MDEVO.MNI1,199,54225,784AdLib music: “Devo”
BDGUTS.MNI1,225,32623,040Backdrop image: Various internal organs.
MHAPPY.MNI1,248,36615,848AdLib music: “Just About Going Wacky”
PREVDEMO.MNI1,264,2143,091Recorded keyboard input for the demo.

Total entries: 35

Overall the entries are split up in a rational way. MZZTOP.MNI and MTECK4.MNI are the only entries that appear in all three VOL files. They would have been ideal candidates to be stored in the STN files instead, but for whatever reason they ended up where they did.

It appears as though a conscious effort was made to ensure entry names across different episodes were kept unique (for instance, END1/END2/END3 for the end story images and A1/B1/C1 for map one of each episode). If all the group files were extracted into the same directory, the only names that would conflict are identical copies of the same data (and PREVDEMO.MNI).

The STN Files Have It All

If you look at what’s actually in the VOL files, you’ll notice there aren’t any sprites, tiles, cartoon images, or sounds anywhere inside. All of those come from the STN file, which means all that data is available in all the episodes – even the first shareware episode.

Episode one didn’t have the Red Jumper Creature, for instance. Or the Frozen Duke. It also did not have any story screens that showed cartoons of Cosmo jumping through a cave ceiling or speaking to Zonk. Nobody playing episode one should have encountered any of these, and yet all the graphical data that supported these elements was on the shareware disk.

Similarly, episodes two and three didn’t have the Cosmo family’s ship from the beginning of E1L1. Episode two didn’t have the Boss. The nontrivial amount of tiles needed to build these are nestled snugly in the STN file regardless, seemingly unaware that nobody ever intended to use them in these episodes.