Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
## Bitmaps
|
||||
|
||||
Bitmaps are images using a palette of colours. System Shock uses bitmaps exclusively for its graphics and uses one byte
|
||||
as index per pixel - thus 256 colours possible. The resources store such bitmaps with content type ```0x02``` and they are always compound, even if only one bitmap is stored.
|
||||
|
||||
Bitmaps are stored with a header, the pixel data (sometimes compressed), and an optional private palette.
|
||||
|
||||
### Header
|
||||
|
||||
**Bitmap Header** (`FrameDesc` struct) (28 bytes)
|
||||
|
||||
0000 [4]byte Unused. Always zero. (used as pointer in the engine)
|
||||
0004 byte Type -- 0x02: Uncompressed; 0x04 Compressed
|
||||
0005 byte Align -- Unused
|
||||
0006 uint16 Flags
|
||||
0008 sint16 Width
|
||||
000A sint16 Height
|
||||
000C uint16 Stride
|
||||
000E byte WidthFactor
|
||||
000F byte HeightFactor
|
||||
0010 [4]int16 HotspotBox: Left, Top, Right, Bottom
|
||||
0018 sint32 Private palette offset
|
||||
|
||||
The ```Flags``` is set to value ```0x0001``` then palette index ```0x00``` should be treated as a transparent pixel instead of painting it black.
|
||||
> Compressed images appear to default to transparency if the resolution of the game is 320x200. Higher resolutions require the flag set to behave identical.
|
||||
>
|
||||
> Other factors may also govern whether the bitmap should be treated with transparency - videos and level textures have their special handling in this regard.
|
||||
|
||||
The ```WidthFactor``` and ```HeightFactor``` specify the index of the highest set bit of ```Width``` and ```Height```
|
||||
respectively. For example, if Width is 320 (0x140), then the WidthFactor is 8 (1<<8 == 0x100).
|
||||
|
||||
`HotspotBox` is a placeholder for different applications. It can define a rectangle, or it only defines a point (using the first two values) for anchoring.
|
||||
|
||||
> For the existing images in the original resource files, all images have a ```Stride``` equal to ```Width```.
|
||||
|
||||
### Pixel Data
|
||||
|
||||
After the header, the pixel data is serialized. If the image is uncompressed (Type ```0x02```),
|
||||
```Height``` x ```Stride``` bytes make up the image. Compressed images (Type ```0x04```) use a simple run-length-encoding (RLE).
|
||||
|
||||
#### Pixel Compression
|
||||
|
||||
The compressed byte-stream contains the following byte sequences:
|
||||
|
||||
00 nn zz write nn bytes of value zz
|
||||
nn .. 0 < nn < 0x80: copy the following nn bytes
|
||||
80 lo hi lo hi are a little-endian uint16 nnnn, to be handled as follows:
|
||||
80 00 00 End of compressed data (Skip to end)
|
||||
80 lo hi 0 < hi < 0x80: skip (nnnn & 0x7FFF) output pixel
|
||||
80 lo hi .. 0x80 <= hi < 0xC0: copy the following (nnnn & 0x3FFF) bytes
|
||||
80 lo C0 hi == 0xC0: undefined (not encountered)
|
||||
80 lo hi zz 0xC0 < hi: write (nnnn & 0x3FFF) bytes of value zz
|
||||
nn 0x80 < nn: skip (nn & 0x7F) output pixel
|
||||
|
||||
Three cases exist to compress pixel data: skip a list of pixel, compress a list of a constant
|
||||
value and copy an arbitrary array of bytes. For each of the three, two encoding variants are used: a short and a long form.
|
||||
If a count is to be serialized that is higher than the long-form can store, the encoder uses the long-form to reduce this number to finalize with the short-form.
|
||||
|
||||
> Skipping output pixel is relevant for compressing video clips. Skipped pixel will retain the colour value from the previous frame.
|
||||
> For still images, initialize the output buffer with 0x00.
|
||||
>
|
||||
> To implement a compressor that creates output closest to the original content, use the following sequence. The term "reference" means either a previous frame bitmap, or a fictual frame with all bytes 0x00.
|
||||
> * From an array of bytes with length X, calculate the count Z of bytes at the end that are identical to reference. Compress only N bytes, where N = X - (Z % 0x7FFF).
|
||||
> The final sequence ```80 00 00``` implies to skip to the end. The encoder
|
||||
> seems to have had a limit of counting only up to 0x7FFF, so there may be further (long) skip entries before.
|
||||
> * Iterate through the pixel data from start to N:
|
||||
> * Count the extent of static value bytes
|
||||
> * Count the number of bytes identical to reference
|
||||
> * Determine encoding method
|
||||
> * If the pixel value shall be retained from the reference, skip the count of bytes
|
||||
> * Otherwise, if the count is more than three, encode the count and the constant value
|
||||
> * Otherwise, count the extent of an arbitrary array; Abort when more than three static or identical values are found in sequence; Encode this array.
|
||||
> * Skip the input bytes that have been serialized and continue with the loop
|
||||
> * Encode end of compressed data sequence.
|
||||
|
||||
|
||||
### Private Palette
|
||||
|
||||
The format also allows for an optional palette, which is specific for the bitmap. If an image has no private palette, then this
|
||||
entry is not present and the resource data ends directly after the pixel data.
|
||||
Without a private palette, the colours of the bitmap are context specific. See [Palettes](Palettes.md) for more information.
|
||||
|
||||
Private palettes are used for the splash screens at the beginning of the game, where nothing else is shown.
|
||||
The ```Private palette offset``` in the bitmap header specifies the absolute offset *from the beginning* of the resource to the private palette entry. This means in case of a compound resource, this offset measures across the previous resource blocks as well.
|
||||
|
||||
**Private Palette** (772 bytes)
|
||||
|
||||
0000 [4]byte Unknown. Always 0x00, 0x00, 0x00, 0x01
|
||||
0004 [256*3]byte RGB values
|
||||
|
||||
> The only working examples for this can be found in ```splash.res```.
|
||||
>
|
||||
> The resource file ```gamescr.res``` also contains three bitmaps with a private palette, but these palettes are not used and are ignored by the engine.
|
||||
> The three bitmaps in question are the help overlay (```ALT+o```), and are shown with the game world in the background. Since in these
|
||||
> cases the world can't change its colour, the screen can't use a private palette. By extension, the side-effects of the "Berserk" patch
|
||||
> also affect the colours of the help overlay.
|
||||
> Furthermore, for these three bitmaps, the values of offset fields are wrong as well.
|
||||
@@ -0,0 +1,33 @@
|
||||
## Fonts
|
||||
|
||||
This chapter documents the data format of the fonts. The game uses [bitmap fonts](https://en.wikipedia.org/wiki/Computer_font#Bitmap_fonts) which describe the characters.
|
||||
|
||||
The fonts are stored in single blocks within resources of type ```0x03```. They can be found in the file ```gamescr.res```.
|
||||
|
||||
All glyphs in the bitmap are lined up in a single line. There are no Y offsets per glyph, and the height is always equal to the height of the bitmap. An array of ```X-Offsets``` points to the start of a glyph and the width is equal the difference to the next offset.
|
||||
|
||||
### Binary Format
|
||||
|
||||
**Font Header** (84 bytes)
|
||||
|
||||
0000 uint16 Font Type -- 0x0000: Monochrome, 0xCCCC: Color
|
||||
0002 [34]byte Unknown -- always 0x00
|
||||
0024 int16 First character index (inclusive)
|
||||
0026 int16 Last character index (inclusive)
|
||||
0028 [32]byte Unknown -- always 0x00
|
||||
0048 int32 Offset to X-Offsets, from beginning of data
|
||||
004C int32 Offset to bitmap, from beginning of data
|
||||
0050 int16 Width of bitmap in bytes
|
||||
0052 int16 Height of bitmap in bytes
|
||||
|
||||
**X-Offsets** (2*N bytes)
|
||||
|
||||
The X-Offsets is an array of ```int16``` values. The length of the array is equal to the number of characters the font covers, plus one. The width of one glyph is always equal the difference to the next offset, even for the last one.
|
||||
|
||||
For a monochrome font, the offset is counted in bits, always beginning with the MSB.
|
||||
|
||||
**Bitmap** (W*H bytes)
|
||||
|
||||
Monochrome fonts have the glyphs packed as bits within the bytes, color fonts use the bytes as palette indices.
|
||||
|
||||
> Fonts that have an outline, primarily seen with the HUD fonts, don't store the outline information in the font bitmaps. The outlines are rendered at runtime.
|
||||
@@ -0,0 +1,182 @@
|
||||
## 3D Object Geometry
|
||||
|
||||
This chapter documents the data format of the three dimensional objects encountered in the game (tables, ICE in cyberspace, crates...).
|
||||
|
||||
All the geometry is stored in the file ```obj3d.res``` with content type ```0x0F```. Each resource in this file is compound with one data block. There is one object per resource (and data block).
|
||||
|
||||
Each three dimensional ```model``` consists of a list vertex definitions and a structure of ```nodes```, with the model itself being the root node. A node contains a list of ```anchors```, with each one having a normal vector and a reference point.
|
||||
Up to exactly one anchor per node may branch out to two further nodes (creating a binary tree). Further anchors contain lists of rendered ```faces```. Faces are either texture mapped or filled with a single colour.
|
||||
|
||||
### Binary Format
|
||||
|
||||
The data starts with 8 bytes of header information, followed by a list of "commands" which describe the structure.
|
||||
|
||||
**Geometry Header** (8 bytes)
|
||||
|
||||
0000 int16 Unknown -- always 0x0027
|
||||
0002 int16 Unknown -- always 0x0008
|
||||
0004 int16 Unknown -- always 0x0002
|
||||
0006 int16 Total number of Faces
|
||||
|
||||
> The ```Total number of Faces``` value does not seem to be authorative. There are a few entries in the resouce file which state a higher count than actually in the model.
|
||||
|
||||
**Model Command** (2+N bytes)
|
||||
|
||||
0000 int16 Model Command Identifier
|
||||
0002 N Command specific content
|
||||
|
||||
> There is no framing structure which would allow skipping command entries, so an implementation has to know of all commands beforehand.
|
||||
|
||||
|
||||
**fixed32/16** (4 bytes)
|
||||
|
||||
In the following, coordinates (and their offsets) are serialized as fixed floating point values in a 32/16 format.
|
||||
|
||||
0000 sint16 Integer value
|
||||
0002 uint16 Fraction (in 1/65536 units)
|
||||
|
||||
> For a simple implementation, it is sufficient to read the value as a ```sint32```, convert it to floating point and divide by 65536.0 .
|
||||
|
||||
|
||||
**Vector** (12 bytes)
|
||||
|
||||
A vector is serialized with three coordinates:
|
||||
|
||||
0000 fixed32/16 X
|
||||
0004 fixed32/16 Y
|
||||
0008 fixed32/16 Z
|
||||
|
||||
|
||||
### Geometry Commands
|
||||
|
||||
#### Vertex Definitions
|
||||
Vertices are always (and only) defined at the beginning of the model (the root node). Three ways exist: define a single vertex, multiple vertices or references to previous ones with offsets.
|
||||
|
||||
##### Define Single Vertex
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0015
|
||||
0002 int16 Unknown -- always 0x0000
|
||||
0004 Vector Vertex coordinates
|
||||
|
||||
##### Define Multiple Vertices
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0003
|
||||
0002 int16 Number of vertices
|
||||
0004 int16 Unknown -- always 0x0000
|
||||
0006 [N]Vector Coordinates of vertices
|
||||
|
||||
##### Define Vertex (One Offset)
|
||||
|
||||
0000 int16 Model Command Identifier -- X: 0x000A, Y: 0x000B, Z: 0x000C
|
||||
0002 int16 New vertex index
|
||||
0004 int16 Reference vertex index
|
||||
0006 fixed32/16 Offset value to coordinate as per command identifier
|
||||
|
||||
> In the existing files, the ```New vertex index``` has always been found to equal the current amount of known vertices, appending at the end of the list.
|
||||
|
||||
##### Define Vertex (Two Offsets)
|
||||
|
||||
0000 int16 Model Command Identifier -- XY: 0x000D, XZ: 0x000E, YZ: 0x000F
|
||||
0002 int16 New vertex index
|
||||
0004 int16 Reference vertex index
|
||||
0006 fixed32/16 Offset value to first coordinate as per command identifier
|
||||
0010 fixed32/16 Offset value to second coordinate as per command identifier
|
||||
|
||||
> In the existing files, the ```New vertex index``` has always been found to equal the current amount of known vertices, appending at the end of the list.
|
||||
|
||||
> To generate a list of vertex definitions as close as possible to the original files, follow these steps:
|
||||
> * Determine count of dissimilar vertices:
|
||||
> * From the list of vertices, iterate reverse from the tail.
|
||||
> * For each vertex, search the list below the current index if there is another vertex which has at least one identical coordinate.
|
||||
> * Abort this loop if the current vertex has no similar other. Vertices from beginning to the current index are all dissimilar.
|
||||
> * Depending on the count of dissimilar vertices, define them with either the single definition or the multiple definition.
|
||||
> * Now iterate forward through the list (until end), beginning with the first vertex after the dissimilar ones.
|
||||
> * For each vertex, again find another most similar from those coming before it (including those already handled in this loop).
|
||||
> * Compare the coordinates again.
|
||||
> * If two are identical, write a single offset definition (for the one coordinate not equal).
|
||||
> * If one is identical, write a double offset definition (for the two coordinates not equal).
|
||||
>
|
||||
> This list assumes the vertices are all unique and they were sorted to have the dissimilar ones come first. This must be done in
|
||||
> combination with modifying the face definitions as they refer to the vertices via indices.
|
||||
> Following this sequence will create a vertex definition with equal byte length as in the original. The differences to the original files are in which
|
||||
> reference vertices were taken.
|
||||
|
||||
#### Node Control
|
||||
|
||||
##### End Of Node
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0000
|
||||
|
||||
Every node is finished with this command.
|
||||
|
||||
##### Define Node Anchor
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0006
|
||||
0002 Vector Normal vector
|
||||
000E Vector Reference point
|
||||
001A int16 Byte offset to 'left' branch
|
||||
001C int16 Byte offset to 'right' branch
|
||||
|
||||
The offset values are based on the start of the command. Branch data comes only after the current node has ben finished; There is always only one ```End Of Node``` command pending. Branches are serialized depth-first.
|
||||
|
||||
> In the existing files always the 'right' branch has been found to be serialized first. Although only a few cases, several layers can be found within one branch. If a branch is not needed, it contains only the ```End Of Node``` command.
|
||||
|
||||
|
||||
#### Face Control
|
||||
|
||||
##### Define Face Anchor
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0001
|
||||
0002 int16 Anchor byte length (measured from start of this command)
|
||||
0004 Vector Normal vector
|
||||
0010 Vector Reference point
|
||||
001C N Face drawing commands
|
||||
|
||||
Faces within one ```Define Face Anchor``` command are either painted in a single colour (with an optional shade) or texture mapped.
|
||||
|
||||
##### Coloured Face
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0004
|
||||
0002 int16 Vertex count
|
||||
0004 [N]int16 Vertex indices
|
||||
|
||||
This command always has either a ```Set Colour``` or a ```Set Colour And Shade``` command before. It may be that several ```Coloured Face``` commands refer to the same setting.
|
||||
|
||||
##### Set Colour
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0005
|
||||
0002 int16 Colour palette index
|
||||
|
||||
##### Set Colour And Shade
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x001C
|
||||
0002 int16 Colour palette cross index
|
||||
0004 int16 Shade value (0..3)
|
||||
|
||||
The ```Colour palette cross index``` refers to a small lookup table from the corresponding static object properties. This lookup table is a list of always 6 palette indices.
|
||||
|
||||
> The data files contain cross indices of 0..6 . TSSHP skips those with value 0 and subtracts 1 from the remaining 6 values.
|
||||
|
||||
##### Texture Mapping
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0025
|
||||
0002 int16 Number of entries
|
||||
0004 [N]Entry Mapping entries
|
||||
|
||||
**Mapping Entry (10 bytes)**
|
||||
|
||||
0000 int16 Vertex index
|
||||
0002 fixed32/16 texture u coordinate
|
||||
0006 fixed32/16 texture v coordinate
|
||||
|
||||
##### Textured Face
|
||||
|
||||
0000 int16 Model Command Identifier -- 0x0026
|
||||
0002 int16 Texture identifier
|
||||
0004 int16 Vertex count
|
||||
0006 [N]int16 Vertex indices
|
||||
|
||||
This command always has a ```Texture Mapping``` command before.
|
||||
|
||||
The ```Texture identifier``` references a texture with a resource identifier base of ```0x01DB```, found in ```citmat.res```.
|
||||
The value of 0 specifies special, object specific handling.
|
||||
@@ -0,0 +1,40 @@
|
||||
## Palettes
|
||||
|
||||
The engine uses a graphic mode with 256 colors per pixel. The graphic resources (images and videos) use bitmaps together
|
||||
with palette information to resolve a pixel value (1 byte) to the corresponding RGB value (3x1 byte).
|
||||
|
||||
Depending on the game-state, different palettes are in use. The splash screens at the beginning are created using a
|
||||
```Private Palette``` (see the [bitmap](Bitmaps.md) format). Cutscenes have their own palette(s) and during the game,
|
||||
one specific palette is used throughout. This game palette is stored in ```gamepal.res```.
|
||||
|
||||
### Format
|
||||
|
||||
Palettes always have a size of 768 bytes, for 256 colors with 1 byte for each of the three colour channels.
|
||||
|
||||
**Palette** (768 bytes)
|
||||
|
||||
0000 [256*3]byte RGB values
|
||||
|
||||
### Transparency
|
||||
|
||||
Palette index 0 is used as a marker for fully transparent pixel. For instance, various decoration or critter sprites need
|
||||
to have transparent areas in order to lose the rectangular shape.
|
||||
|
||||
### Pixel Animation / Palette Looping
|
||||
|
||||
During the game various textures can be observed with small animations. Most typically seen with LED-like blinking effects.
|
||||
Instead of implementing this with multiple frames, the engine simply loops through the colors within a few groups.
|
||||
|
||||
The following groups (palette index values) are looped:
|
||||
|
||||
0x03 - 0x07: Changed every ~1200 msec
|
||||
0x0B - 0x0F: Changed every ~700 msec
|
||||
0x10 - 0x14: Changed every ~360 msec
|
||||
0x15 - 0x17: Changed every ~1800 msec
|
||||
0x18 - 0x1A: Changed every ~1430 msec
|
||||
0x1B - 0x1F: Changed every ~1080 msec
|
||||
|
||||
> These groups are looped even while the game is paused.
|
||||
>
|
||||
> Above switching times have been determined by counting frames of a video recording from dosbox, running within a VM.
|
||||
> Exact values may be different.
|
||||
@@ -0,0 +1,45 @@
|
||||
## Sound Effects
|
||||
|
||||
Sound effects are stored in ```digifx.res```, which contains resources of data type ```0x07```.
|
||||
The resources have IDs in the range from ```0x00C9``` to ```0x013B```, are uncompressed and have one effect per resource.
|
||||
|
||||
Each resource contains one sound effect, which is stored in the
|
||||
[Creative Voice File](http://wiki.multimedia.cx/index.php?title=Creative_Voice) format.
|
||||
|
||||
Although the format would allow for different sample formats, only unsigned 8-bit PCM audio (MIME type ```audio/L8```)
|
||||
is supported and expected. The sound effects don't use any special block types of the file format,
|
||||
only one ```Sound data``` block is present, followed by a ```Terminator``` block identifier. All effects have one channel (mono).
|
||||
|
||||
### Format
|
||||
The following is an extract of the above linked page, specialized to the use by the engine.
|
||||
|
||||
***Sound Effect***
|
||||
|
||||
0000 [19]byte Identifier text "Creative Voice File"
|
||||
0013 byte Identifier terminator: 0x1A
|
||||
0014 int16 Length of header (commonly 0x1A)
|
||||
0016 uint16 Version number (0x010A)
|
||||
0018 uint16 Version number validation
|
||||
|
||||
001A byte Sound data block type: 0x01
|
||||
001B [3]byte Length of block data (sample count + 2)
|
||||
|
||||
001E byte Frequency divisor
|
||||
001F byte Codec ID (audio/L8: 0x00)
|
||||
0020 []byte Samples
|
||||
|
||||
NNNN byte Terminator block type: 0x00
|
||||
|
||||
***Version Number***
|
||||
|
||||
The version number is encoded as a major and minor number, in the high and low bytes respectively.
|
||||
|
||||
The validation value is the result of building the binary complement of the version number and adding the constant ```0x1234```.
|
||||
|
||||
***Frequency Divisor***
|
||||
|
||||
The frequency divisor is used to calculate the sample rate with the following formula:
|
||||
|
||||
```
|
||||
sampleRate := 1000000 / (256 - frequencyDivisor)
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
## Texts
|
||||
|
||||
All texts are stored in the language specific files ```cybstrng.res```, ```frnstrng.res``` and ```gerstrng.res```.
|
||||
These files contain 188 resources of type ```0x01```. They are compound with several blocks. Each block contains
|
||||
one string, which is always 0x00 terminated.
|
||||
|
||||
While the English strings may be readable according to standard ASCII, with the French and German options, texts
|
||||
need to be interpreted according to the DOS [code page 437](http://en.wikipedia.org/wiki/Code_page_437).
|
||||
|
||||
> Since texts are always presented through the game fonts, any one-byte codepage can be used - As long as the
|
||||
> corresponding font bitmaps match for the requested characters (bytes).
|
||||
|
||||
### Control Characters
|
||||
|
||||
For texts with multiple lines, ```0x0A``` acts as a newline marker.
|
||||
|
||||
```0x02``` is a soft-hyphen between syllables. Not all texts contain this character.
|
||||
|
||||
### Resource Overview
|
||||
|
||||
* 0x0867 Trap messages, one per block
|
||||
* 0x0868 Wall texts
|
||||
* 0x0869 Switch/Panel descriptions
|
||||
* 0x086A Texture descriptions (Note: From block 273 onwards of the 512 blocks, the text is always "bug")
|
||||
* 0x086B "Can't use X" texts for textures (Note: bug texts are simply "can't be used")
|
||||
* 0x086C HUD texts/messages
|
||||
* 0x086D Object short names
|
||||
* 0x086E Messages of the targeting unit
|
||||
* 0x086F HUD messages (top)
|
||||
* 0x0870 Log category titles ("Level 1 Logs"...)
|
||||
* 0x0871 Action messages
|
||||
* 0x0872 Intro cutscene subtitles
|
||||
* 0x0873 Death cutscene subtitles
|
||||
* 0x0874 Win cutscene subtitles
|
||||
* 0x0875 System scanner label texts
|
||||
* 0x0876 Sign/Logo names
|
||||
* 0x0877 Monitor/Screen texts
|
||||
* 0x0878 Cyberspace toggle texts
|
||||
* 0x0879 Access card names
|
||||
* 0x087A Datalet messages (cyberspace nodes)
|
||||
* 0x087B Menu texts
|
||||
* 0x087C Credits
|
||||
* 0x087D Keyboard help
|
||||
* 0x087E Various HUD messages
|
||||
* 0x087F Tooltip texts
|
||||
* 0x0880 Main menu texts
|
||||
* 0x0881 Endgame statistics texts
|
||||
* 0x0882 Inventory description/version texts
|
||||
* 0x0883 Texts for MFD games
|
||||
|
||||
* 0x0024 Object names (readable)
|
||||
|
||||
* 0x003C - 0x0046 Paper text messages
|
||||
|
||||
#### [Electronic Messages](../content/ElectronicMessages.md)
|
||||
|
||||
* 0x0989 - 0x09B6 E-Mails
|
||||
* 0x09B8 - 0x09BD Level R logs
|
||||
* 0x09C8 - 0x09D7 Level 1 Logs
|
||||
* 0x09D8 - 0x09E4 ...
|
||||
* 0x09E8 - 0x09F0
|
||||
* 0x09F8 - 0x09FE
|
||||
* 0x0A08 - 0x0A13
|
||||
* 0x0A18 - 0x0A21
|
||||
* 0x0A28 - 0x0A30
|
||||
* 0x0A38 - 0x0A3D Level 8 logs
|
||||
* 0x0A98 - 0x0AA4 Data fragments
|
||||
@@ -0,0 +1,57 @@
|
||||
## Video Clips
|
||||
|
||||
Video clips are made out of a sequence of standard [bitmaps](Bitmaps.md). The meta-information about a video clip
|
||||
is stored in resources of type ```0x04```. This information block refers to the resource that contains the frames, which
|
||||
is stored in the same resource file.
|
||||
|
||||
Contrary to [MOVI Resources](moviResources.md), video clips contain only the frame information - nothing else.
|
||||
|
||||
### Format
|
||||
|
||||
**Video Clip Sequence Description** (16+5*N bytes)
|
||||
|
||||
0000 int16 Width of video.
|
||||
0002 int16 Height of video.
|
||||
0004 int16 Resource ID of frames, referring to a compound resource
|
||||
0006 [6]byte Unknown. Always 0x00
|
||||
000C int16 Unknown. TriOp intro: 0x0001, 0x0000 otherwise
|
||||
000E N*[5]byte Sub-table of N sequence entries
|
||||
MMMM int16 Unknown. Always 0x010C
|
||||
|
||||
**Video Clip Sequence Entry** (5 bytes)
|
||||
|
||||
0000 byte Unknown. Always 0x04.
|
||||
0001 byte First frame
|
||||
0002 byte Last frame
|
||||
0003 int16 Frame time (in milliseconds)
|
||||
|
||||
|
||||
### Video Mails
|
||||
|
||||
Video mails are small animated movies sent as mail during the game, all stored in ```vidmail.res```.
|
||||
|
||||
A single video mail is split up in several sequences. Most notably, the TriOp intro is a reused sequence for all mails.
|
||||
The resource file contains 12 such sequences: Per sequence there is one resource of type ```0x04```
|
||||
and a corresponding bitmap resource (containing the frames in blocks).
|
||||
|
||||
Video mails have a size of 200x100 pixel and are the same for the HD and CD releases. They share the palette of the
|
||||
game (```gamepal.res```).
|
||||
|
||||
#### Hardcoded constants
|
||||
|
||||
The classic engine has the following hardcoded constants:
|
||||
|
||||
* There are 6 video mails, ordered as: "shield", "grove", "bridge", "laser", "explode", and "status"
|
||||
* "laser" has two sequences, "explode" has five sequences. The twelth sequence is the intro, having index 10, right before the status.
|
||||
* The first ID for the sequence descriptions is `0x0A4C`
|
||||
* The first ID for the sequence bitmaps is `0x0A40`
|
||||
|
||||
> The engine always plays the intro first, then the requested video mail. The bundling of sequences (like for "explode") is hardcoded,
|
||||
> so if another video mail could either split itself up, or simply have only one frame (or none at all) at the end.
|
||||
|
||||
### Cutscenes
|
||||
|
||||
The files ```start1.res```, ```death.res``` and ```win1.res``` contain low-res variants of the corresponding
|
||||
cutscenes.
|
||||
|
||||
The palettes for these cutscenes are stored in ```cutspal.res```.
|
||||
@@ -0,0 +1,258 @@
|
||||
## MOVI Resources
|
||||
|
||||
Resources with content type `0x11` are a media container themselves, similar to audio/video container formats.
|
||||
These resources are called "MOVI Resources" since the initial tag is the four letter code `MOVI`.
|
||||
|
||||
### Format
|
||||
|
||||
Such a resource contains the following components:
|
||||
* a header, describing meta information about the media,
|
||||
* a palette, with initial colours,
|
||||
* an index, describing the following data entries, and
|
||||
* the data entries.
|
||||
|
||||
**MOVI Header** (`MovieHeader` struct) (1024 bytes)
|
||||
|
||||
0000 uint32 magicId Container tag 0x49564F4D ("MOVI")
|
||||
0004 int32 numChunks Number of chunk entries in file
|
||||
0008 int32 sizeChunks Size of index table
|
||||
000C int32 sizeData Size of content (all data entries)
|
||||
0010 fix totalTime Length of media (in seconds)
|
||||
0014 fix frameRate Frame rate (informational only)
|
||||
0018 int16 frameWidht Width of video
|
||||
001A int16 frameHeight Height of video
|
||||
001C int16 gfxNumBits For videos may be 0x0008, 0x000F or 0x0018, 0x0000 otherwise
|
||||
001E int16 isPalette Palette presence in file. For videos 0x0001, 0x0000 otherwise
|
||||
0020 int16 audioNumChans Numbers of audiochannels 0x0000 - no audio, 0x0001 - mono, 0x0002 stereo
|
||||
0022 int16 audioSampleSize Audio sample size, 0x0001 - 8-bit, 0x0002 - 16-bit
|
||||
0026 fix audioSampleRate Sample rate of audio, in Hz (0x2B770000 - 11127 Hz, 0x56EE0000 - 22254 Hz)
|
||||
0030 uint8[216] reserved Reserved
|
||||
0108 uint8[768] palette Palette
|
||||
|
||||
If the index table size is greater than the total of index entry number times one entry size, the remaining bytes are zero.
|
||||
|
||||
> The index table sizes come in multiples of 0x0400. The rough rule for recreating the existing index sizes is:
|
||||
> Required size less than 0x0400 -> Use 0x0400. Otherwise: Round up to next 0x0400 border and add another 0x0400.
|
||||
|
||||
**Palette** (256 * 3 bytes)
|
||||
|
||||
The palette is always present, even if the MOVI contains only audio. In such cases all the colour channels are 0x00.
|
||||
|
||||
**Movie chunk Entry** (`MovieChunk` struct) (8 bytes)
|
||||
|
||||
0000 uint32 Chunk info (bitfield)
|
||||
0004 uint32 offset Offset to data (from beginning)
|
||||
|
||||
First 4 bytes are uint32 bit field which follow structure:
|
||||
|
||||
bits 00-23 24 time Fixed point timestamp from movie start (in seconds)
|
||||
bits 24-24 1 played Has this chunk been clocked out? (Used by engine internally)
|
||||
bits 25-28 4 flags Chunk type specific flags
|
||||
bits 39-31 3 chunkType Chunk type (see below)
|
||||
|
||||
**Chunk type specific flags** may be:
|
||||
|
||||
For Video chunk type (0x01 MOVIE_CHUNK_VIDEO)
|
||||
|
||||
0x0F MOVIE_FVIDEO_BMTMASK 4 bits of flags is bmtype
|
||||
0x0F MOVIE_FVIDEO_BMF_4X4 4x4 movie format
|
||||
|
||||
> Not clear, why those flags uses same code, this may be bug or leftovers from reusing library component
|
||||
|
||||
For Palette chunk type (0x04 MOVIE_CHUNK_PALETTE)
|
||||
|
||||
0x00 MOVIE_FPAL_SET Set palette from data
|
||||
0x01 MOVIE_FPAL_BLACK Set palette to black
|
||||
0x07 MOVIE_FPAL_EFFECTMASK 4 bits of flags is effect
|
||||
0x08 MOVIE_FPAL_CLEAR If bit set, clear screen
|
||||
|
||||
For Table chunk type (0x05 MOVIE_CHUNK_TABLE)
|
||||
|
||||
0x01 MOVIE_FTABLE_COLORSET Table is color set
|
||||
0x02 MOVIE_FTABLE_HUFFTAB Huffman table (compressed)
|
||||
|
||||
### Chunk Types
|
||||
|
||||
#### 0 End of chunk (MOVIE_CHUNK_END)
|
||||
|
||||
This type is always present as the last entry in the index. It is used to calculate the length of the previous entry as
|
||||
the table does not contain a length field for entries.
|
||||
|
||||
The timestamp of this entry should match the media length from the header.
|
||||
|
||||
#### 1 Video (MOVIE_CHUNK_VIDEO)
|
||||
|
||||
Video frames are compressed in two variants, for low and high resolution.
|
||||
|
||||
Frames are either "full" frames, which set all, or nearly all, pixels in the buffer, typically at a scene change. Other frames are "delta" frames, which re-use (most of) the previous pixel in the buffer. This re-use is done by writing/using the palete index 0x00, which is meant to be transparent.
|
||||
|
||||
##### Low-resolution Video
|
||||
|
||||
Low-resolution video is using type `0x21`. Data contains a bounding box and then the compressed frame using the [compression algorithm](./Bitmaps.md#pixel-compression) for standard bitmaps.
|
||||
|
||||
**Low Resolution Video Data** (8+N bytes)
|
||||
|
||||
0000 [4]int16 Bounding Box
|
||||
0008 []byte Compressed pixel data
|
||||
|
||||
> In the existing videos, the bounding box is always `0, 0, 320, 150`, specifying the same size as the video itself.
|
||||
|
||||
##### High-resolution Video
|
||||
|
||||
High-resolution video is using type `0x79` with a more complex compression algorithm. This algorithm works in conjunction of the `Table` types. See [below](#high-resolution-video-compression) for details on the algorithm.
|
||||
|
||||
**High Resolution Video Data** (2+N bytes)
|
||||
|
||||
0000 int16 Offset to Mask-stream in bytes, from beginning (XXXX)
|
||||
0002 []byte Bitstream
|
||||
XXXX []byte Mask-stream
|
||||
|
||||
The `Bitstream` is a stream of serialized big-endian integers. The `Mask-stream` is a stream of serialized little-endian integers of different lengths (2, 4, 6, and 8 bytes).
|
||||
|
||||
The order and layout of the values in the streams is determined by the algorithm. The decoder will want to read past the end of the streams in a few cases. For these cases the missing bits/bytes are to be set to zero. The encoder removed any trailing 0x00 bytes from these streams in order to save further space.
|
||||
|
||||
#### 2 Audio (MOVIE_CHUNK_AUDIO)
|
||||
|
||||
The data entry contains unsigned 8-bit PCM samples.
|
||||
|
||||
> Audio is chunked into entries of 8192 bytes, even if the MOVI contains only audio.
|
||||
|
||||
#### 3 Text (MOVIE_CHUNK_TEXT)
|
||||
|
||||
**Subtitle Entry** (`MovieTextItem` struct) (16+N bytes)
|
||||
|
||||
0000 uint32 tag Subtitle Control
|
||||
0004 uint32 offset Offset to text string, always 0x10
|
||||
0008 [8]byte Unused -- always 0x00, as they are skipped
|
||||
0010 []byte Null terminated string
|
||||
|
||||
Subtitle text will be shown with the color from palette index 255 (last entry).
|
||||
|
||||
##### Subtitle Controls
|
||||
|
||||
`'AREA'` (uint32 `0x41455241`) is for setting up the subtitle space. It defines the rectangle, in pixel (left, top, right, bottom), where the text is to be displayed. Example:
|
||||
|
||||
```
|
||||
20 365 620 395 CLR
|
||||
```
|
||||
|
||||
`'STD '` (uint32 `0x20445453`), `'FRN '` (uint32 `0x204E5246`) and `'GER '` (uint32 `0x20524547`) specify the actual subtitle text in the respective language, with `'STD '` being the default language (English).
|
||||
|
||||
#### 4 Palette (MOVIE_CHUNK_PALETTE)
|
||||
|
||||
There are two palette entry types: `0x4C` and `0x04`. For any change in the palette, two entries are in the container. Both entries always come in sequence (first `0x4C`, then `0x04`) and both have identical timestamp values.
|
||||
|
||||
The entry with type `0x4C` will have a length of 0 and the same data offset as the next entry, `0x04`.
|
||||
The entry with type `0x04` points to data of 0x300 bytes (= 256*3) which describes the new palette for upcoming frames.
|
||||
|
||||
> At least for high-resolution videos, the next frame after a palette change does not always entirely set the complete pixel buffer for the next frame.
|
||||
> In the intro video for instance, SHODAN's gaining of conscience scene has a few pixel left over from the previous scene.
|
||||
>
|
||||
> As a recommendation, a decoder should wipe the current frame buffer with 0x00 before applying a new palette/drawing further frames.
|
||||
> An alternative could be to remap all pixel values with palette indices as close as possible to the new palette, though this might alter the resulting frame too much.
|
||||
|
||||
#### 5 Table (MOVIE_CHUNK_TABLE)
|
||||
|
||||
Table entries are used in combination with video type `0x79` entries for high-resolution videos. There are two table types:
|
||||
* `0x05`: Palette lookup list
|
||||
* `0x0D`: Control table
|
||||
|
||||
See [below](#high-resolution-video-compression) for details on the algorithm.
|
||||
|
||||
Such entries are common to several frames. For any change, two entries are in the container. Both entries always come in sequence (first `0x05`, then `0x0D`).
|
||||
|
||||
> In the existing files, a change of tables is always followed by a change of palette. The exception is the first scene, which uses the initial palette from the MOVI header.
|
||||
|
||||
##### Palette lookup list
|
||||
|
||||
This entry, with type `0x05` is a simple lookup list. Every entry of type byte is a palette index.
|
||||
This list is truncated as well. The decoder will want to read past the end of this stream, in which case zero bytes must be returned.
|
||||
|
||||
##### Packed Control Words
|
||||
|
||||
Table type `0x0D` contains a runlength encoded list of control words.
|
||||
|
||||
**Packed Control Words** (4+(N*4) bytes)
|
||||
|
||||
0000 int32 Unpacked size
|
||||
0004 []Packed Packed entries
|
||||
|
||||
The `Unpacked size` field equals the number of control words, times three (specifying the size in bytes).
|
||||
The remainder of the entry is then the list of packed control words, from which the actual list of control words is unpacked.
|
||||
|
||||
**Packed Entry** (4 bytes)
|
||||
|
||||
0000 byte Word count
|
||||
0001 uint24 Control Word
|
||||
|
||||
The field `Word count` specifies how often the `Control Word` shall be extracted. A value of 0 is never encountered.
|
||||
|
||||
### High-resolution Video Compression
|
||||
|
||||
High-resolution video compression uses scene-specific tables shared amongst several frames and frame-specific bit- and mask-streams. Frames in this compression are made up of tiles with a size of 4x4 pixel each. For the high-resolution videos of 600x300 pixel this means 150x75 tiles. Tiles are coloured according to different types of control words, from left to right, top to bottom.
|
||||
|
||||
**Control Word** (3 bytes, masked)
|
||||
|
||||
0xF00000 Count value
|
||||
0x0E0000 Type
|
||||
0x01FFFF Parameter
|
||||
|
||||
|
||||
As a rough guideline, the frame-specific bit-stream provides index values into the control word table. According to the type of the control word, the current tile is coloured either directly, or with the additional help of the mask-stream and palette lookup list.
|
||||
|
||||
A note on the bit-stream: Reading from this stream does not automatically advance the current position. Advancing the bit-stream is a separate operation, which may advance with a different number of bits than were previously read. This may cause re-use of some bits to increase compression.
|
||||
|
||||
As noted above, the bit- and mask-stream, as well as the palette lookup list, have possible trailing zero values removed in the files. When the decoder wants to read beyond their end, they must provide values set to 0.
|
||||
|
||||
|
||||
#### Decoder sequence
|
||||
|
||||
A possible decoder is implemented using the following sequence:
|
||||
|
||||
* Iterate over the tiles, outer loop is the rows, inner loop the columns. Finish the loops if either all tiles are covered, or the bit-stream has been exhausted. The stream is exhausted if the current position is at the end of or beyond the available range.
|
||||
* Read a 12-bit value from the bit-stream. Use this value as an index into the control word table.
|
||||
* If the `Count value` of the control word is zero
|
||||
* A `Count value` of zero indicates a `Long Offset`. In this case, the `Type` and `Parameter` fields are taken together to form a 20-bit `Long Offset` value.
|
||||
* Advance the bit-stream by 8 bits
|
||||
* Enter a loop as long as the current control word indicates a `Long Offset`.
|
||||
* Advance the bit-stream by 4 bits
|
||||
* Read a 4-bit value from the bit-stream. Add this value to the `Long Offset` of the current control word and use the result as another index into the control word table. The retrieved entry becomes the new current control word.
|
||||
* Advance the bit-stream by the `Count` value of the current control word. This may indicate a different amount of bits than previously read, even less.
|
||||
* If the type of the current control word is `6`, use the previously processed control word as the current one for further processing.
|
||||
* If the type of the current control word is `5`, tiles shall be skipped
|
||||
* Read a 5-bit value from the bit-stream and advance it by 5 bits. This is the `Skip Count` value.
|
||||
* A `Skip Count` of `0x1F` skips the remainder of the current tile row
|
||||
* Any other value skips the given amount of columns, additionally to the current one. A value of 0 skips only the current tile.
|
||||
* Any type in the range of `0` to `4` colours the current tile. See below.
|
||||
* Next loop iteration
|
||||
|
||||
> Type `7` is never encountered. According to the documentation of TSSHP, this has the same meaning as `6`: use previous control word.
|
||||
|
||||
##### Encoder hints
|
||||
|
||||
* Using the previous control word (type `6`) is not allowed for the first tile of a row. It can't repeat the last operation from the previous row.
|
||||
* Usage of "transparency" byte `0x00` is only processed if at the first index of a palette lookup. See below.
|
||||
|
||||
##### Colouring a tile
|
||||
|
||||
To colour a tile, the `Parameter` value of the current control word is used, as well as the `mask-stream` and the `palette lookup list`. The 16 pixel of a tile are coloured from left to right, top to bottom.
|
||||
|
||||
The palette value for a pixel is determined by an index into a small `lookup array` of palette indices. Depending on the type of the control word, this lookup array is either a made up one, or a segment of the `palette lookup list`. The length of a `lookup array` is always a power of 2 and has possible lengths of 2, 4, 8 or 16.
|
||||
|
||||
The corresponding index values into such lookup arrays have bit sizes of 1, 2, 3 or 4 bits. The 16 index values for a tile are packed into a `mask integer` of 2, 4, 6 or 8 bytes. Depending on the type of the control word, this `mask integer` is made up or read from the `mask-stream`.
|
||||
|
||||
> For example, a lookup array of 4 palette values requires an index size of 2 bits, resulting in a mask integer of 4 byte.
|
||||
>
|
||||
> For pixel coordinates given in x,y: Mask integer bits 0-1 colour pixel 0,0; mask integer bits 2-3 colour pixel 1,0; ...
|
||||
> mask integer bits 28-29 colour pixel 2,3 and mask integer bits 30-31 colour pixel 3,3.
|
||||
|
||||
|
||||
| Type | Lookup Array | Mask Integer |
|
||||
|:----:|--------------------------------------------------------|-------------------------|
|
||||
| 0 | `[ Parameter >> 0 & 0xFF, Parameter >> 8 & 0xFF ]` | Static `0xAAAA` |
|
||||
| 1 | `[ Parameter >> 0 & 0xFF, Parameter >> 8 & 0xFF ]` | 2 byte from mask-stream |
|
||||
| 2 | Parameter is start index into list, 4 entries | 4 byte from mask-stream |
|
||||
| 3 | Parameter is start index into list, 8 entries | 6 byte from mask-stream |
|
||||
| 4 | Parameter is start index into list, 16 entries | 8 byte from mask-stream |
|
||||
|
||||
If the lookup array index is `0`, and the entry ix `0x00`, use the color from the previous frame.
|
||||
Reference in New Issue
Block a user