In this topic I will post additions and corrections to this document: http://www.popot.org/documentation.php? ... ifications
PoP1 Palettes (corrections)
The first four bytes are marked unknown.
Byte 0 tells how many images are associated with the palette, i.e. how many images should the game load when it loads the palette.
If n is the value of byte 0 and id is the id of the palette, then images from id+1 to id+n belong to this palette.
Byte 3 is always 0x10 (16), perhaps it means the number of colors in the palette, but changing it has no effect.
CGA and EGA palettes: (I already published this, but it seems somebody misunderstood it a bit.)
The specification states that the four entries that belong to the same color are grouped together, but that's not the case.
Instead, first we have the top-left entry of all 16 colors, then the top-right of all, then the bottom-left and bottom-right.
For example, EDUNGEON.DAT contains the following:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
If the palette was stored as the specification says, it would contain this:
0000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF
Also, the EGA palette used by PoP is a bit different from the default. Color 0 is transparent, and color 13 is black.
PoP2 Palettes (additions)
SHPL: SHape PaLette, or perhaps SHaPe Loader?
Code: Select all
2 bytes: the ID of the first image (SHAP)
2 bytes: the number of images
2 bytes: 0
1 byte: number of colors
n*3 bytes: RGB data (same format as in PoP1)
These resources always exist together.
PALS stores raw RGB data.
PALC stores the number of palettes in the PALS resource.
For example, KID.DAT/PALS/25001 contains 8 palettes, one for each type of level.
PALT stores cutscene palettes.
The format is raw RGB data, without header.
PoP2 SHAP: images (addition) (This is already in PR)
The PoP2 image format is an extension of the PoP1 image format.
There is a new bit-per-pixel value: 1111 means 256 colors = 8 bpp. (0000 = 2 colors = 1 bpp, 1011 = 16 colors = 4 bpp)
There is a new compression method: LZG+RLEC.
It is signified by setting the last 4 bits of ImageMask to 0011 (LZG_LR) and setting the last bit of the first byte of ImageMask to 1.
The decompression method:
Code: Select all
LZG+RLE:
repeat until you have enough bytes in the output:
read 2 bytes from the input: TempLength
decompress with LZG from the input into a temporary buffer until it contains TempLength bytes
go to the beginning of the temporary buffer
repeat while you reach the end of the temporary buffer:
read 2 bytes from the temporary buffer: LineLength
decompress with RLEC from the next LineLength bytes of the temporary buffer to the output
end repeat
end repeat
RLEC:
repeat until you reach the end of the input:
read 1 byte: C
if C>=0x80 then:
// repeat
read 1 byte: X
write X into the output (C-0x80)+1 times
else:
// copy
copy C+1 bytes from the input to the output
endif
end repeat
Note: zero means transparent only if it's encoded in "repeat" mode.
PoP2 STRL: string lists (addition)
The first byte is the number of strings that follow.
Each string is zero-terminated.
PoP2 TXT4: compressed texts (addition)
First, you need the following table from PRINCE.EXE:
Code: Select all
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00: 00 20 61 65 74 6F 6E 69 73 72 64 6C 68 75 67 66 aetonisrdlhugf
10: 63 77 79 70 62 6D 6B 2C 76 53 41 2E 54 27 50 4D cwypbmk,vSA.T'PM
20: 78 42 43 49 52 47 44 57 48 71 45 2D 7A 4E 46 4B xBCIRGDWHqE-zNFK
30: 4C 30 6A 3A 35 31 59 4A 38 5C 55 3F 37 33 51 3B L0j:51YJ8\U?73Q;
40: 32 21 34 36 39 0D 0A 09 4F 56 58 5A 28 29 2A 2B 2!469♪◙○OVXZ()*+
50: 22 23 24 25 26 3C 3D 3E 2F 40 5B 5D 5E 5F 60 07 "#$%&<=>/@[]^_`•
60: 01 02 03 04 05 06 08 0B 0C 0E 0F 10 11 12 13 14 ☺☻♥♦♣♠◘♂♀♫☼►◄↕‼¶
70: 15 16 17 18 19 1A 1B 1C 1D 1E 1F 7B 7C 7D 7E 7F §▬↨↑↓→←∟↔▲▼{|}~⌂
The remaining part should be read as bits.
Read the first two bits as a binary number and name it w.
Repeat the following n times:
Read the next four bits, name it c. If c=0 then read the next w+5 bits into c. Use c as an index into the above table, and append the character to the output.
For example, PRINCE.DAT/TXT4/8010 contains the following:
05 00 42 D2 05 4C 00
05 00 tells that the text will contain five characters.
42 D2 05 4C 00 converted to binary: 01000010 11010010 00000101 01001100 00000000
If we split it up according to the above algorithm: 01|0000|101101|0010|0000|010101|0011|0000|000000
The first two bits are 01, so w+5 will be 6.
First we read 0000, so we read another 6 bits, which is 101101 = 0x2D. Row 2, column D contains 0x4E = 'N'.
Next we read 0010=0x02. Row 0, column 2 contains 0x61 = 'a'.
Third: 0000, read another 6 bits, 010101 = 0x15. Row 1, column 5 contains 0x6D='m'.
Fourth: 0011 = 0x03. Row 0, column 3 contains 0x65 = 'e'.
Fifth: 0000, read another 6 bits, 000000 = 0x00. Row 0, column 0 contains 0x00, which marks the end of strings in C.
Reading them together, we get "Name". This is one of the headers in the Hall of fame.
The following texts are contained in TXT4 resources: (in PRINCE.DAT)
The cheat activation word (resource 10)
The credits (resources 1000..1011) (*)
The text at the end of the demo version (resources 1020..1026) (*)
The texts on the options, save/load game, hall of fame, copy protection screens (8000..8016)
(*): After decompression, you'll get a string list. (Same format as STRL.)
PoP2 FONT: bitmap fonts (addition)
This format is also used for the hardcoded fonts of PoP1 and PoP2.
Header:
Code: Select all
1 byte: code of first character that has an image
1 byte: code of last character that has an image
2 byte: height above baseline
2 byte: height below baseline
2 byte: distance between lines
2 byte: spacing between characters
(last-first+1)*2 byte: addresses
Code: Select all
2 byte: height in pixels
2 byte: width in pixels (width in bytes=(width+7) div 8)
2 byte: 0
(width in bytes)*height bytes: data, each pixel row is padded to an integer number of bytes.
Sounds: (correction)
PoP1:
Code: Select all
1 byte: type of sound, 0=pcspeaker, 1=wave, 2=midi, +0x80 if looped (level door opening, 10015)
if pcspeaker:
2 bytes: ticks per second
for each sound:
2 byte: frequency in hertz
0=silence
1=silence at the beginning
2=cue point (marker) (used for triggering events when the music reaches this point)
1 byte: length in ticks
for cue points, this is the marker id, usually a lowercase letter
2 bytes: 0x0012
if wave:
2 bytes: sample rate (Hz)
2 bytes: number of samples, or 0 if compressed
2 bytes: always 0
1 byte: sample size, 8 if not compressed, 255 if compressed (PoP1 demo)
The following appears only if compressed:
2 bytes: number of samples after decompression
if midi:
The full contents of a MIDI file follow.
Code: Select all
1 byte: type of sound, 0=pcspeaker, 1=wave, 2=midi, +0x80 if looped
if pcspeaker:
Same as for PoP1.
if wave:
2 bytes: sample rate (Hz)
1 byte: sample size, 8 if not compressed, 255 if compressed (NISDIGI.DAT)
2 bytes: number of samples, or 0 if compressed
2 bytes: 0 for most sounds (*)
2 bytes: 0 for most sounds (*)
The following appears only if compressed:
2 bytes: number of samples after decompression
if midi:
Same as for PoP1.
(FRAGSND.DAT resources store a 15-byte something with a "MSeq" header.)
(*) Some looped sounds in DIGISND.DAT store offsets here. Their IDs are: 10007, 10047, 10054, 10258.
Code: Select all
read 1 byte: Sample
write Sample
repeat until there are not enough bytes in the output:
read 3 bits: B
if B==7 then:
Sample := Sample xor 0x80
write Sample
else:
repeat:
read B+2 bits (signed) : Delta
if Delta is the smallest negative number encodable in B+2 bits then: exit repeat
Sample := Sample + Delta
write Sample
end repeat
endif
end repeat