Difference between revisions of "PC-98 format"

From Princed Wiki
Jump to: navigation, search
(Added list of files)
(Added backgrounds)
Line 5: Line 5:
 
'''Byte order''': Numbers larger than a byte use little-endian (Intel) byte order, unless noted otherwise.
 
'''Byte order''': Numbers larger than a byte use little-endian (Intel) byte order, unless noted otherwise.
  
'''Number systems''': Hexadecimal numbers are prefixed with 0x.
+
'''Number systems''': Hexadecimal numbers are prefixed with 0x. This is omitted in sequences of bytes, for readability.
  
 
'''Numbering of bits''': The least significant bit of a byte is bit 0, the most significant bit of a byte is bit 7.
 
'''Numbering of bits''': The least significant bit of a byte is bit 0, the most significant bit of a byte is bit 7.
  
'''Tools''': Zarazala has made some tools for working with the files described in this document. You can get them from [[#External links]].
+
'''Tools''': A Japanese programmer, Zarazala, has made some tools for working with the files described in this document. You can get them from [[#External links]].
 +
In each section below, I mention the relevant tool.
  
 
== Disk images ==
 
== Disk images ==
  
Disk images can be either headerless, or have a header.
+
Disk images can be either headerless (*.TFD), or have a header (*.FDI). Emulators can use both.
  
 
Online, this game is most commonly found in *.FDI files, these have a header of 0x1000 bytes.
 
Online, this game is most commonly found in *.FDI files, these have a header of 0x1000 bytes.
Line 218: Line 219:
 
| BEEP.EFC || sound effects || sound effects used when only a beeper is available (in Neko Project 2: Device → Sounds → Disable boards)
 
| BEEP.EFC || sound effects || sound effects used when only a beeper is available (in Neko Project 2: Device → Sounds → Disable boards)
 
|-
 
|-
| JAFFER1.DAT || background || Intro: Jaffar's face
+
| JAFFER1.DAT || [[#Backgrounds|background]] || Intro: Jaffar's face
 
|-
 
|-
| JAFFER2.DAT || background || Intro: Jaffar's hands and top half of crystal ball
+
| JAFFER2.DAT || [[#Backgrounds|background]] || Intro: Jaffar's hands and top half of crystal ball
 
|-
 
|-
| JAFFER3.DAT || background || Intro: bottom half of crystal ball
+
| JAFFER3.DAT || [[#Backgrounds|background]] || Intro: bottom half of crystal ball
 
|-
 
|-
| OPEN1.DAT || background || Intro: In the crystal ball: the prince and the princess
+
| OPEN1.DAT || [[#Backgrounds|background]] || Intro: In the crystal ball: the prince and the princess
 
|-
 
|-
| OPEN2.DAT || background || Intro: In the crystal ball: the prince and the princess caught
+
| OPEN2.DAT || [[#Backgrounds|background]] || Intro: In the crystal ball: the prince and the princess caught
 
|-
 
|-
| OPEN3.DAT || background || Intro: In the crystal ball: the prince in dungeon
+
| OPEN3.DAT || [[#Backgrounds|background]] || Intro: In the crystal ball: the prince in dungeon
 
|-
 
|-
| OPEN11.DAT || background || Intro: Waving water for OPEN1.DAT
+
| OPEN11.DAT || [[#Backgrounds|background]] || Intro: Waving water for OPEN1.DAT
 
|-
 
|-
| OPEN12.DAT || background || Intro: Waving water for OPEN1.DAT
+
| OPEN12.DAT || [[#Backgrounds|background]] || Intro: Waving water for OPEN1.DAT
 
|-
 
|-
| OPEN32.DAT || background || Intro: Torch flame for OPEN3.DAT
+
| OPEN32.DAT || [[#Backgrounds|background]] || Intro: Torch flame for OPEN3.DAT
 
|-
 
|-
| OPEN33.DAT || background || Intro: Torch flame for OPEN3.DAT
+
| OPEN33.DAT || [[#Backgrounds|background]] || Intro: Torch flame for OPEN3.DAT
 
|-
 
|-
| JAF_S1.DAT || background || Intro: Jaffar grinning
+
| JAF_S1.DAT || [[#Backgrounds|background]] || Intro: Jaffar grinning
 
|-
 
|-
| JAF_S2.DAT || background || Intro: Jaffar grinning
+
| JAF_S2.DAT || [[#Backgrounds|background]] || Intro: Jaffar grinning
 
|-
 
|-
| JAF_E1.DAT || background || Intro: Jaffar's eyes glowing
+
| JAF_E1.DAT || [[#Backgrounds|background]] || Intro: Jaffar's eyes glowing
 
|-
 
|-
| JAF_E2.DAT || background || Intro: Jaffar's eyes glowing
+
| JAF_E2.DAT || [[#Backgrounds|background]] || Intro: Jaffar's eyes glowing
 
|-
 
|-
| JAF_E3.DAT || background || Intro: Jaffar's eyes glowing
+
| JAF_E3.DAT || [[#Backgrounds|background]] || Intro: Jaffar's eyes glowing
 
|-
 
|-
| JAF_E4.DAT || background || Intro: Jaffar's eyes glowing
+
| JAF_E4.DAT || [[#Backgrounds|background]] || Intro: Jaffar's eyes glowing
 
|-
 
|-
| TITLE.DAT || background || Title screen: Persia at night
+
| TITLE.DAT || [[#Backgrounds|background]] || Title screen: Persia at night
 
|-
 
|-
| TITLE2.DAT || background || Title screen: game title
+
| TITLE2.DAT || [[#Backgrounds|background]] || Title screen: game title
 
|-
 
|-
| TITLE3.DAT || background || Title screen: copyrights
+
| TITLE3.DAT || [[#Backgrounds|background]] || Title screen: copyrights
 
|-
 
|-
| TITLE4.DAT || background || Title screen: a game by Jordan Mechner
+
| TITLE4.DAT || [[#Backgrounds|background]] || Title screen: a game by Jordan Mechner
 
|-
 
|-
| PROOM.DAT || background || Princess's room
+
| PROOM.DAT || [[#Backgrounds|background]] || Princess's room
 
|-
 
|-
 
| VCLIP.DAT || raw 1bpp image || clipping masks?
 
| VCLIP.DAT || raw 1bpp image || clipping masks?
 
|-
 
|-
| MOYOU.DAT || background || menu background
+
| MOYOU.DAT || [[#Backgrounds|background]] || background of the main menu
 
|-
 
|-
| TRAP.DAT || ([[#Compression|compressed]]) sprites || traps, potions, doors, everything not part of the background / level objects except buttons and loose floors
+
| TRAP.DAT || ([[#Compression|compressed]]) sprites || traps, potions, doors, everything not part of the background (level objects except buttons and loose floors)
 
|-
 
|-
 
| PLATE1.DAT || ([[#Compression|compressed]]) sprites || buttons, loose floors for LEV01.CHR
 
| PLATE1.DAT || ([[#Compression|compressed]]) sprites || buttons, loose floors for LEV01.CHR
Line 302: Line 303:
 
| CHTAB7.DAT || ([[#Compression|compressed]]) sprites || Jaffar in the intro
 
| CHTAB7.DAT || ([[#Compression|compressed]]) sprites || Jaffar in the intro
 
|-
 
|-
| LEV01.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 0, 1, 2, 3
+
| LEV01.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 0, 1, 2, 3 (dungeon)
 
|-
 
|-
| LEV04.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 4, 5, 6
+
| LEV04.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 4, 5, 6 (palace)
 
|-
 
|-
| LEV07.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 7, 8, 9
+
| LEV07.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 7, 8, 9 (dungeon)
 
|-
 
|-
| LEV10.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 10, 11
+
| LEV10.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 10, 11 (palace)
 
|-
 
|-
| LEV12.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 12, 13
+
| LEV12.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for levels 12, 13 (dungeon)
 
|-
 
|-
| LEV14.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for level 14
+
| LEV14.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for level 14 (halls)
 
|-
 
|-
 
| LEV15.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for level 15
 
| LEV15.CHR || ([[#Compression|compressed]]) [[#Tiles|tiles]] || graphics for level 15
Line 352: Line 353:
 
| EFONT.DAT || font || ending credits font
 
| EFONT.DAT || font || ending credits font
 
|-
 
|-
| ENDING1.DAT || background || Ending: the prince before the sultan
+
| ENDING1.DAT || [[#Backgrounds|background]] || Ending: the prince bows before the sultan
 
|-
 
|-
| ENDING2.DAT || background || Ending: the prince and the princess wave to the people
+
| ENDING2.DAT || [[#Backgrounds|background]] || Ending: the prince and the princess wave to the people
 
|-
 
|-
| ENDING3.DAT || background || Ending: the prince and the princess together
+
| ENDING3.DAT || [[#Backgrounds|background]] || Ending: the prince and the princess together in the sunset
 
|-
 
|-
| ENDING4.DAT || background || Ending: palace garden
+
| ENDING4.DAT || [[#Backgrounds|background]] || Ending: palace garden
 
|-
 
|-
| ENDING5.DAT || background || Ending: "The End" text
+
| ENDING5.DAT || [[#Backgrounds|background]] || Ending: "The End" text
 
|-
 
|-
| ENDING6.DAT || backgrounds || Ending: waving flags
+
| ENDING6.DAT || [[#Backgrounds|background]] ([[#ENDING6.DAT|multiple images]]) || Ending: waving flags
 
|}
 
|}
  
Line 375: Line 376:
  
 
Each block adds 4 bytes to the output, except those with head bytes 0x11, 0x21, and 0x91. Those add multiples of 4 bytes.
 
Each block adds 4 bytes to the output, except those with head bytes 0x11, 0x21, and 0x91. Those add multiples of 4 bytes.
 +
 +
There is no end marker, the decompression ends when the compressed file ends.
  
 
In the arguments and the output column, each group of two letters represents a byte.
 
In the arguments and the output column, each group of two letters represents a byte.
  
 
In the following table, the head bytes are ordered first by their second half, then by their first half.
 
In the following table, the head bytes are ordered first by their second half, then by their first half.
 +
The system in the codes is more visible this way.
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 461: Line 465:
 
| 0xN8 (N=0..D or F) || see above || Like 0xN7, but instead of 00, use FF.
 
| 0xN8 (N=0..D or F) || see above || Like 0xN7, but instead of 00, use FF.
 
|-
 
|-
| 0xN9 (N=0..D) || see above || Like 0xN7, but instead of 00, use the corresponding byte from the last 4 bytes of the output.
+
| 0xN9 (N=0..D) || see above || Like 0xN7, but instead of 00, use the corresponding byte from the last 4 bytes of the output (the value from 4 bytes before).
 
|-
 
|-
| 0xNA (N=0..D) || see above || Like 0xN7, but instead of 00, use the corresponding byte from the penultimate 4 bytes of the output.
+
| 0xNA (N=0..D) || see above || Like 0xN7, but instead of 00, use the corresponding byte from the penultimate 4 bytes of the output (the value from 8 bytes before).
 
|}
 
|}
  
Line 538: Line 542:
 
=== Blocks changed by the game ===
 
=== Blocks changed by the game ===
  
''(Based on the documentation by zarazala)''
+
''(Based on the documentation by Zarazala)''
  
 
When you pick up an item or a loose floor starts falling, the block number of that tile is incremented by 1.
 
When you pick up an item or a loose floor starts falling, the block number of that tile is incremented by 1.
Line 624: Line 628:
 
It is used for the whole game, except the following:
 
It is used for the whole game, except the following:
 
* It does not affect the Broderbund logo animation.
 
* It does not affect the Broderbund logo animation.
* Nor does it affect the flashes of the screen when someone is hurt, etc.
+
* Nor does it affect the flashes of the screen when someone is hurt, etc. (those colors are in MAIN.PRG)
  
 
It contains 7 RGB color values, for palette slots 1-7.
 
It contains 7 RGB color values, for palette slots 1-7.
Line 636: Line 640:
 
The default colors are:
 
The default colors are:
 
{| class="wikitable"
 
{| class="wikitable"
! index !! bytes !! HTML !! color
+
! index !! bytes !! HTML !!colspan="2"| color
 
|-
 
|-
| 0 || -    || #000 ||bgcolor="#000"|  
+
| 0 || -    || #000 ||bgcolor="#000"|   || black
 
|-
 
|-
| 1 || 09 00 || #009 ||bgcolor="#009"|  
+
| 1 || 09 00 || #009 ||bgcolor="#009"|   || blue
 
|-
 
|-
| 2 || A0 00 || #A00 ||bgcolor="#A00"|  
+
| 2 || A0 00 || #A00 ||bgcolor="#A00"|   || red
 
|-
 
|-
| 3 || BB 00 || #B0B ||bgcolor="#B0B"|  
+
| 3 || BB 00 || #B0B ||bgcolor="#B0B"|   || magenta
 
|-
 
|-
| 4 || 00 08 || #080 ||bgcolor="#080"|  
+
| 4 || 00 08 || #080 ||bgcolor="#080"|   || green
 
|-
 
|-
| 5 || 0D 0B || #0BD ||bgcolor="#0BD"|  
+
| 5 || 0D 0B || #0BD ||bgcolor="#0BD"|   || cyan
 
|-
 
|-
| 6 || D0 0D || #DD0 ||bgcolor="#DD0"|  
+
| 6 || D0 0D || #DD0 ||bgcolor="#DD0"|   || yellow
 
|-
 
|-
| 7 || EE 0E || #EEE ||bgcolor="#EEE"|  
+
| 7 || EE 0E || #EEE ||bgcolor="#EEE"|   || white
 
|}
 
|}
  
 
== Tiles ==
 
== Tiles ==
 +
 +
* '''Tools''': ''chrconv.exe''
  
 
Tiles are stored in the ([[#Compression|compressed]]) LEV*.CHR files.
 
Tiles are stored in the ([[#Compression|compressed]]) LEV*.CHR files.
Line 666: Line 672:
  
 
The first three planes contain bits 0, 1, and 2 of the color indices for each pixel.
 
The first three planes contain bits 0, 1, and 2 of the color indices for each pixel.
 +
These are then combined into a three bits-per-pixel image.
 +
 +
With the default palette, these bitplanes roughly correspond to blue, red, and green components, respectively.
 +
I say "roughly" because the intensity of the enabled components is different for different colors.
 +
 +
(The fourth plane contains transparency masks, but they are used only for tiles in the foreground layer?
 +
It's 0 for transparent parts and for some backgrounds, 1 otherwise.)
 +
 +
The 256 tiles should be displayed in a 16×16 arrangement, this keeps related tiles together.
 +
 +
== Backgrounds ==
 +
 +
* '''Tools''': ''picconv.exe''
 +
 +
Backgrounds are images without transparent parts.
 +
They don't necessarily fill the screen.
 +
 +
Backgrounds have this header:
 +
* 2 bytes: Width in bytes. Multiply by 8 to get the width in pixels.
 +
* 2 bytes: Height in pixels.
 +
 +
After the header comes the image data.
 +
The image data is compressed, but with a different method than described in [[#Compression]].
 +
 +
The basic idea is the same, though:
 +
Compressed data is stored as a series of blocks.
 +
Each block starts with a head byte specifying what to do, followed by 0 to 3 argument bytes.
 +
 +
Backgrounds decode into three bitplanes, similarly to [[#Tiles]].
 +
 +
Each block adds 3 bytes to the output, or rather, one byte to each of the 3 bitplanes.
 +
The exceptions are the blocks which repeat bytes. These add multiples of 3 bytes, or, the same number of bytes to each bitplane.
 +
 +
There is no end marker, the decompression ends when the compressed file ends (or when the output has enough bytes?).
 +
 +
In the arguments and the output column, each group of two letters represents a byte.
 +
 +
In the following table, the head bytes are ordered first by their second half, then by their first half.
 +
The system in the codes is more visible this way.
 +
 +
{| class="wikitable"
 +
! head byte !! arguments !! output
 +
|-
 +
| 0x00 || nn || Repeat the ''next'' 3×1 bytes of the output 0xnn times.
 +
|-
 +
| 0xN0 (N=1..F) || - || Repeat the ''next'' 3×1 bytes of the output N times.
 +
|-
 +
|colspan="3"| Note: The above two codes are a bit tricky. When you see a head byte ending in 0, don't write anything into the output yet.
 +
Just set a flag which reminds you that you need to repeat, and also store the value of nn or N as ''repeat_count''.
 +
Every time after you processed a block which is ''not'' a "repeat next" block, check if the repeat flag is set.
 +
If it is, then repeat the last 3×1 bytes ''repeat_count'' times.
 +
|-
 +
| 0x01 || xx || xx xx xx (i.e. write the next byte 3 times to the output)
 +
|-
 +
| 0x02 || xx yy || xx xx yy
 +
|-
 +
| 0x12 || xx yy || xx yy xx
 +
|-
 +
| 0x22 || xx yy || xx yy yy
 +
|-
 +
| 0x03 || xx yy zz || xx yy zz (i.e. copy the next 3 bytes to the output)
 +
|-
 +
| 0x04 || xx || 00 00 xx
 +
|-
 +
| 0x14 || xx || 00 xx 00
 +
|-
 +
| 0x24 || xx || xx 00 00
 +
|-
 +
| 0x34 || xx yy || 00 xx yy
 +
|-
 +
| 0x44 || xx yy || xx 00 yy
 +
|-
 +
| 0x54 || xx yy || xx yy 00
 +
|-
 +
| 0x05 || xx || FF FF xx
 +
|-
 +
| 0x15 || xx || FF xx FF
 +
|-
 +
| 0x25 || xx || xx FF FF
 +
|-
 +
| 0x35 || xx yy || FF xx yy
 +
|-
 +
| 0x45 || xx yy || xx FF yy
 +
|-
 +
| 0x55 || xx yy || xx yy FF
 +
|-
 +
|colspan="3"| Note: 0xN5 head bytes are like 0xN4 head bytes, except they use 0xFF instead of 0x00.
 +
|-
 +
| 0x06 || - || 00 00 00
 +
|-
 +
| 0x16 || - || FF 00 00
 +
|-
 +
| 0x26 || - || 00 FF 00
 +
|-
 +
| 0x36 || - || FF FF 00
 +
|-
 +
| 0x46 || - || 00 00 FF
 +
|-
 +
| 0x56 || - || FF 00 FF
 +
|-
 +
| 0x66 || - || 00 FF FF
 +
|-
 +
| 0x76 || - || FF FF FF
 +
|-
 +
|colspan="3"| Note: A 0xN6 head byte basically appends 8 pixels with color N.
 +
The appended bytes are filled with bits 4, 5, and 6 of the head byte, respectively.
 +
|-
 +
| 0xR7 || nn || Go up R+1 scanlines from where you are (i.e. go back (R + 1) × ''width in bytes'' × 3 bytes), read 0xnn × 3 bytes from there, and append them to the output.
 +
|}
 +
 +
=== ENDING6.DAT ===
 +
 +
ENDING6.DAT has a slightly different format, because it contains multiple images.
 +
 +
The file begins with 12 offsets, 2 bytes each.
 +
These offsets tell where each image starts in the file.
  
(The fourth plane contains transparency masks, but they are used only for tiles in the foreground layer?)
+
The images themselves are in the same format as the other [[#Backgrounds]].
  
 
== TODO ==
 
== TODO ==
  
* Graphics: backgrounds, sprites
+
* Graphics: sprites
 
* Fonts
 
* Fonts
 
* Music and sounds
 
* Music and sounds
Line 681: Line 803:
 
* VCLIP.DAT
 
* VCLIP.DAT
 
* the hit point images in MAIN.PRG
 
* the hit point images in MAIN.PRG
* the Broderbund logo in BJLOGO.PRG
+
* the Broderbund logo in BJLOGO.PRG (BJ = Broderbund Japan?)
  
 
== External links ==
 
== External links ==
  
* [https://forum.princed.org/viewtopic.php?p=34174#p34174 Documentation and tools by zarazala, and the game itself.]
+
* [https://forum.princed.org/viewtopic.php?p=34174#p34174 Documentation and tools by Zarazala, and the game itself.]

Revision as of 09:54, 28 December 2021

This document describes the format of data in Prince of Persia 1 for the PC-98.

General

Byte order: Numbers larger than a byte use little-endian (Intel) byte order, unless noted otherwise.

Number systems: Hexadecimal numbers are prefixed with 0x. This is omitted in sequences of bytes, for readability.

Numbering of bits: The least significant bit of a byte is bit 0, the most significant bit of a byte is bit 7.

Tools: A Japanese programmer, Zarazala, has made some tools for working with the files described in this document. You can get them from #External links. In each section below, I mention the relevant tool.

Disk images

Disk images can be either headerless (*.TFD), or have a header (*.FDI). Emulators can use both.

Online, this game is most commonly found in *.FDI files, these have a header of 0x1000 bytes.

  • Tools: datacut.exe to extract files from a (headerless!) disk image, makeimg.exe to make a disk image from files.

How recognize and convert headered images

A headerless game disk starts with the bytes EB 0A. If they appear at offset 0x1000 then you have a headered disk image.

To convert a headered disk image to headerless, go to offset 0x1000 and delete everything before it. You might want to save the new file under a different name.

FDI header

The FDI header contains the following information:

Offset in bytes Size Value on PoP disks Meaning
0x00 4 bytes 0 (unknown)
0x04 4 bytes 0x90 = 144 (unknown)
0x08 4 bytes 0x1000 = 4096 Start offset of the raw disk image (i.e. the size of the FDI header).
0x0C 4 bytes 0x134000 = 1261568 Size of the raw disk image in bytes.
0x10 4 bytes 0x400 = 1024 Size of a sector (or cluster?) in bytes.
0x14 4 bytes 8 Number of sectors per track?
0x18 4 bytes 2 Number of sides?
0x1C 4 bytes 0x4D = 77 Number of tracks per side?

If you multiply the last four numbers, you get the size of the raw disk image: 0x400 * 8 * 2 * 0x4D = 0x134000.

The rest of the header is filled with zeroes.

File system

A disk consists of 0x4D0 sectors, numbered from 0 to 0x4CF. Each sector is 0x400 = 1024 bytes.

The game disk (disk A) contains the following parts:

Offset in bytes Occupied sectors Contents
0x0000 - 0x03FF 0 #Boot sector
0x0400 - 0x0FFF 1 - 3 #File allocation table
0x1000 - 0x1FFF 4 - 7 #Directory
0x2000 - 0x133FFF 8 - 0x4CF Data area

The offsets in the table assume you have a headerless disk image.

Boot sector

The boot sector contains the code for starting the game.

From offset 2 is the disk label "GAME DISK" on disk A, and "USER DISK" on disk B.

Directory

This area contains 0x100 = 256 entries, of 0x10 = 16 bytes each.

Each entry describes a file in the following format:

Offset in bytes Size Meaning
0 8 bytes Base name. If it's shorter than 8 characters then it's padded with spaces (0x20).
8 3 bytes Extension. (The same padding is used here, although it does not occur in the original PoP disk image.)
0x0B = 11 3 bytes Unknown. Might be file attributes and/or modification date/time?
0x0E = 14 2 bytes The number of the first sector of this file.

To get the full file name, concatenate the base name (with padding removed), a dot, and the extension.

Unused entries are filled with 0xFF bytes.

File allocation table

This area contains 2-byte entries for each sector on the disk.

There is space for 0x600 entries, but only the first 0x4D0 entries are used, since the disk has only that many sectors. The unused entries are filled with zeroes.

The possible values of each entry are:

  • 0xFFFF : If the sector is before the data area (sectors 0-7).
  • 0x0008 - 0x04CF : If this sector is part of a file but not the last sector.
    • This whole sector is part of the file.
    • The value is the number of the next sector of the file.
  • 0xFC00 - 0xFFFF : If this is the last sector of a file.
    • Subtract 0xFC00 from the value to get a size. The first size bytes of this sector are part of the file.
    • If you get 0 from the subtraction, then the whole sector is part of the file. (This does not occur in the original PoP disk image, though.)
    • The unused area of last sectors is filled with 0x00. (makeimg.exe fills it with 0xE5.)
  • 0x0000 : If the sector is not used or does not exist (unused entries).
    • Unused sectors are filled with 0xE5.

How to read a file

  • Find the file in the #Directory.
  • Read the number of the first sector into current_sector.
  • Repeat until exit:
    • current_sector should be a valid sector number pointing into the data area (between 0x0008 and 0x04CF). If it's not then it's an error.
    • Read the #File allocation table entry corresponding to current_sector into next_sector. (offset = 0x400 + current_sector * 2, length = 2)
    • Read the sector numbered current_sector into sector_data. (offset = current_sector * 0x400, length = 0x400)
    • If next_sector < 0xFC00:
      • Append sector_data to the output.
      • Set current_sector to next_sector.
      • Continue the loop.
    • else:
      • Set size to next_sector - 0xFC00.
      • If size = 0 then set size to 0x400.
      • Append the first size bytes of sector_data to the output.
      • Exit the loop.
  • Done.

TODO: How to make an empty disk. How to add a file.

List of files

In the order they appear in the #Directory:

file format contents
MUSIC.SYS program music player
MAIN.PRG program gameplay, cutscenes
SUBPROG.PRG program menus, intro, ending
BJLOGO.PRG program Broderbund logo animation
PALET.DAT palette the palette used by the game
FONT.DAT font menu font
FIRE.DAT sprites torch flame
MUSIC1.MUS music
MUSIC2.MUS music
MUSIC3.MUS music
MUSIC4.MUS music
FIGHT.MUS music
FIGHTF.MUS music
FIGHTS.MUS music
FIGHTK.MUS music
VICT1.MUS music
APPEAR.MUS music
APPEAR2.MUS music
PICKUP.MUS music
POWUP.MUS music
KUSURI.MUS music
CLEAR.MUS music
EXIT.MUS music
DEAD.MUS music
DEAD2.MUS music
OPEN.MUS music
ROOM1.MUS music
ROOM2.MUS music
END1.MUS music
END2.MUS music
END3.MUS music
TIMEOUT.MUS music
MENU.MUS music
SOUND.EFC sound effects sound effects used when synthesizer hardware is available (in Neko Project 2: Device → Sounds → PC-9801-86)
BEEP.EFC sound effects sound effects used when only a beeper is available (in Neko Project 2: Device → Sounds → Disable boards)
JAFFER1.DAT background Intro: Jaffar's face
JAFFER2.DAT background Intro: Jaffar's hands and top half of crystal ball
JAFFER3.DAT background Intro: bottom half of crystal ball
OPEN1.DAT background Intro: In the crystal ball: the prince and the princess
OPEN2.DAT background Intro: In the crystal ball: the prince and the princess caught
OPEN3.DAT background Intro: In the crystal ball: the prince in dungeon
OPEN11.DAT background Intro: Waving water for OPEN1.DAT
OPEN12.DAT background Intro: Waving water for OPEN1.DAT
OPEN32.DAT background Intro: Torch flame for OPEN3.DAT
OPEN33.DAT background Intro: Torch flame for OPEN3.DAT
JAF_S1.DAT background Intro: Jaffar grinning
JAF_S2.DAT background Intro: Jaffar grinning
JAF_E1.DAT background Intro: Jaffar's eyes glowing
JAF_E2.DAT background Intro: Jaffar's eyes glowing
JAF_E3.DAT background Intro: Jaffar's eyes glowing
JAF_E4.DAT background Intro: Jaffar's eyes glowing
TITLE.DAT background Title screen: Persia at night
TITLE2.DAT background Title screen: game title
TITLE3.DAT background Title screen: copyrights
TITLE4.DAT background Title screen: a game by Jordan Mechner
PROOM.DAT background Princess's room
VCLIP.DAT raw 1bpp image clipping masks?
MOYOU.DAT background background of the main menu
TRAP.DAT (compressed) sprites traps, potions, doors, everything not part of the background (level objects except buttons and loose floors)
PLATE1.DAT (compressed) sprites buttons, loose floors for LEV01.CHR
PLATE4.DAT (compressed) sprites buttons, loose floors for LEV04.CHR
PLATE7.DAT (compressed) sprites buttons, loose floors for LEV07.CHR
PLATEA.DAT (compressed) sprites buttons, loose floors for LEV10.CHR
PLATEC.DAT (compressed) sprites buttons, loose floors for LEV12.CHR
PLATEE.DAT (compressed) sprites button for LEV14.CHR
CHTAB1.DAT (compressed) sprites prince
CHTAB2.DAT (compressed) sprites prince and mouse
CHTAB3.DAT (compressed) sprites prince and sword
CHTAB4F.DAT (compressed) sprites fat guard on level 6
CHTAB4G.DAT (compressed) sprites regular guard
CHTAB4K.DAT (compressed) sprites shadow on level 12
CHTAB4S.DAT (compressed) sprites skeleton on level 3
CHTAB4V.DAT (compressed) sprites the boss on level 13
CHTAB4X.DAT (compressed) sprites Jaffar on level 14, 15
CHTAB5.DAT (compressed) sprites prince
CHTAB6.DAT (compressed) sprites princess, Jaffar in the intro, hourglass, princess room torch flame
CHTAB7.DAT (compressed) sprites Jaffar in the intro
LEV01.CHR (compressed) tiles graphics for levels 0, 1, 2, 3 (dungeon)
LEV04.CHR (compressed) tiles graphics for levels 4, 5, 6 (palace)
LEV07.CHR (compressed) tiles graphics for levels 7, 8, 9 (dungeon)
LEV10.CHR (compressed) tiles graphics for levels 10, 11 (palace)
LEV12.CHR (compressed) tiles graphics for levels 12, 13 (dungeon)
LEV14.CHR (compressed) tiles graphics for level 14 (halls)
LEV15.CHR (compressed) tiles graphics for level 15
LEV00.MAP (compressed) level level 0 (demo)
LEV01.MAP (compressed) level level 1
LEV02.MAP (compressed) level level 2
LEV03.MAP (compressed) level level 3
LEV04.MAP (compressed) level level 4
LEV05.MAP (compressed) level level 5
LEV06.MAP (compressed) level level 6
LEV07.MAP (compressed) level level 7
LEV08.MAP (compressed) level level 8
LEV09.MAP (compressed) level level 9
LEV10.MAP (compressed) level level 10
LEV11.MAP (compressed) level level 11
LEV12.MAP (compressed) level level 12 (12a) (up to the Shadow fight)
LEV13.MAP (compressed) level level 13 (12b) (a boss, but not Jaffar yet)
LEV14.MAP (compressed) level level 14 (12c) (the halls leading to the princess)
LEV15.MAP (compressed) level level 15 (12d) (the fight with Jaffar)
DEMOPLAY.KEY automatic moves Automatic moves of the prince for the demo level.
EFONT.DAT font ending credits font
ENDING1.DAT background Ending: the prince bows before the sultan
ENDING2.DAT background Ending: the prince and the princess wave to the people
ENDING3.DAT background Ending: the prince and the princess together in the sunset
ENDING4.DAT background Ending: palace garden
ENDING5.DAT background Ending: "The End" text
ENDING6.DAT background (multiple images) Ending: waving flags

Compression

  • Tools: compress.exe

The following files are compressed: TRAP.DAT, PLATE*.DAT, CHTAB*.DAT, LEV*.CHR, LEV*.MAP

Compressed data is stored as a series of blocks. Each block starts with a head byte specifying what to do, followed by 0 to 4 argument bytes.

Each block adds 4 bytes to the output, except those with head bytes 0x11, 0x21, and 0x91. Those add multiples of 4 bytes.

There is no end marker, the decompression ends when the compressed file ends.

In the arguments and the output column, each group of two letters represents a byte.

In the following table, the head bytes are ordered first by their second half, then by their first half. The system in the codes is more visible this way.

head byte arguments output
0x00 xx yy zz ww xx yy zz ww (i.e. copy the next 4 bytes to the output)
0x01 - Repeat the last 4 bytes of the output.
0x11 nn Repeat the last 4 bytes of the output, 0xnn + 1 times.
0x21 nn mm Repeat the last 4 bytes of the output, 0xmmnn + 1 times.
0x81 - Repeat the penultimate 4 bytes of the output.
0x91 nn Repeat the penultimate 4 bytes of the output, 0xnn + 1 times.

Phrased differently, start from the 8th last byte of the output, and copy (0xnn + 1) * 4 bytes.
If nn > 1 then the source overlaps the destination, and the bytes written will later become bytes read. This is intentional.

0x02 xx xx xx xx xx (i.e. write the next byte 4 times to the output)
0x03 xx yy xx yy yy yy
0x13 xx yy xx yy xx xx
0x23 xx yy xx xx yy xx
0x33 xx yy xx xx xx yy
0x04 xx yy xx xx yy yy
0x14 xx yy xx yy xx yy
0x24 xx yy xx yy yy xx
0x44 xx yy zz xx xx yy zz
0x54 xx yy zz xx yy xx zz
0x64 xx yy zz xx yy zz xx
0x74 xx yy zz xx yy yy zz
0x84 xx yy zz xx yy zz yy
0x94 xx yy zz xx yy zz zz
0xN5 (N=0..F) ba dc Na Nb Nc Nd (i.e. write bytes whose upper half comes from the head and their lower half comes from the arguments)
0xN6 (N=0..F) ba dc aN bN cN dN (i.e. write bytes whose lower half comes from the head and their upper half comes from the arguments)
0x07 xx 00 00 00 xx
0x17 xx 00 00 xx 00
0x27 xx 00 xx 00 00
0x37 xx xx 00 00 00
0x47 xx yy 00 00 xx yy
0x57 xx yy 00 xx 00 yy
0x67 xx yy 00 xx yy 00
0x77 xx yy xx 00 00 yy
0x87 xx yy xx 00 yy 00
0x97 xx yy xx yy 00 00
0xA7 xx yy zz 00 xx yy zz
0xB7 xx yy zz xx 00 yy zz
0xC7 xx yy zz xx yy 00 zz
0xD7 xx yy zz xx yy zz 00
0xF7 - 00 00 00 00
0xN8 (N=0..D or F) see above Like 0xN7, but instead of 00, use FF.
0xN9 (N=0..D) see above Like 0xN7, but instead of 00, use the corresponding byte from the last 4 bytes of the output (the value from 4 bytes before).
0xNA (N=0..D) see above Like 0xN7, but instead of 00, use the corresponding byte from the penultimate 4 bytes of the output (the value from 8 bytes before).

Levels

  • Tools: mapedit.exe

The levels are stored in the (compressed) LEV*.MAP files.

After decompression, levels contain the following data:

Offset Size Contents
0x0000..0x07FF 2048 (128 halfblocks * 4*4 CHR tiles) CHR tiles of each halfblock
0x0800..0x08FF 256 (128 blocks * 2 halfblocks) back layer halfblocks of each block
0x0900..0x09FF 256 (128 blocks * 2 halfblocks) front layer halfblocks of each block (The most significant bit is sometimes set, what does it mean?)
0x0A00..0x0AFF 256 (128 blocks * 2 bytes) #Block flags and objects
0x0B00..0x0DCF 720 (24 rooms * 30 tiles) blocks (The most significant bit is sometimes set, what does it mean?)
The remaining parts correspond to the DOS / Apple II level format from offset 0x2D0.
0x0DD0..0x109F 720 (24 rooms * 30 tiles) modifiers
0x10A0..0x119F 256 #Door events 1
0x11A0..0x129F 256 #Door events 2
0x12A0..0x12FF 96 (24 rooms * 4 directions) room links (left,right,up,down) (1 to 24, or 0 if there is no adjacent room)
0x1300 1 number of used rooms
0x1301..0x1318 24 room row within level map (unused)
0x1319..0x1330 24 room column within level map (unused)
0x1331..0x133F 15 unused
0x1340 1 starting room (1 to 24)
0x1341 1 starting tile position (0 to 29)
0x1342 1 starting direction (0x00=right, 0xFF=left)
0x1343..0x1346 4 unused
0x1347..0x135E 24 guard tile position for each room (0 to 29=0x1D, or 30=0x1E if no guard)
0x135F..0x1376 24 guard direction for each room (0x00=right, 0xFF=left)
0x1377..0x138E 24 unused?
0x138F..0x13A6 24 unused?
0x13A7..0x13BE 24 guard skill for each room
0x13BF..0x13D6 24 unused?
0x13D7..0x13EE 24 unused? (DOS PoP stores the guard color here)
0x13EF..0x13FF 17 unused

Tiles and blocks

Tiles from LEV*.CHR files are 16×16 pixels. 4×4 tiles are combined into a halfblock (64×64 pixels). Then two halfblocks (top and bottom halves) are combined in each layer (back, front) of each block (64×128 pixels).

Blocks changed by the game

(Based on the documentation by Zarazala)

When you pick up an item or a loose floor starts falling, the block number of that tile is incremented by 1. Therefore, for each block with a potion, a sword, or a loose floor, the next block must have the same graphics and flags but no object.

TODO: What happens when a loose floor lands? There is no broken floor object. To add a broken floor, the block number has to be changed, but by what logic?

Special events which change tiles similarly increase the block number by 1. The exception is the appearing of the mirror, which decreases the block number by 1.

Block flags and objects

Only the first byte of each item is used, the second bytes are zero.

byte meaning notes
flags -- these can be combined with objects
0x01 floor
0x02 wall
0x04 torch Unlike in DOS PoP, torches appear in the tile where they are placed, not one tile to the right.
objects
0x00 none
0x08 loose floor
0x10 open button The modifier selects the door event to be triggered.
0x18 close button The modifier selects the door event to be triggered.
0x20 door If the modifier is 1 then the door is open, else it is closed.
0x28 spike
0x30 heal potion
0x38 hurt potion
0x40 life potion
0x48 upside down potion
0x50 slow fall potion
0x58 chomper
0x60 sword
0x68 mirror Not used in level data: Placed on level 4 when the exit door opens.
0x70 level door
0x78 skeleton
0x80 door top
0x88 balcony stars Used in the top left corner of balconies.

Door events

(same format as in the DOS / Apple II version)
-------------------------  7  6  5  4  3  2  1  0 <-bits
byte from door events 1 | NX R1 R0 L4 L3 L2 L1 L0
byte from door events 2 | R4 R3 R2  0  0  0  0  0

R4 R3 R2 R1 R0: The number of the room. (1..24)

L4 L3 L2 L1 L0: The location in the room. (0..29)

NX: If zero then trigger next door event, if one then don't.

Palette

The palette is stored in PALET.DAT.

It is used for the whole game, except the following:

  • It does not affect the Broderbund logo animation.
  • Nor does it affect the flashes of the screen when someone is hurt, etc. (those colors are in MAIN.PRG)

It contains 7 RGB color values, for palette slots 1-7. Slot 0 is not in the file, it is always black.

Each color is encoded in two bytes, with the bits arranged as: rrrrbbbb 0000gggg

That is, in the first byte, bits 0-3 store the blue intensity, bits 4-7 store the red intensity. In the second byte, bits 0-3 store the green intensity, and bits 4-7 are unused.

The default colors are:

index bytes HTML color
0 - #000   black
1 09 00 #009   blue
2 A0 00 #A00   red
3 BB 00 #B0B   magenta
4 00 08 #080   green
5 0D 0B #0BD   cyan
6 D0 0D #DD0   yellow
7 EE 0E #EEE   white

Tiles

  • Tools: chrconv.exe

Tiles are stored in the (compressed) LEV*.CHR files.

After decompression, they contain data for 256 tiles, each is 0x80 bytes long. The data of each tile is further split into four sections (bitplanes) of 0x20 bytes each.

To decode the image of a tile, arrange the bits of each plane in a 16×16 array. The bits should be read from bit 0 to bit 7, and the array should be filled left to right, top to bottom.

The first three planes contain bits 0, 1, and 2 of the color indices for each pixel. These are then combined into a three bits-per-pixel image.

With the default palette, these bitplanes roughly correspond to blue, red, and green components, respectively. I say "roughly" because the intensity of the enabled components is different for different colors.

(The fourth plane contains transparency masks, but they are used only for tiles in the foreground layer? It's 0 for transparent parts and for some backgrounds, 1 otherwise.)

The 256 tiles should be displayed in a 16×16 arrangement, this keeps related tiles together.

Backgrounds

  • Tools: picconv.exe

Backgrounds are images without transparent parts. They don't necessarily fill the screen.

Backgrounds have this header:

  • 2 bytes: Width in bytes. Multiply by 8 to get the width in pixels.
  • 2 bytes: Height in pixels.

After the header comes the image data. The image data is compressed, but with a different method than described in #Compression.

The basic idea is the same, though: Compressed data is stored as a series of blocks. Each block starts with a head byte specifying what to do, followed by 0 to 3 argument bytes.

Backgrounds decode into three bitplanes, similarly to #Tiles.

Each block adds 3 bytes to the output, or rather, one byte to each of the 3 bitplanes. The exceptions are the blocks which repeat bytes. These add multiples of 3 bytes, or, the same number of bytes to each bitplane.

There is no end marker, the decompression ends when the compressed file ends (or when the output has enough bytes?).

In the arguments and the output column, each group of two letters represents a byte.

In the following table, the head bytes are ordered first by their second half, then by their first half. The system in the codes is more visible this way.

head byte arguments output
0x00 nn Repeat the next 3×1 bytes of the output 0xnn times.
0xN0 (N=1..F) - Repeat the next 3×1 bytes of the output N times.
Note: The above two codes are a bit tricky. When you see a head byte ending in 0, don't write anything into the output yet.

Just set a flag which reminds you that you need to repeat, and also store the value of nn or N as repeat_count. Every time after you processed a block which is not a "repeat next" block, check if the repeat flag is set. If it is, then repeat the last 3×1 bytes repeat_count times.

0x01 xx xx xx xx (i.e. write the next byte 3 times to the output)
0x02 xx yy xx xx yy
0x12 xx yy xx yy xx
0x22 xx yy xx yy yy
0x03 xx yy zz xx yy zz (i.e. copy the next 3 bytes to the output)
0x04 xx 00 00 xx
0x14 xx 00 xx 00
0x24 xx xx 00 00
0x34 xx yy 00 xx yy
0x44 xx yy xx 00 yy
0x54 xx yy xx yy 00
0x05 xx FF FF xx
0x15 xx FF xx FF
0x25 xx xx FF FF
0x35 xx yy FF xx yy
0x45 xx yy xx FF yy
0x55 xx yy xx yy FF
Note: 0xN5 head bytes are like 0xN4 head bytes, except they use 0xFF instead of 0x00.
0x06 - 00 00 00
0x16 - FF 00 00
0x26 - 00 FF 00
0x36 - FF FF 00
0x46 - 00 00 FF
0x56 - FF 00 FF
0x66 - 00 FF FF
0x76 - FF FF FF
Note: A 0xN6 head byte basically appends 8 pixels with color N.

The appended bytes are filled with bits 4, 5, and 6 of the head byte, respectively.

0xR7 nn Go up R+1 scanlines from where you are (i.e. go back (R + 1) × width in bytes × 3 bytes), read 0xnn × 3 bytes from there, and append them to the output.

ENDING6.DAT

ENDING6.DAT has a slightly different format, because it contains multiple images.

The file begins with 12 offsets, 2 bytes each. These offsets tell where each image starts in the file.

The images themselves are in the same format as the other #Backgrounds.

TODO

  • Graphics: sprites
  • Fonts
  • Music and sounds
  • Texts?
  • User disk
  • Automatic moves (DEMOPLAY.KEY)
  • FM Towns version (PRI1.DAT)
  • Hacks for easier playtesting: skipping the Broderbund logo, starting any level.
  • VCLIP.DAT
  • the hit point images in MAIN.PRG
  • the Broderbund logo in BJLOGO.PRG (BJ = Broderbund Japan?)

External links