PC-98 format

From Princed Wiki
Revision as of 16:25, 26 December 2021 by David (talk | contribs) (Added compression)
Jump to: navigation, search

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.

Disk images

Disk images can be either headerless, or have a header.

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.

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.

Compression

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.

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.

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.
0xNA (N=0..D) see above Like 0xN7, but instead of 00, use the corresponding byte from the penultimate 4 bytes of the output.

TODO

  • Levels
  • Graphics: palette, tiles, backgrounds, sprites
  • Fonts
  • Music and sounds
  • Texts?
  • User disk
  • FM Towns version

External links