Skip to content

Original Level File Format

ErikaRedmark edited this page Sep 12, 2014 · 24 revisions

This is an ongoing collection of information attempting to understand the old file format. Since there are many addon levels available, and possibly many more, it becomes unfeasible to re-create manually each world. The idea is to build some kind of importer to recreate the levels automatically from the raw level data (preferably as it is stored in the .msh windows format files (which is the resource fork bundled together so it could exist on systems without such a fork) and the .rsrc created when dragging a file to a windows machine from Sheep Shaver (very similiar concept although different method and format). The plan is twofold:

  1. Understand and interpret the binary format of the .Plvl and .WrLd resource forks.
  2. Be able to find the .WrLd resource and the .Plvl resources (associated with the proper levels) within the .msh file and/or the file generated automatically by SheepShaver for preserving resource forks.

Common

All short values are 2 bytes, signed, and little-endian.

All bool types are 1 byte.

World (.WrLd)

This test examination will look at Spaced Out. All data will be in Hex form.

Raw

001D0020005A03840451000100010002000100010001000200000000000000000000000000000000000001010000000000000000000000000000000F000F001000140014001400100000000000000000000000000000000000000012001200000000000000000000000000000000000000000000000000000000

Examined

First 5 values are shorts:
001D = 29 Red Keys
0020 = 32 Blue Keys
005A = 90 Fruit in total (includes keys???)
0384 = 900 Exit Door Level (not required in port)
0451 = 1105 Bonus Door Level

This is an array of size 16. Death Animations for each hazard: 1 is burn, 2 is electrocute. Assuming 0 is normal death and 3 is bee sting. One of these has to mean Non-Lethal, since non-lethal hazards exist in some addon worlds (Corys Storm Keep rain, for instance)

.[0] = 0001 = 1 - Bomb (Burns bonzo)
.[1] = 0001 = 1 - Bomb (Burns bonzo)
.[2] = 0002 = 2 - Lightbulb (Electrifies bonzo)
.[3] = 0001 = 1 - Lava (Red) (Burns Bonzo)
.[4] = 0001 = 1 - Lava (Blue) (Burns Bonzo)
.[5] = 0001 = 1 - Lava (Yellow) (Burns Bonzo)
.[6] = 0002 = 2 - Zappy Electric Thing (Electrifies bonzo)
.[7] = 0000
.[8] = 0000
.[9] = 0000
[10] = 0000
[11] = 0000
[12] = 0000
[13] = 0000
[14] = 0000
[15] = 0000

Parallel array to the hazards list of bool. False means does not explode, true means explodes.
.[0] = 01 = 1 - Bomb Explodes
.[1] = 01 = 1 - Dynamite Explodes
.[2] = 00 = 0 - Lightbulb does not explode
.[3] = 00 = 0 - Red lava does not explode
.[4] = 00 = 0 - Same with Blue lava
.[5] = 00 = 0 - Same with Yellow lava
.[6] = 00 = 0 - Little green zappy thing does not explode.
--- No more actual hazards past here anyway (at least for Spaced Out)
.[7] = 00
.[8] = 00
.[9] = 00
[10] = 00
[11] = 00
[12] = 00
[13] = 00
[14] = 00
[15] = 00

Parallel array to hazards list of shorts; death sounds. In the port, each death animation is hardcoded to a death sound, so this becomes irrelevant.
.[0] = 000F Bomb Death sound
.[1] = 000F
.[2] = 0010 Electrocution Sound
.[3] = 0014 Lava Death (bit different from bomb death)
.[4] = 0014
.[5] = 0014
.[6] = 0010 Electrocution Sound
.[7] = 0000
.[8] = 0000
.[9] = 0000
[10] = 0000
[11] = 0000
[12] = 0000
[13] = 0000
[14] = 0000
[15] = 0000

Parallel array to hazards list of shorts; explosion sounds. In the port, all explosions sound the same.
.[0] = 0012
.[1] = 0012
.[2] = 0000
.[3] = 0000
.[4] = 0000
.[5] = 0000
.[6] = 0000
.[7] = 0000
.[8] = 0000
.[9] = 0000
[10] = 0000
[11] = 0000
[12] = 0000
[13] = 0000
[14] = 0000
[15] = 0000

Level (.Plvl)

There is no indication in the data itself which id the level is. That is part of the resource id itself when seen in ResEdit, so that information will depend on whatever form the export of the resource fork is.

- 0002 = Number of sprites
- 0004 = Number of items

First array of type MSSpriteData is for actual sprites. After saving debug levels, it appears that the sprites are written out to the file in a random order, however order should be irrelevant:

[0] MSSpriteData:

Location(x, y)
- 00C8 = 200 | Vertical
- 01F4 = 500 | Horizontal

Minimum(x, y)
- 00C8 = 200 | Top
- 0154 = 340 | Left

Maximum(x, y)
- 00DC = 220 | Bottom - 40 (Bottom in ORIGINAL, not port)
- 0258 = 600 | Right - 40 (Right in ORIGINAL, not port)

Speed(x, y) Remember that speeds are inverted in port.
- FFFF = -1 | (Velocity) Vertical
- FFFE = -2 | (Velocity) Horizontal

- 000E = 14 (Sprite Id)
- 000A = 10 (Set of flags -> 0000 0000 | 0000 1010)

Possible majour issue with sprite ids: In the port a sprite may have two sets of animation, but both sets are ONE id, with extra metadata specifying if it can face a specific direction. The original had each sprite sheet it's own id. Will need to look at resource pack dynamically and build a mapping from sprite Id in original to sprite Id AND single facing direction (basically a single piece of data will become two based on the resource pack)

Finally, the Id will always be 0 for the bonus door and 1 for the exit door, given how the original was hard-coded. There is no need to check additional flags for that.

Below is a Legend (partially verified) of the flags for Subkind. Some items are not required for the port.

0000 0000 | [8] [7] [6] [5] | [4] [3] [2] [1]
1. Increasing frames if 1, Cycling if 0
2. Cycling frames if 0, increasing if 1. Just looking at the first bit is enough.
3. Slow Animation if 1, Fast if 0
4. Two Way Facing Vertical (Not available in port yet)
5. Two Way facing Horizontal
6. ?
7. Door. Does not indicate whether bonus or exit. In original game, resource Id 0 was always bonus and resource Id 1 was always exit. This seems hardcoded, so looking at this bit is not relevant; just need to look at sprite id and compare to either 0 for bonus, 1 for exit, anything else for other.
8. Energy Drainer if 1, Normal sprite if 0.

Continuing on the look at the Spaced out level 1000

[1] MSSpriteData: 
- 012C = 300
- 0190 = 400

- 0128 = 296
- 00ED = 237

- 01AD = 429 | Bottom - 40
- 0258 = 600 | Right - 40

- 0002 = 2
- 0002 = 2

- 001D = 29 (Sprite Id)
- 000A = 2 (Set of flags -> 0000 0000 | 0000 0010)

Next one looks almost like a copy of the first sprite, but no such sprite exists...

[2] MSSpriteData: (But there are only two sprites on this screen?!)
- 00C8 = 200
- 0244 = 580

- 00C8 = 200
- 0154 = 340

- 00DC = 220
- 0258 = 600

- FFFF = -1
- FFFE = -2

- 000E = 14
- 000A = 10 (Set of flags -> 0000 0000 | 0000 1010)

Padding (No more sprites)

- [3] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [4] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [5] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [6] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [7] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [8] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [9] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000

Second array of 25 MSSpriteData, but that structure is actually re-used to store goodies. Fields take on a different meaning.

ResourceId is the goodie id (1:1 correspondence in port). subKind appears to be a counter. location.x and location.y appear to be pixel-locations as the sprites would use (offset by the 80 pixels of the UI) that would resolve to a grid location. This may be because goodies use the same sprite collision (pixel perfect) logic that sprites do.

[0] MSSpriteData (Goodie):
- 00A0 = 160 (160 / 20 = 8)  Location.x is the ROW, not column. It takes the 80 pixel UI adjustment of 4 tiles for a final total of 4.
- 01CC = 460 (460 / 20 = 23)
(Goodie exists at 4, 23)
Row 4, column 23 (remember row 0 is the first row, and column 0 is first col)

- 0000 = Unused
- 0000 = Unused

- 0000 = Unused
- 0000 = Unused

- 0000 = Unused
- 0000 = Unused

- 0001 = Blue Key
- 0003 = Possibly means the number of goodies left in the array?

[1] MSSpriteData (Goodie):
- 00A0 = 160 | ( 8 )
- 0230 = 560 | ( 28)

- 0000 0000 | 0000 0000 | 0000 0000

- 0007 = Banana
- 0002 = One less than last element

[2] MSSpriteData (Goodie):
- 0104
- 012C

- 0000 0000  | 0000 0000  | 0000 0000 

- 0000 = Red Key
- 0001 = One less than last element

[3] MSSpriteData (Goodie):
- 00A0
- 0028

- 0000 0000 |  0000 0000  | 0000  0000 

- 0000 = Red Key
- 0000 = Last goodie in the array?

- [4] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [5] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [6] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [7] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [8] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000
- [9] 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000

Unsure why these are not zero... This looks like garbage data.

- [10] 0078 6D8A | 2F3C 0028 | 0028 2F3C | 003C 003C | A8A7 554F 
- [11] 2F2F 0006 | 4879 0078 | 6D8A A8AD | 101F 6710 | 3F3C 03E9 
- [12] 61FF FFFF | D3E0 544F | 6000 016E | 4879 0078 | 6D8A 2F3C 
- [13] 0028 0064 | 2F3C 003C | 0078 A8A7 | 554F 2F2F | 0006 4879 
- [14] 0078 6D8A | A8AD 101F | 6710 3F3C | 03EA 61FF | FFFF D3AA 
- [15] 544F 6000 | 0138 4879 | 0078 6D8A | 2F3C 0028 | 00A0 2F3C 
- [16] 003C 00B4 | A8A7 554F | 2F2F 0006 | 4879 0078 | 6D8A A8AD 
- [17] 101F 6710 | 3F3C 03EB | 61FF FFFF | D374 544F | 6000 0102 
- [18] 4879 0078 | 6D8A 2F3C | 0028 00DC | 2F3C 003C | 00F0 A8A7 
- [19] 554F 2F2F | 0006 4879 | 0078 6D8A | A8AD 101F | 6710 3F3C 
- [20] 03EC 61FF | FFFF D33E | 544F 6000 | 00CC 4879 | 0078 6D8A 
- [21] 2F3C 0028 | 0118 2F3C | 003C 012C | A8A7 554F | 2F2F 0006 
- [22] 4879 0078 | 6D8A A8AD | 101F 6710 | 3F3C 03ED | 61FF FFFF 
- [23] D308 544F | 6000 0096 | 4879 0078 | 6D8A 2F3C | 0014 021C 
- [24] 2F3C 003C | 0244 A8A7 | 554F 2F2F | 0006 4879 | 0078 6D8A 

The Level Data Array (short levelData[32][20]); 32 * 20 level data array = 640 elements of shorts making 1280 bytes. However, the data is not stored in row/column form. It is stored in column/row form, with an entire column of tiles first. (as in, from top to bottom on the leftmost side, then top to bottom on the next col...)

Legend:

Remember that a - indicates a hex value from the binary file. These are only legend values

0x0000 -> Empty  
0x0001 -> 0x0020 Solids	   (32 unique solids)  
0x0021 -> 0x0050 Thrus	   (47 unqiue thrus)  
0x0051 -> 0x0090 Scenes    (63 unique scenes)  

0x0091 -> 0x00A0 Hazards   (16 definable hazards)  

0x00A1 -> Conveyer 1: Left (anti-clockwise)  
0x00A2 -> Conveyer 2: Left  
0x00A9 -> Conveyer 1: Right (clockwise)
0x00AA -> Conveyer 2: Right  

0x00B1 -> Collapsible 1  
0x00B2 -> Collapsible 2  
- 0001 0000 0000 0000 0000 0010 000E 0008 0008 0008 0008 0008 0008 000D 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 00C1 003E 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 003F 0001 
- 0002 0000 0000 0000 0000 003F 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 003E 003E 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0006 003F 003F 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0000 0000 0000 0000 0000 0006 0005 003E 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0000 0000 0000 0001 
- 0003 0000 0000 0000 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 0000 0000 0000 0006 0005 0007 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 0000 0000 0006 0005 0007 0006 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0007 0000 0000 0000 0000 0000 0006 0005 0007 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0000 0000 0000 003E 0000 0006 0005 0007 0000 00C1 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0000 0000 0000 003F 0000 0005 0007 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 003E 0000 0000 0000 0000 0007 0000 0000 0000 0000 000F 0061 0061 0061 0061 0061 0061 0061 0061 000F 
- 003F 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001
- 0006 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 00C2 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0003 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 00C8 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001
- 0001 0000 0000 0000 0000 003E 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001 
- 0001 0000 0000 0000 0000 003F 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0001

Starting location of bonzo (In pixel location like all sprites. X field must be 80 pixel adjusted for UI

- 01A4 = 420 (Vertical)
- 00B4 = 180 (Horiztonal)

This seems like screen id, but is actually .ppat resource id.
- 03E8 = 1000
.ppat are not sequentially ordered (at least in Spaced Out) going from 129 to 131 to 135 to 1000... so it may be required to look at all available pattern numbers, order them, and then based on the index in the ordered array determines which pattern in the resource pack they get.

Missing Information

  • Interpreting sprite flags to get Cycling vs Increasing frames.
  • ... two sets verticle vs two sets horizontal vs neither

Clone this wiki locally