Compare commits

..

1 Commits

Author SHA1 Message Date
an 92eeecd0a6 PNG initial code 2019-03-18 08:24:20 -04:00
539 changed files with 16961 additions and 17600 deletions

10
.gitignore vendored
View File

@ -1,10 +1,6 @@
**/*.rs.bk
*.bat
*.user
.DS_Store
/out
/target
/tycho/resources/icons.qrc
/tycho/resources/icons/*
/out
*.res
**/*.rs.bk
Cargo.lock
perf.data*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "rust-qt-binding-generator"]
path = rust-qt-binding-generator
url = https://git.greyserv.net/marrub/rust-qt-binding-generator

View File

@ -1,23 +1,24 @@
[package]
name = "maraiah"
version = "0.1.0"
authors = ["Alison Watson <marrub@greyserv.net>", "Tae Matous"]
authors = ["Alison Sanderson <marrub@greyserv.net>"]
description = "Marathon editing tools."
homepage = "https://greyserv.net/maraiah/"
repository = "http://git.greyserv.net/marrub/Maraiah"
readme = "README.md"
license = "CC0-1.0"
edition = "2018"
publish = false
license = "MIT"
edition = "2018"
publish = false
[features]
serde_obj = ["serde"]
[workspace]
members = ["leela", "tycho"]
members = ["source/leela", "source/tycho"]
[dependencies]
bitflags = "1.1"
bitflags = "1.0"
failure = {version = "0.1", features = ["std"]}
serde = {version = "1.0", features = ["derive"], optional = true}
memchr = "2.0"
@ -31,4 +32,4 @@ lto = true
[lib]
name = "maraiah"
path = "maraiah/lib.rs"
path = "source/lib.rs"

21
LICENSE
View File

@ -1,15 +1,14 @@
Modified source code from rust-qt-binding-generator is included as a submodule
under the directory of the same name. That code is GNU GPL with exception to the
generated code. It is only required to *build* Tycho, so this exception also
applies to any binary version of Tycho.
Some of the data contained in tests/data is Copyright © Bungie Software. I do
not own them, but am permitted to redistribute them. PngSuite is included in
the test suite as well, its license is under tests/data/png/LICENSE. Everything
else, including all source code is licensed as Public Domain as stated below.
All program sources are licensed as such, are fully original, and may be used
freely. If you wish to contribute to the project you MUST explicitly accept the
terms and conditions and put your code as well under this license.
Some of the data contained in tests/data and benches/data is Copyright © Bungie
Software. I do not own them, but am permitted to redistribute them. Everything
else is public domain, as stated:
To the extent possible under law, I, Alison Watson, and all other contributors
to Maraiah, have waived all copyright and related or neighboring rights to this
Document as described by the Creative Commons Zero license as follows:
To the extent possible under law, I, Alison Sanderson, have waived all
copyright and related or neighboring rights to this Document as described by
the Creative Commons Zero license as follows:
Creative Commons Legal Code

2478
MarathonData.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,6 @@
# Maraiah
Maraiah
=======
Public domain tools for editing data for the Marathon engine. The name "Maraiah"
is derived from the Japanese transliteration of "Mariah," and is pronounced
"mah-rye-uhh" (/mɑɹaɪʌ/ in IPA.) It has no special meaning.
Index of documentation:
- [Marathon Data](doc/data/0-index.md)
- [`maraiah-leela`](doc/maraiah-leela.md)
- [`maraiah-tycho`](doc/maraiah-tycho.md)
- [`maraiah`](doc/maraiah.md)
Public domain tools for editing data for the Marathon engine. The name
"Maraiah" is derived from the Japanese transliteration of "Mariah," and is
pronounced "mah-rye-uhh" (/mɑɹaɪʌ/ in IPA.) It has no special meaning.

View File

@ -1,37 +0,0 @@
# LICENSING ###################################################################
To the extent possible under law, I, Alison Watson, have waived all copyright
and related or neighboring rights to this Document as described by the Creative
Commons Zero license included in this project under the `LICENSE` file, or if
unavailable, the link below:
<http://creativecommons.org/publicdomain/zero/1.0/>
All of the information in this Document is original research. Marathon and Forge
are owned by Bungie, Inc. QuickDraw, QuickTime and Macintosh are owned by Apple.
Aleph One (also referred to as A1 in this Document) is owned by Bungie, Inc. et
al. Igni Ferroque, Ferro, and Atque are owned by Gregory Smith (treellama.) Any
other copyrights not mentioned here belong to their respective owners and not
me.
If you need explanation on anything in this document don't hesitate to ask me.
Contact information is available at <http://greyserv.net>.
# CONTENTS ####################################################################
- [LICENSING](#licensing)
The license this document is under.
- [CONTENTS](#contents)
This table of contents.
- [TERMINAL CODE](1-terminal.md)
Info on terminal definition files.
- [DATA FORMATS](2-data.md)
Data formats used throughout Marathon.
- [STRUCTURES](3-struct.md)
Structure types used throughout Marathon.
- [ENUMERATIONS](4-enum.md)
Names for integers used in Marathon's structures.
- [FLAGS](5-flag.md)
Names for bit field flags used in Marathon's structures.
<!-- EOF -->

View File

@ -1,331 +0,0 @@
# TERMINAL CODE ###############################################################
The terminal definition format is extremely straightforward. Terminal commands
begin lines and are in the format:
```
#COMMAND_NAME parameters
```
In Forge and Maraiah, commands need not be uppercase. Atque does require all
commands to be uppercase.
Comments also begin lines (they can't be after the beginning of one) and will
disable any text proceeding them. They are formed like:
```
; comment content here
```
Terminals are numbered, and this is used in the map to determine which terminal
to display when a computer is used.
## Blocks ##
### Terminal Blocks ###
The number for the terminal being currently defined is set with the `#TERMINAL`
and `#ENDTERMINAL` commands. These are formed as:
```
#TERMINAL number
#ENDTERMINAL number
```
For example, defining a terminal numbered "1" would be:
```
#TERMINAL 1
; terminal's contents here
#ENDTERMINAL 1
```
### Sections ###
There are four possible sections in a terminal, which are between the
`#TERMINAL` and `#ENDTERMINAL` blocks:
```
#UNFINISHED
#FINISHED
#FAILURE
#SUCCESS
```
These all mark the start of where the terminal will display, depending on the
current status of your mission.
| Name | Will display when |
| ---- | ----------------- |
| `UNFINISHED` | your objective has not been met or no other block exists |
| `FINISHED` | you have succeeded or failed |
| `FAILURE` | you have failed your objective |
| `SUCCESS` | you have succeeded in your objective |
Sections must have an end, which is defined with:
```
#END
```
Between sections may be any amount of regular commands. There are two kinds of
these normal commands: Text commands, and interactive commands.
## Text Commands ##
All text commands may have (but do not require) text following them, which may
be formatted.
Line breaks will break in-game, but unbroken lines will automatically wrap. It
is generally best to just put all of your text onto one line, even if this
destroys your sanity. Use a text editor with line wrapping for this.
Example:
```
#PICT 10007
~text interface terminal malfunction error ~2992dud
welcome to mabmap i am durandal the most pretty ai in ever i made the pfhor ded and won all the everything you should go shoot some things I put here because reasons
```
### Formatting ###
Text effects are designated by a '$' and then one of the following:
| $-code | Effect |
| ------ | ------ |
| `I/i` | enables/disables italic text |
| `B/b` | enables/disables bold text |
| `U/u` | enables/disables underlined text |
| `Cn` | changes the text color, where "n" is a number 0 through 9 |
For more information on colors, see section ENUMERATIONS, Terminal Color.
Example:
```
$C1$BoOooO$IooOOoo$i$b$C0 ... $C6did I $Uspook$u you?$C0
```
### Text Command Overview ###
Text commands include:
```
#PICT pict_id alignment
#LOGON pict_id
#LOGOFF pict_id
#INFORMATION
#CHECKPOINT goal_id
#BRIEFING level_number
```
### `#PICT` ###
```
#PICT pict_id alignment
```
`#PICT` is the most basic and most used command throughout Marathon 2.
It displays a picture to the specified alignment and text to the other side.
It will:
- Wait for input before proceeding.
- Display 45 characters per line, and display up to 22 lines on one page.
- Display text aligned to the left on the right side of the screen.
If alignment is specified as RIGHT, text is aligned to the right on the left of
the screen. If alignment is specified as CENTER, no text may be displayed, only
an image. If no alignment is specified, it will default to an image on the left
and text on the right.
Example:
```
#PICT 10007
~text interface terminal malfunction error ~2992dud
hellote this is example text from durnadle prettiest ai in ever thank u for reading goodbye
```
### `#LOGON`, `#LOGOFF` ###
```
#LOGON pict_id
#LOGOFF pict_id
```
`#LOGON` and `#LOGOFF` are generally used first and last in a terminal.
These two display a PICT in the middle of the screen and text below the image if
you supply it. They both do things to the screen borders.
They will:
- Automatically continue, an input will interrupt it.
- Only display one line of text, at most 72 characters.
- Display text aligned to the center in the middle of the screen.
Example:
```
#LOGON 1600
<CMND PRAMA &681g1>
; ... content ...
#LOGOFF 1600
ehhg.431.4122//<PFGR ZNE6 &49c2>
```
### `#INFORMATION` ###
```
#INFORMATION
```
`#INFORMATION` will just display text, and is mostly used in Marathon 1.
It will:
- Wait for input before proceeding.
- Display 72 characters per line, and display up to 22 lines on one page.
- Display text aligned to the left on the left side of the screen.
Example:
```
#INFORMATION
you suck at videogames love durandal
p.s. if you don't win i'm erasing your home planet from existence
```
### `#CHECKPOINT` ###
```
#CHECKPOINT goal_id
```
`#CHECKPOINT` may only be used in Marathon 1, unless you're using Aleph One
version 1.1 or higher.
This shows a map centered on the specified goal point, with the goal circled, on
the left of the screen.
The map will only show polygons connected to the polygon the goal is in, so if
you have a separated area where the goal point is, it will only display that. It
will also not display secret areas and any polygons proceeding them.
It will:
- Wait for input before proceeding.
- Display 45 characters per line, and display up to 22 lines on one page.
- Display text aligned to the left on the right side of the screen.
Example:
```
#CHECKPOINT 7
go shoot these things so i can claim this victory as mine forever and tell you about the things that i totally shot for approximately 200 years
```
### `#BRIEFING` ###
```
#BRIEFING level_number
```
BRIEFING may only be used in Marathon 1. It is identical to INFORMATION, but
after you're done reading, it will teleport you to the specified level.
## Interactive Commands ##
Interactive commands are all actions carried out by the game that do not all
effect the active terminal.
### Interactive Command Overview ###
Interactive commands include:
```
#INTERLEVEL TELEPORT level_number
#INTRALEVEL TELEPORT polygon_tag
#TAG tag
#SOUND sound_number
#STATIC duration
```
### `#INTERLEVEL TELEPORT` ###
```
#INTERLEVEL TELEPORT level_number
```
`#INTERLEVEL TELEPORT` exits the terminal and teleports you to the specified
level. If the level number is "256", this ends the game.
Example:
```
#INTERLEVEL TELEPORT 7
```
### `#INTRALEVEL TELEPORT` ###
```
#INTRALEVEL TELEPORT polygon_tag
```
`#INTRALEVEL TELEPORT` exits the terminal and teleports you to the centroid of
the specified polygon within the map.
Example:
```
#INTRALEVEL TELEPORT 77
```
### `#TAG` ###
```
#TAG tag
```
`#TAG` activates all lights and platforms with the specified tag.
Example:
```
#TAG 77
```
### `#SOUND` ###
```
#SOUND sound_number
```
`#SOUND` plays the specified sound from the Sounds file or from the scenario and
then goes to the next level.
Example:
```
#SOUND 77
```
### `#STATIC` ###
```
#STATIC duration
```
`#STATIC` fills the terminal with TV static for the specified duration in
1/30ths seconds. Aleph One only.
Example:
```
#STATIC 60
```
<!-- EOF -->

View File

@ -1,111 +0,0 @@
# DATA FORMATS ################################################################
## Wad ##
Wad files are used for scenario data, images such as those in terminals and the
main menu, and physics files. Here is a listing of all chunks used within them.
Map tags:
| Name | Description |
| ---- | ----------- |
| `Minf` | Static Map Info structure |
| `PNTS` | Array of Point |
| `EPNT` | Array of Endpoint |
| `LINS` | Array of Line |
| `SIDS` | Array of Side |
| `POLY` | Array of Polygon |
| `LITE` | Array of Light |
| `NOTE` | Array of Annotation |
| `OBJS` | Array of Object |
| `plac` | Array of Object Frequency |
| `plat` | Array of Platform Data |
| `medi` | Array of Media Data |
| `ambi` | Array of Ambient Sound |
| `bonk` | Array of Random Sound |
| `term` | Array of Terminal |
| `NAME` | NTBS containing map name |
| `påth` | Unused, supposed to be guardpaths (å is $8C) |
| `door` | Unused, supposed to be extra door data |
Map files can be identified by the `Minf` chunk.
Maps will always have either a `PNTS` or `EPNT` chunk, depending on what the map
(and editor) use. `PNTS` are plain and have no more information than the actual
position, while `EPNT` can be loaded directly into memory by the engine. `EPNT`
also tells the engine that the map is preprocessed and that `iidx` and `PLAT`
chunks also exist. With `DataVersion` as `DataM1`, the format must always be
preprocessed.
Physics tags:
| Name | Description |
| ---- | ----------- |
| `FXpx` | Array of Effect Definition |
| `MNpx` | Array of Monster Definition |
| `PRpx` | Array of Projectile Definition |
| `PXpx` | Array of Physics Definition |
| `WPpx` | Array of Weapon Definition |
Physics definitions may be embedded into a map in Infinity and Aleph One, so the
only reliable way to check if something is a pure physics file is by checking
that there are no chunks other than `**px` chunks.
Image tags:
| Name | Description |
| ---- | ----------- |
| `PICT` | Picture Resource |
| `clut` | Banished to the shadow realm |
Images can be identified by the `PICT` chunk.
Save file tags:
| Name | Description |
| ---- | ----------- |
| `plyr` | Not analyzed (saved player data) |
| `dwol` | Not analyzed (saved dynamic world data) |
| `mobj` | Not analyzed (saved object data) |
| `iidx` | Not analyzed (saved map indices) |
| `alin` | Not analyzed (saved automap lines) |
| `apol` | Not analyzed (saved automap polygons) |
| `mOns` | Not analyzed (saved monsters) |
| `fx ` | Not analyzed (saved effects) |
| `bang` | Not analyzed (saved projectiles) |
| `PLAT` | Not analyzed (saved platform data) |
| `weap` | Not analyzed (saved weapon state) |
| `cint` | Not analyzed (saved terminal state) |
Preferences tags:
| Name | Description |
| ---- | ----------- |
| `graf` | Not analyzed (graphics prefs) |
| `serl` | Not analyzed (serial code) |
| `netw` | Not analyzed (network prefs) |
| `plyr` | Not analyzed (player prefs) |
| `inpu` | Not analyzed (input prefs) |
| `snd ` | Not analyzed (sound prefs) |
| `envr` | Not analyzed (environment prefs) |
Aleph One tags:
| Name | Description |
| ---- | ----------- |
| `ShPa` | Not analyzed (shapes) |
| `MMLS` | Not analyzed (MML scripts) |
| `LUAS` | Not analyzed (Lua scripts) |
## Marathon 2 Shapes (`.shpA`) ##
The Shapes file is used for storing animation, sprite, sky and texture data. It
uses a fixed, offset-based format for everything, with sprites sorted into
collections along with frames and sequences.
## Marathon 2 Sounds (`.sndA`) ##
The Sounds file is used for storing information about sounds as well as the
actual PCM data.
<!-- EOF -->

File diff suppressed because it is too large Load Diff

View File

@ -1,447 +0,0 @@
# ENUMERATIONS ################################################################
Here is a list of names and descriptions for enumerations used throughout this
document. The names may not match those used in the original engine and are
re-named for clarity of purpose.
### Picture Opcode ###
Operations used in QuickDraw images. Aleph One ignores most of these, so it's
only necessary to document what's ignored and how to process CopyBits. (If
you're interested in the QuickDraw format, Apple has legacy documents still
available on the Internet.)
Opcodes `$0100` through `$7FFF` are skipped by seeking forward by the most
significant byte's value times two.
Opcodes `$8000` through `$8100` are reserved and therefore ignored.
Any unspecified opcodes are not worth skipping, and the state machine should
exit upon reading them.
| Name | Value | Ignored | Extra data |
| ---- | ----- | :-----: | ---------- |
| `NoOp` | `$0000` | No | None |
| `Clip` | `$0001` | Yes | `u16 Size`, `(Size & ~1) - 2`B |
| `BkPat` | `$0002` | Yes | 8B |
| `TxFont` | `$0003` | Yes | 2B |
| `TxFace` | `$0004` | Yes | 2B |
| `TxMode` | `$0005` | Yes | 2B |
| `SpExtra` | `$0006` | Yes | 4B |
| `PnSize` | `$0007` | Yes | 4B |
| `PnMode` | `$0008` | Yes | 2B |
| `PnPat` | `$0009` | Yes | 8B |
| `FillPat` | `$000A` | Yes | 8B |
| `OvSize` | `$000B` | Yes | 4B |
| `Origin` | `$000C` | Yes | 4B |
| `TxSize` | `$000D` | Yes | 2B |
| `FgColor` | `$000E` | Yes | 4B |
| `BgColor` | `$000F` | Yes | 4B |
| `TxRatio` | `$0010` | Yes | 8B |
| `VersionOp` | `$0011` | Yes | 2B |
| `PnLocHFrac` | `$0015` | Yes | 2B |
| `ChExtra` | `$0016` | Yes | 2B |
| `RGBFgCol` | `$001A` | Yes | 6B |
| `RGBBkCol` | `$001B` | Yes | 6B |
| `HiliteMode` | `$001C` | Yes | None |
| `HiliteColor` | `$001D` | Yes | 6B |
| `DefHilite` | `$001E` | Yes | None |
| `OpColor` | `$001F` | Yes | 6B |
| `Line` | `$0020` | Yes | 8B |
| `LineFrom` | `$0021` | Yes | 4B |
| `ShortLine` | `$0022` | Yes | 6B |
| `ShortLineFrom` | `$0023` | Yes | 2B |
| `LineJustify` | `$002D` | Yes | 10B |
| `GlyphState` | `$002E` | Yes | 8B |
| `FrameRect` | `$0030` | Yes | 8B |
| `PaintRect` | `$0031` | Yes | 8B |
| `EraseRect` | `$0032` | Yes | 8B |
| `InvertRect` | `$0033` | Yes | 8B |
| `FillRect` | `$0034` | Yes | 8B |
| `FrameSameRect` | `$0038` | Yes | None |
| `PaintSameRect` | `$0039` | Yes | None |
| `EraseSameRect` | `$003A` | Yes | None |
| `InvertSameRect` | `$003B` | Yes | None |
| `FillSameRect` | `$003C` | Yes | None |
| `PackBitsRect` | `$0098` | No | CopyBits structure |
| `PackBitsRgn` | `$0099` | No | CopyBits structure |
| `DirectBitsRect` | `$009A` | No | CopyBits structure |
| `DirectBitsRgn` | `$009B` | No | CopyBits structure |
| `ShortComment` | `$00A0` | Yes | 2B |
| `LongComment` | `$00A1` | Yes | 2B, `u16 Size`, `Size & ~1`B |
| `OpEndPic` | `$00FF` | No | 2B |
| `Version` | `$02FF` | Yes | 2B |
| `HeaderOp` | `$0C00` | Yes | Header Op structure |
| `CompressedQuickTime` | `$8200` | No | QuickTime Image structure |
### CopyBits Pixel Depth ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| `Pal1` | `1` | Color mapped bit |
| `Pal2` | `2` | Color mapped dibit |
| `Pal4` | `4` | Color mapped nibble |
| `Pal8` | `8` | Color mapped byte |
| `X1RGB5` | `16` | X1RGB5 |
| `RGB8` | `32` | RGB8 (if NoPad) or XRGB8 |
### Pack Type ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| `Default` | `0` | Always pack |
| `None` | `1` | Never pack |
| `NoPad` | `2` | Never pack, no padding channel in 32bpp mode |
| `RLE16` | `3` | Only pack in 16bpp mode |
| `RLE32` | `4` | Only pack in 32bpp mode, no padding channel |
### Polygon Type ###
| Name | Value | Description | Permutation |
| ---- | ----- | ----------- | ----------- |
| `Normal` | `0` | Normal, no effects | None |
| `ImpassItem` | `1` | Items may not pass | None |
| `ImpassMons` | `2` | Monsters may not pass | None |
| `Hill` | `3` | Hill (for King of the Hill) | None |
| `Base` | `4` | Base (for Capture The Flag et al) | Team |
| `Platform` | `5` | Platform | Plat index |
| `TrigLightOn` | `6` | Triggers light on | Light index |
| `TrigPlatOn` | `7` | Triggers platform on | Plat index |
| `TrigLightOff` | `8` | Triggers light off | Poly index |
| `TrigPlatOff` | `9` | Triggers platform off | Poly index |
| `Teleporter` | `10` | Teleports to polygon centroid | Poly index |
| `ZoneBorder` | `11` | Zone border | None |
| `Goal` | `12` | Goal point | None |
| `TrigMonsVis` | `13` | Triggers near-by visible monsters | None |
| `TrigMonsInv` | `14` | Triggers near-by invisible monsters | None |
| `TrigMonsDual` | `15` | Same as TrigMonsInv | None |
| `TrigItems` | `16` | Triggers near-by invisible items | None |
| `MustExplore` | `17` | Must be entered for Exploration | None |
| `AutoExit` | `18` | Teleports to next level if success | None |
### Old Polygon Type ###
| Name | Value | Description | Permutation |
| ---- | ----- | ----------- | ----------- |
| `Normal` | `0` | Normal, no effects | None |
| `ImpassItem` | `1` | Items may not pass | None |
| `ImpassMons` | `2` | Monsters may not pass | None |
| `MinorOuch` | `3` | Damages the player a little | None |
| `MajorOuch` | `4` | Damages the player a lot | Team |
| `Platform` | `5` | Platform | Plat index |
| `TrigLightOn` | `6` | Triggers light on | Light index |
| `TrigPlatOn` | `7` | Triggers platform on | Plat index |
| `TrigLightOff` | `8` | Triggers light off | Poly index |
| `TrigPlatOff` | `9` | Triggers platform off | Poly index |
| `Teleporter` | `10` | Teleports to polygon centroid | Poly index |
| `Glue` | `11` | Slows the player down | None |
| `GlueTrigger` | `12` | TODO | TODO |
| `SuperGlue` | `13` | Slows the player down a lot | None |
| `MustExplore` | `14` | Must be entered for Exploration | None |
| `AutoExit` | `15` | Teleports to next level if success | None |
### Control Panel Type ###
This is used internally for each control panel preset and determines the
permutation each one uses.
| Name | Value | Description | Permutation |
| ---- | ----- | ----------- | ----------- |
| `Oxygen` | `0` | Oxygen refuel | None |
| `Shield` | `1` | Health charger | None |
| `Shield2x` | `2` | Health charger (2x) | None |
| `Shield3x` | `3` | Health charger (3x) | None |
| `Light` | `4` | Light switch | Light index |
| `Platform` | `5` | Platform switch | Plat index |
| `Tag` | `6` | Tag switch | Tag or -1 |
| `PatternBuf` | `7` | Save station | None |
| `Terminal` | `8` | Computer terminal | None |
### Side Type ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| `Full` | `0` | First texture is mapped over the whole side |
| `High` | `1` | First texture is mapped on the ceiling panel |
| `Low` | `2` | First texture is mapped on the floor panel |
| `Composite` | `3` | Composite texture (Not implemented) |
| `Split` | `4` | First texture for ceiling panel, second for floor |
"Panel" here refers to a protrusion in between two polygons. These would be
mapped to upper/lower textures in Doom for instance. Even the source code for
Marathon 2 itself acknowledges how redundant this enumeration is.
### Saved Object Group ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| TODO | `0` | Monster |
| TODO | `1` | Object |
| TODO | `2` | Item |
| TODO | `3` | Player |
| TODO | `4` | Goal |
| TODO | `5` | Sound source (facing is sound volume) |
### Transfer Mode ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| `Normal` | `0` | Normal |
| `FadeBlack` | `1` | Fade to black |
| `Invisibility` | `2` | Invisibility |
| `Invisibility2` | `3` | Invisibility (subtle) |
| `Pulsate` | `4` | Pulsate (polygons only) |
| `Wobble` | `5` | Wobble (polygons only) |
| `Wobble2` | `6` | Wobble (fast, polygons only) |
| `Static` | `7` | Static |
| `Static2` | `8` | 50% static |
| `Sky` | `9` | Sky |
| `Smear` | `10` | Smear |
| `StaticFade` | `11` | Static (fade out) |
| `StaticPulse` | `12` | Static (pulsating) |
| `FoldIn` | `13` | Fold-in |
| `FoldOut` | `14` | Fold-out |
| `SlideHorz` | `15` | Horizontal slide |
| `SlideHorz2` | `16` | Horizontal slide (fast) |
| `SlideVert` | `17` | Vertical slide |
| `SlideVert2` | `18` | Vertical slide (fast) |
| `Wander` | `19` | Wander |
| `Wander2` | `20` | Wander (fast) |
| `BigSky` | `21` | Big sky |
### Light Type ###
| Name | Value | Description |
| ---- | ----- | ----------- |
| `Normal` | `0` | Normal light |
| `Strobe` | `1` | Strobe light |
| `Media` | `2` | Media light |
### Old Light Type ###
| Name | Value |
| ---- | ----- |
| `Normal` | `0` |
| `Rheostat` | `1` |
| `Flourescent` | `2` |
| `Strobe` | `3` |
| `Flickers` | `4` |
| `Pulsates` | `5` |
| `Annoying` | `6` |
| `EnergyEfficient` | `7` |
### Old Light Mode ###
| Name | Value |
| ---- | ----- |
| `TurningOn` | `0` |
| `Active` | `1` |
| `TurningOff` | `2` |
| `Inactive` | `3` |
| `Toggle` | `4` |
- `TurningOn` and `Active` mean the new light created from this should have the
`InitActive` flag. Others do not mean anything.
### Wad Version ###
Used to determine how the engine loads the Wad file.
| Name | Value |
| ---- | ----- |
| `VerBase` | `0` |
| `VerDir` | `1` |
| `VerOver` | `2` |
| `VerMI` | `4` |
- `VerBase` has no directory entry.
- `VerDir` has a directory entry.
- `VerOver` supports data overlays.
- `VerMI` is used for Marathon Infinity data.
### Data Version ###
Used to determine how the engine loads map data.
| Name | Value |
| ---- | ----- |
| `DataM1` | `0` |
| `DataM2` | `1` |
- `DataM1` means Marathon 1 map data.
- `DataM2` means Marathon 2 map data.
### Terminal Group Type ###
| Name | Value | Permutation |
| ---- | ----- | ----------- |
| `Logon` | `0` | Pict ID |
| `Unfinished` | `1` | None |
| `Success` | `2` | None |
| `Failure` | `3` | None |
| `Info` | `4` | None |
| `End` | `5` | None |
| `TeleInter` | `6` | Level ID |
| `TeleIntra` | `7` | Polygon ID |
| `Checkpoint` | `8` | Goal ID |
| `Sound` | `9` | Sound ID |
| `Movie` | `10` | Movie ID |
| `Track` | `11` | Track ID |
| `Pict` | `12` | Pict ID |
| `Logoff` | `13` | Pict ID |
| `Camera` | `14` | Object ID |
| `Static` | `15` | 1/30 secs |
| `Tag` | `16` | Tag number |
### Terminal Color ###
These are the default colors. These can be overridden with mods.
| Name | Value |
| ---- | ----- |
| `LightGreen` | `0` |
| `White` | `1` |
| `Red` | `2` |
| `DarkGreen` | `3` |
| `LightBlue` | `4` |
| `Yellow` | `5` |
| `DarkRed` | `6` |
| `DarkBlue` | `7` |
| `Color8` | `8` |
| `Color9` | `9` |
### Sound Behaviour ###
Sound behaviours are used to determine falloff and volume bounds of a sound.
| Name | Value |
| ---- | ----- |
| `Quiet` | `0` |
| `Normal` | `1` |
| `Loud` | `2` |
- `Quiet` sounds will not be heard at all when obstructed, and only play at full
volume point-blank. They fall off very quickly, with a range of only 5 world
units.
- `Normal` sounds will be heard at at most half volume when obstructed, and fall
off at 10 world units unobstructed.
- `Loud` sounds will be heard at 3/4 volume when obstructed and can be heard at
full volume for twice the distance as a normal sound, and fall off at 15 units
unobstructed.
### Light Function ###
| Name | Value |
| ---- | ----- |
| `Constant` | `0` |
| `Linear` | `1` |
| `Smooth` | `2` |
| `Flicker` | `3` |
- `Constant` maintains the final intensity for `Period`.
- `Linear` transitions between the initial and final intensities over `Period`.
- `Smooth` does a sine transition in the same fashion as `Linear`.
- `Flicker` flickers between a random smoothed intensity and the final one.
### Media Type ###
| Name | Value |
| ---- | ----- |
| `Water` | `0` |
| `Lava` | `1` |
| `Goo` | `2` |
| `Sewage` | `3` |
| `Jjaro` | `4` |
### Texture Collection ###
| Name | Value |
| ---- | ----- |
| `Water` | `0` |
| `Lava` | `1` |
| `Sewage` | `2` |
| `Jjaro` | `3` |
| `Pfhor` | `4` |
These are the default texture sets.
### Landscape ###
| Name | Value |
| ---- | ----- |
| `LhowonDay` | `0` |
| `LhowonNight` | `1` |
| `Moon` | `2` |
| `Space` | `3` |
These are the default landscapes.
### Platform Type ###
| Name | Value |
| ---- | ----- |
| `SphtDoor` | `0` |
| `SphtDoorSplit` | `1` |
| `SphtDoorLock` | `2` |
| `SphtPlat` | `3` |
| `SphtPlatNoisy` | `4` |
| `SphtDoorHeavy` | `5` |
| `PfhorDoor` | `6` |
| `SphtPlatHeavy` | `7` |
| `PfhorPlatform` | `8` |
This apparently used to do something, but now does nothing, and is merely left
over for editor preset usage.
### Weapon Type ###
| Name | Value |
| ---- | ----- |
| `Melee` | `0` |
| `Normal` | `1` |
| `DualFunc` | `2` |
| `DualPistol` | `3` |
| `Multipurpose` | `4` |
### Casing Type ###
| Name | Value |
| ---- | ----- |
| `Rifle` | `0` |
| `Pistol` | `1` |
| `PistolLeft` | `2` |
| `PistolRight` | `3` |
| `SMG` | `4` |
| `None` | `65535` |
### Damage Type ###
| Name | Value |
| ---- | ----- |
| `Explosion` | `0` |
| `ElectricalStaff` | `1` |
| `Projectile` | `2` |
| `Absorbed` | `3` |
| `Flame` | `4` |
| `HoundClaws` | `5` |
| `AlienProjectile` | `6` |
| `HulkSlap` | `7` |
| `CompilerBolt` | `8` |
| `FusionBolt` | `9` |
| `HunterBolt` | `10` |
| `Fist` | `11` |
| `Teleporter` | `12` |
| `Defender` | `13` |
| `YetiClaws` | `14` |
| `YetiProjectile` | `15` |
| `Crushing` | `16` |
| `Lava` | `17` |
| `Suffocation` | `18` |
| `Goo` | `19` |
| `EnergyDrain` | `20` |
| `OxygenDrain` | `21` |
| `HummerBolt` | `22` |
| `ShotgunProjectile` | `23` |
| `None` | `65535` |
<!-- EOF -->

View File

@ -1,407 +0,0 @@
# FLAGS #######################################################################
### Endpoint Flags ###
| Name | Bit |
| ---- | --- |
| `Solid` | `0` |
| `SameHeight` | `1` |
| `Transparent` | `2` |
- `Solid` means the point belongs to a solid line.
- `SameHeight` means all polygons with this point have the same height, relative
to the point's lines
- `Transparent` means the point does not belong to an opaque line.
### Line Flags ###
| Name | Bit |
| ---- | --- |
| `TransSide` | `9` |
| `ElevVar` | `10` |
| `Elevation` | `11` |
| `Landscape` | `12` |
| `Transparent` | `13` |
| `Solid` | `14` |
- `TransSide` means the line has a transparent side.
- `ElevVar` means polygons on both sides do not have the same heights.
- `Elevation` means there is a differing height between this line's polygons.
- `Landscape` means this line shows only sky.
- `Transparent` means both sides are see-through.
- `Solid` means the line cannot be walked through.
### Side Flags ###
If `DataVersion` is `DataM1`, then `ItemOpt` must be set by the client.
| Name | Bit |
| ---- | --- |
| `Status` | `0` |
| `Panel` | `1` |
| `Repair` | `2` |
| `ItemUse` | `3` |
| `Lighted` | `4` |
| `CanDestroy` | `5` |
| `HitOnly` | `6` |
| `ItemOpt` | `7` |
- `Status` means the panel is switched already.
- `Panel` means the side is a control panel.
- `Repair` means the panel must be switched for Repair.
- `ItemUse` means the panel uses an item (for scripts, unused otherwise.)
- `Lighted` means the panel must be 3/4ths or more lit up to be used.
- `CanDestroy` makes projectiles toggle and disable this panel.
- `HitOnly` means the panel can only be hit by projectiles.
- `ItemOpt` means the item is optional for this panel (for scripts.)
### Polygon Flags ###
| Name | Bit |
| ---- | --- |
| `Detached` | `14` |
### Map Object Flags ###
| Name | Bit |
| ---- | --- |
| `Invisible` | `0` |
| `Ceiling` | `1` |
| `Blind` | `2` |
| `Deaf` | `3` |
| `Floating` | `4` |
| `NetOnly` | `5` |
- `Invisible` makes the object initially invisible (warps in.)
- `Ceiling` reverses the Z coordinate (from the ceiling.)
- `Blind` makes the object unable to be activated by sight.
- `Deaf` makes the object unable to be activated by sound.
- `NetOnly` makes the object only show up in net-games (items only.)
### Mission Flags ###
| Name | Bit |
| ---- | --- |
| `Extermination` | `0` |
| `Exploration` | `1` |
| `Retrieval` | `2` |
| `Repair` | `3` |
| `Rescue` | `4` |
| `M1Exploration` | `5` |
| `M1Rescue` | `6` |
| `M1Repair` | `7` |
- `Extermination` means you must kill all monsters on the map, with an error
threshold of 8 alien enemies maximum.
- `Exploration` means you must explore marked polygons.
- `Retrieval` means you must grab marked items.
- `Repair` means you must flip marked switches.
- `Rescue` means you must keep 50% of BoBs alive.
- `M1Exploration` is the same as `Exploration` except you only need to look at
each marked polygon, not actually enter them.
- `M1Rescue` is the same as `Rescue`. Since this is an internal flag, all it
actually does is change which monster class it checks.
- `M1Repair` is the same as `Repair`, except it only requires that the last
switch (by side index) be switched to succeed.
### Environment Flags ###
| Name | Bit |
| ---- | --- |
| `Vacuum` | `0` |
| `Magnetic` | `1` |
| `Rebellion` | `2` |
| `LowGrav` | `3` |
| `M1Glue` | `4` |
| `LavaFloor` | `5` |
| `Rebellion2` | `6` |
| `Music` | `7` |
| `TermPause` | `8` |
| `M1Monster` | `9` |
| `M1Weps` | `10` |
- `Vacuum` makes most weapons not work and oxygen deplete.
- `Magnetic` fucks up the motion sensor.
- `Rebellion` makes S'pht friendly and strips items and health.
- `LowGrav` lowers gravity.
- `M1Glue` makes glue handle like Marathon 1.
- `LavaFloor` makes the floor damage you.
- `Rebellion2` is the same as `Rebellion` but does not strip items/health.
- `Music` makes the level have music.
- `TermPause` makes terminals stop time (in Solo only.)
- `M1Monster` sets monster activation limits to Marathon 1's.
- `M1Weps` changes weapon pickups on Total Carnage and makes grenades low
gravity.
### Light Flags ###
| Name | Bit |
| ---- | --- |
| `InitActive` | `0` |
| `SlaveValue` | `1` |
| `Stateless` | `2` |
### Entry Point Flags ###
| Name | Bit |
| ---- | --- |
| `Solo` | `0` |
| `CoOp` | `1` |
| `Carnage` | `2` |
| `KTMWTB` | `3` |
| `KOTH` | `4` |
| `Defense` | `5` |
| `Rugby` | `6` |
| `CTF` | `7` |
- `KTMWTB` is Kill The Man With The Ball.
### Terminal Flags ###
| Name | Bit |
| ---- | --- |
| `Encoded` | `0` |
### Terminal Group Flags ###
| Name | Bit |
| ---- | --- |
| `DrawOnRight` | `0` |
| `DrawCenter` | `1` |
### CopyBits Flags ###
| Name | Bit |
| ---- | --- |
| `PICT2` | `15` |
### Color Table Flags ###
| Name | Bit |
| ---- | --- |
| `DeviceMap` | `15` |
### Frame Flags ###
| Name | Bit |
| ---- | --- |
| `Obscure` | `13` |
| `FlipY` | `14` |
| `FlipX` | `15` |
- `Obscure` makes the player's torso obscure the legs.
- `FlipY` and `FlipX` flip pixels on the respective axis.
### Sound Definition Flags ###
| Name | Bit |
| ---- | --- |
| `NoRestart` | `0` |
| `NoChannelSwitch` | `1` |
| `LessPitchChange` | `2` |
| `NoPitchChange` | `3` |
| `NoObstruction` | `4` |
| `NoMediaObstruct` | `5` |
| `Ambient` | `6` |
### Random Sound Flags ###
| Name | Bit |
| ---- | --- |
| `IgnoreDirection` | `0` |
### Media Flags ###
| Name | Bit |
| ---- | --- |
| `SoundObstruct` | `0` |
- `SoundObstruct` means the media makes no sound when under the floor. This is
most sensible for, for instance, lava which can be drained.
### Object Frequency Flags ###
| Name | Bit |
| ---- | --- |
| `RandomLocation` | `0` |
### Static Platform Flags ###
| Name | Bit |
| ---- | --- |
| `InitActive` | `0` |
| `InitExtended` | `1` |
| `StopAtEachLevel` | `2` |
| `StopAtInitLevel` | `3` |
| `StartAdjOnStop` | `4` |
| `ExtendsFloorToCeil` | `5` |
| `ComesFromFloor` | `6` |
| `ComesFromCeil` | `7` |
| `CausesDamage` | `8` |
| `NoActivateParent` | `9` |
| `ActivatesOnce` | `10` |
| `ActivatesLight` | `11` |
| `DeactivatesLight` | `12` |
| `PlayerControls` | `13` |
| `MonsterControls` | `14` |
| `ReverseOnObstruct` | `15` |
| `NoExtDeactivation` | `16` |
| `UsePolygonHeights` | `17` |
| `DelayedActivation` | `18` |
| `StartAdjOnStart` | `19` |
| `StopAdjOnStart` | `20` |
| `StopAdjOnStop` | `21` |
| `Slow` | `22` |
| `StartAtEachLevel` | `23` |
| `Locked` | `24` |
| `Secret` | `25` |
| `Door` | `26` |
If I could explain to you why there are this many flags, I gladly would, but
this actually hurts my head.
### Effect Definition Flags ###
| Name | Bit |
| ---- | --- |
| `EndOnLoop` | `0` |
| `EndOnXferLoop` | `1` |
| `SoundOnly` | `2` |
| `MakeTwinVisible` | `3` |
| `MediaEffect` | `4` |
### Weapon Flags ###
| Name | Bit |
| ---- | --- |
| `Automatic` | `0` |
| `RemoveAfterUse` | `1` |
| `InstantCasing` | `2` |
| `Overloads` | `3` |
| `RandomAmmo` | `4` |
| `TemporaryPower` | `5` |
| `ReloadOneHand` | `6` |
| `FireOutOfPhase` | `7` |
| `FireUnderMedia` | `8` |
| `TriggerSameAmmo` | `9` |
| `SecondaryFlip` | `10` |
### Damage Flags ###
| Name | Bit |
| ---- | --- |
| `AlienDamage` | `0` |
- `AlienDamage` will decrease the damage on Easy and Wuss.
### Projectile Flags ###
| Name | Bit |
| ---- | --- |
| `Guided` | `0` |
| `StopOnLoop` | `1` |
| `Persistent` | `2` |
| `Alien` | `3` |
| `Gravity` | `4` |
| `NoHorzError` | `5` |
| `NoVertError` | `6` |
| `TogglePanels` | `7` |
| `PosVertError` | `8` |
| `Melee` | `9` |
| `Ripper` | `10` |
| `PassTransRandom` | `11` |
| `PassTransMore` | `12` |
| `DoubleGravity` | `13` |
| `ReboundFloor` | `14` |
| `ThroughMedia` | `15` |
| `BecomeItem` | `16` |
| `Bloody` | `17` |
| `WanderHorz` | `18` |
| `WanderVert` | `19` |
| `UseLowGrav` | `20` |
| `PassMedia` | `21` |
### Monster Flags ###
| Name | Bit |
| ---- | --- |
| `IgnoreLOS` | `0` |
| `Flying` | `1` |
| `Alien` | `2` |
| `Major` | `3` |
| `Minor` | `4` |
| `NoOmit` | `5` |
| `Floats` | `6` |
| `NoAttack` | `7` |
| `Snipe` | `8` |
| `Invisible` | `9` |
| `SubtlyInvisible` | `10` |
| `Kamikaze` | `11` |
| `Berserker` | `12` |
| `Enlarged` | `13` |
| `DelayedDeath` | `14` |
| `FireSymmetrical` | `15` |
| `NuclearDeath` | `16` |
| `NoFireBackwards` | `17` |
| `CanDieInFlames` | `18` |
| `WaitForGoodShot` | `19` |
| `Tiny` | `20` |
| `FastAttack` | `21` |
| `LikesWater` | `22` |
| `LikesSewage` | `23` |
| `LikesLava` | `24` |
| `LikesGoo` | `25` |
| `TeleUnderMedia` | `26` |
| `UseRandomWeapon` | `27` |
### Monster Class ###
| Name | Bit |
| ---- | --- |
| `Player` | `0` |
| `Civilian` | `1` |
| `Madd` | `2` |
| `PossessedHummer` | `3` |
| `Defender` | `4` |
| `Fighter` | `5` |
| `Trooper` | `6` |
| `Hunter` | `7` |
| `Enforcer` | `8` |
| `Juggernaut` | `9` |
| `Hummer` | `10` |
| `Compiler` | `11` |
| `Cyborg` | `12` |
| `Assimilated` | `13` |
| `Tick` | `14` |
| `Yeti` | `15` |
### Damage Type Flag ###
| Name | Bit |
| ---- | --- |
| `Explosion` | `0` |
| `ElectricalStaff` | `1` |
| `Projectile` | `2` |
| `Absorbed` | `3` |
| `Flame` | `4` |
| `HoundClaws` | `5` |
| `AlienProjectile` | `6` |
| `HulkSlap` | `7` |
| `CompilerBolt` | `8` |
| `FusionBolt` | `9` |
| `HunterBolt` | `10` |
| `Fist` | `11` |
| `Teleporter` | `12` |
| `Defender` | `13` |
| `YetiClaws` | `14` |
| `YetiProjectile` | `15` |
| `Crushing` | `16` |
| `Lava` | `17` |
| `Suffocation` | `18` |
| `Goo` | `19` |
| `EnergyDrain` | `20` |
| `OxygenDrain` | `21` |
| `HummerBolt` | `22` |
| `ShotgunProjectile` | `23` |
<!-- EOF -->

View File

@ -1,18 +0,0 @@
# `maraiah-leela`
`maraiah-leela` is a CLI utility that essentially allows for a direct user
interface to the underlying Maraiah library.
## Features
No compile-time features are available for `maraiah-leela`.
## Dependencies
`maraiah-leela` requires:
- `rustc` version 1.36.0 or greater.
## Installation
To compile, run `cargo build -p maraiah-leela`.

View File

@ -1,31 +0,0 @@
# `maraiah-tycho`
`maraiah-tycho` is a map editor written in C++ and Rust using Qt.
## Dependencies
`maraiah-tycho` requires:
- A working C++17 compiler and runtime.
- `cmake` version 3.14 or greater.
- `fish` for running the necessary scripts.
- `librsvg` for compiling images and for the bundle script.
- `pigz` to compile the images.
- `qt5`, specifically the Core, GUI and Widgets libraries.
- `rustc` version 1.36.0 or greater.
## Installation
To compile:
- `tools/gen_images tycho`
- `cargo build -p maraiah-tycho`
If you wish to create a Macintosh Application Bundle, run:
`tools/bundle Tycho <path-to-executable>`
This will also create an installer DMG. If you don't want to create an installer
and just want the app, run:
`env NO_DMG=1 tools/bundle Tycho <path-to-executable>`

View File

@ -1,27 +0,0 @@
# `maraiah`
The basis of this project is the Rust project `maraiah` which provides functions
and structures to build applications which work with Marathon's data.
Maraiah comes with a set of almost fully portable tools written in Rust,
including binary file reading, safe C-like `enum` reading, improved error
handling, FFI tools, fixed point numbers, and image and sound structures.
Maraiah also comes with APIs for working with various data formats, primarily
Marathon's. It also handles Macintosh PICT files, PPM, TARGA, RIFF WAVE, and
more.
## Features
Maraiah supports serializing and deserializing through `serde` with the
`serde_obj` feature.
## Dependencies
`maraiah` requires:
- `rustc` version 1.36.0 or greater.
## Installation
To compile, run `cargo build`.

View File

@ -1,14 +0,0 @@
[package]
name = "maraiah-leela"
version = "0.0.0"
edition = "2018"
[dependencies]
clap = "2"
maraiah = {path = "..", features = ["serde_obj"]}
serde = "1.0"
serde_yaml = "0.8"
[[bin]]
name = "maraiah-leela"
path = "main.rs"

View File

@ -1,161 +0,0 @@
#![allow(clippy::unit_arg)]
use maraiah::{err::*, file::*, image::*, machdr, map, shp, snd, sound::*};
use std::{collections::HashSet, fs, io, slice::from_ref};
/*
fn open(path: &str) -> ResultS<io::BufReader>
{
let fp = fs::File::open(path)?;
Ok(io::BufReader::new(fp))
}
fn file_read<T, F>(path: &str, f: F) -> ResultS<T>
where F: FnOnce(&[u8]) -> ResultS<T>
{
let mm = open(path)?;
let bp = &mm[machdr::try_mac_header(&mm)..];
f(bp)
}
fn exists(path: String) -> Result<(), String>
{
match std::fs::metadata(path) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
fn each_value<F>(opt: &clap::ArgMatches<'_>,
name: &str,
mut f: F) -> ResultS<()>
where F: FnMut(&str) -> ResultS<()>
{
if let Some(values) = opt.values_of(name) {
for value in values {
f(value)?;
}
}
Ok(())
}
fn dbg_info(data: impl std::fmt::Debug)
{
println!("{:#?}", data);
}
fn dump_map(opt: &clap::ArgMatches<'_>, f: &str) -> ResultS<()>
{
let mut cnks = HashSet::new();
if let Some(opt_cnks) = opt.values_of("chunks") {
for typ in opt_cnks {
cnks.insert(typ);
}
}
Ok(())
}
fn dump_shp(opt: &clap::ArgMatches<'_>, f: &str) -> ResultS<()>
{
unimplemented!();
}
fn dump_snd(opt: &clap::ArgMatches<'_>, f: &str) -> ResultS<()>
{
unimplemented!();
}
fn sub_data_c(_opt: &clap::ArgMatches<'_>) -> ResultS<()>
{
unimplemented!();
}
fn sub_dump_c(opt: &clap::ArgMatches<'_>) -> ResultS<()>
{
each_value(opt, "map", |f| dump_map(opt, f))?;
each_value(opt, "shp", |f| dump_shp(opt, f))?;
each_value(opt, "snd", |f| dump_snd(opt, f))?;
Ok(())
}
fn sub_info_c(opt: &clap::ArgMatches<'_>) -> ResultS<()>
{
each_value(opt, "map", |f| Ok(dbg_info(file_read(f, map::read)?)))?;
each_value(opt, "shp", |f| Ok(dbg_info(file_read(f, shp::read)?)))?;
each_value(opt, "snd", |f| Ok(dbg_info(file_read(f, snd::read)?)))?;
Ok(())
}
*/
fn main() -> ResultS<()>
{
use std::io::prelude::*;
let inp = include_bytes!("../tests/data/map/Test.in");
let mut rd = std::io::BufReader::new(&inp[..]);
let mp = map::head::read(&mut rd).unwrap();
let en = map::entr::read_all(&mp).unwrap();
let ed = map::data::read_all(mp.head(), &en).unwrap();
write!(&mut std::fs::File::create("dicks.txt").unwrap(), "{:#?}", ed);
use clap::clap_app;
let sub_data = clap_app! {
@subcommand data =>
(about: "Dumps data into a discrete folder/YAML format")
};
let sub_dump = clap_app! {
@subcommand dump =>
(about: "Dumps particular parts of data")
(@arg chunks: -c --chunks [name]... "Dumps named chunks from an entry")
(@group files =>
(@attributes +required +multiple)
(@arg map: -m --map [file]... {exists} "Loads Map files")
(@arg shp: -s --shp [file]... {exists} "Loads Shapes files")
(@arg snd: -n --snd [file]... {exists} "Loads Sounds files"))
};
let sub_info = clap_app! {
@subcommand info =>
(about: "Outputs debug info")
(@group files =>
(@attributes +required +multiple)
(@arg map: -m --map [file]... {exists} "Loads Map files")
(@arg shp: -s --shp [file]... {exists} "Loads Shapes files")
(@arg snd: -n --snd [file]... {exists} "Loads Sounds files"))
};
let opt = clap_app! {
(env!("CARGO_PKG_NAME")) =>
(version: maraiah::meta::version())
(author: maraiah::meta::authors().replace(':', ", "))
(about: maraiah::meta::description())
(setting: clap::AppSettings::SubcommandRequiredElseHelp)
(subcommand: sub_data)
(subcommand: sub_dump)
(subcommand: sub_info)
};
let opt = opt.get_matches();
match opt.subcommand() {
("data", Some(opt)) => sub_data_c(opt)?,
("dump", Some(opt)) => sub_dump_c(opt)?,
("info", Some(opt)) => sub_info_c(opt)?,
_ => unreachable!(),
}
*/
Ok(())
}
// EOF

View File

@ -1,626 +0,0 @@
//! Binary data conversion utilities.
use crate::err::*;
use std::{convert::{TryFrom, TryInto}, fmt, num::NonZeroU16};
#[doc(hidden)]
#[macro_export]
macro_rules! rd_impl {
// worker, creates let statement for 16 bit numbers
(W $b:expr, $pth:path, $nam:ident, $n:expr) => {
let $nam = $pth([$b[$n], $b[$n + 1]]);
};
// worker, creates let statement for 32 bit numbers
(D $b:expr, $pth:path, $nam:ident, $n:expr) => {
let $nam = $pth([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]);
};
// big endian, u16
(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => {
$crate::rd_impl!(W $b, u16::from_be_bytes, $nam, $n + $at);
};
// big endian, i16
(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => {
$crate::rd_impl!(W $b, i16::from_be_bytes, $nam, $n + $at);
};
// big endian, u32
(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => {
$crate::rd_impl!(D $b, u32::from_be_bytes, $nam, $n + $at);
};
// big endian, i32
(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => {
$crate::rd_impl!(D $b, i32::from_be_bytes, $nam, $n + $at);
};
// little endian, u16
(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => {
$crate::rd_impl!(W $b, u16::from_le_bytes, $nam, $n + $at);
};
// little endian, i16
(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => {
$crate::rd_impl!(W $b, i16::from_le_bytes, $nam, $n + $at);
};
// little endian, u32
(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => {
$crate::rd_impl!(D $b, u32::from_le_bytes, $nam, $n + $at);
};
// little endian, i32
(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => {
$crate::rd_impl!(D $b, i32::from_le_bytes, $nam, $n + $at);
};
// either endianness, Angle
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Angle) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
let $nam = $crate::fixed::Angle::from_bits($nam);
};
// either endianness, Fixed
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Fixed) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u32);
let $nam = $crate::fixed::Fixed::from_bits($nam);
};
// either endianness, Unit
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Unit) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
let $nam = $crate::fixed::Unit::from_bits($nam);
};
// either endianness, OptU16
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, OptU16) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
let $nam = $crate::bin::OptU16::from($nam);
};
// either endianness, u16 -> usize
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u16) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
let $nam = usize::from($nam);
};
// either endianness, u32 -> usize
($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u32) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, u32);
let $nam = $crate::bin::usize_from_u32($nam);
};
// either endianness, enum type with TryFrom
($e:ident, $b:expr, $at:expr, $n:expr;
$nam:ident, enum, $et:ident$(::$etc:ident)*, $t:ident) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, $t);
let $nam: $et$(::$etc)* = std::convert::TryFrom::try_from($nam)?;
};
// either endianness, bitflag type
($e:ident, $b:expr, $at:expr, $n:expr;
$nam:ident, flag, $ft:ident$(::$ftc:ident)*, $t:ident) => {
$crate::rd_impl!($e, $b, $at, $n; $nam, $t);
let $nam = flag_ok!($ft$(::$ftc)*, $nam)?;
};
// no endianness, u8
($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, u8) => {
let $nam = $b[$n + $at];
};
// no endianness, i8
($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, i8) => {
let $nam = $b[$n + $at] as i8;
};
// no endianness, [u8]
($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, u8) => {
let $nam = &$b[$n + $at..$n + $at + $rn];
};
// no endianness, Ident
($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Ident) => {
$crate::rd_impl!(D $b, Ident, $nam, $n + $at);
};
// no endianness, fn([u8]) -> T
($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr;
$nam:ident, no_try, $f:expr) => {
let $nam = $f(&$b[$n + $at..$n + $at + $rn]);
};
// no endianness, fn([u8]) -> Result<T>
($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, $f:expr) => {
let $nam = $f(&$b[$n + $at..$n + $at + $rn])?;
};
}
/// Reads structured data from a byte slice.
///
/// # Syntax
///
/// First start by specifying the basic information, using the syntax:
/// `endian: ENDIAN, buf: BUFFER, size: SIZE, start: START,` where:
///
/// - `ENDIAN` is `BIG` or `LITTLE` for big- or little-endian respectively.
/// - `BUFFER` is a `u8` slice to read data from. This expression will be
/// evaluated many times, so be careful when specifying it.
/// - `SIZE` is an expression specifying the last index that should be used by
/// this macro in `BUFFER`.
/// - `START` is an expression specifying the index to start at in `BUFFER`. All
/// indices and sizes will have this added to them.
///
/// Following that is a block with the syntax `data { ... }`. All lines within
/// this block have the syntax `let NAME = TYPE[INDEX] OPTS;` where:
///
/// - `NAME` is the binding to put the resulting data in.
/// - `TYPE` is one of:
/// - `u8` or `i8`: one byte will be read at `INDEX`. If `INDEX` is a range,
/// this will be a slice into `BUFFER` instead.
/// - `u16` or `i16`: two bytes will be read at `INDEX` with `from_*_bytes`.
/// If `OPTS` is `usize`, this converts the resulting number to `usize` by
/// using `usize::from`.
/// - `u32` or `i32`: four bytes will be read at `INDEX` with `from_*_bytes`.
/// If `OPTS` is `usize`, this converts the resulting number to `usize` by
/// using `usize_from_u32`.
/// - `Ident`: four bytes will be read at `INDEX` into an array, disregarding
/// endianness, creating an `Ident` object.
/// - `Angle`: same as `u16`, but the result is passed to
/// `fixed::Angle::from_bits`, resulting in a `fixed::Angle` object.
/// - `Fixed`: same as `u32`, but the result is passed to
/// `fixed::Fixed::from_bits`, resulting in a `fixed::Fixed` object.
/// - `Unit`: same as `u16`, but the result is passed to
/// `fixed::Unit::from_bits`, resulting in a `fixed::Unit` object.
/// - `OptU16`: same as `u16`, but the result is passed to `OptU16::from`,
/// resulting in an `OptU16` object.
/// - The name of a function, which is passed the index range as its only
/// argument. The function's result has the `?` operator applied to it,
/// unless `OPTS` is `no_try`.
/// - `OPT`, if not one of the things listed above, may be `enum TYPE` to apply
/// `TryFrom<TYPE>`, or `flag TYPE` to apply a bitfield made by `c_bitfield!`.
/// - `INDEX` is either an integer literal which must be representable as
/// `usize`, or a range with the syntax `INDEX; SIZE` denoting the beginning
/// and size of the range.
///
/// # Panics
///
/// This macro will not panic unless any index expression used exceeds or
/// equals `SIZE + START`, or a function passed to it panics.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate maraiah;
/// # use maraiah::err::*;
/// #
/// # fn main() -> ResultS<()>
/// # {
/// let buffer = &[4, 0, 2, 0, 0, 0, 6];
///
/// read_data! {
/// endian: LITTLE, buf: buffer, size: 7, start: 0, data {
/// let four = u16[0];
/// let two = u32[2];
/// let six = u8[6];
/// let byte = u8[2; 4];
/// }
/// }
///
/// assert_eq!(four, 4_u16);
/// assert_eq!(two, 2_u32);
/// assert_eq!(six, 6_u8);
/// assert_eq!(byte, &[2, 0, 0, 0]);
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! read_data {
(
endian: $e:ident, buf: $b:expr, size: $sz:expr, start: $at:expr, data {
$(let $nam:ident = $t:ident$(::$tc:ident)*[$n:expr $(; $rn:expr)?]
$($ex:ident$(::$exc:ident)*)*;)*
}
) => {
$crate::bin::check_data($b, $at + $sz)?;
$($crate::rd_impl!($e, $b, $at, $n;
$($rn;)? $nam, $($ex$(::$exc)*,)* $t$(::$tc)*);)*
};
}
/// Checks if there is enough data in `b`.
///
/// # Errors
///
/// Returns `Err` if `b.len()` is less than `sz`.
#[inline]
pub fn check_data<T>(b: &[T], sz: usize) -> ResultS<()>
{
if b.len() < sz {
Err(err_msg("not enough data"))
} else {
Ok(())
}
}
/// Casts a `u32` to a `usize`.
///
/// # Panics
///
/// Will panic if the platform does not have a 32-bit `usize`. In this case,
/// the application is not supported, but will still run and panic at runtime.
///
/// # Examples
///
/// ```
/// use maraiah::bin::usize_from_u32;
///
/// assert_eq!(usize_from_u32(777u32), 777usize);
/// ```
#[inline]
pub fn usize_from_u32(n: u32) -> usize
{
usize::try_from(n).expect("platform is 16-bit")
}
/// Creates an `Ident` from a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::{ident, Ident};
///
/// assert_eq!(ident(b"POLY"), Ident([b'P', b'O', b'L', b'Y']));
/// ```
#[inline]
pub fn ident(b: &[u8]) -> Ident
{
Ident(b[0..4].try_into().expect("not enough data"))
}
/// Applies `u32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::u32b;
///
/// assert_eq!(u32b(&[0x00, 0x0B, 0xDE, 0x31]), 777_777u32);
/// ```
#[inline]
pub fn u32b(b: &[u8]) -> u32
{
u32::from_be_bytes(b[0..4].try_into().expect("not enough data"))
}
/// Applies `u16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::bin::u16b;
///
/// assert_eq!(u16b(&[0x1E, 0x61]), 7_777u16);
/// ```
#[inline]
pub fn u16b(b: &[u8]) -> u16
{
u16::from_be_bytes(b[0..2].try_into().expect("not enough data"))
}
/// Applies `i32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::i32b;
///
/// assert_eq!(i32b(&[0xFF, 0x89, 0x52, 0x0F]), -7_777_777i32);
/// ```
#[inline]
pub fn i32b(b: &[u8]) -> i32
{
i32::from_be_bytes(b[0..4].try_into().expect("not enough data"))
}
/// Applies `i16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::bin::i16b;
///
/// assert_eq!(i16b(&[0xE1, 0x9F]), -7_777i16);
/// ```
#[inline]
pub fn i16b(b: &[u8]) -> i16
{
i16::from_be_bytes(b[0..2].try_into().expect("not enough data"))
}
/// Applies a read function over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When there is no
/// data left in `b`, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
///
/// # Examples
///
/// ```
/// use maraiah::{bin::{rd_array, u16b}, err::*};
///
/// fn read_a_u16(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))}
///
/// let inp = &[0x1E, 0x61, 0x03, 0x09];
/// assert_eq!(rd_array(inp, read_a_u16).unwrap(), vec![7_777u16, 777u16]);
/// ```
pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
{
let mut v = Vec::new();
let mut p = 0;
while p < b.len() {
let (r, s) = read(&b[p..])?;
v.push(r);
p += s;
}
Ok(v)
}
/// Applies a read function a number of times over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When `n` elements
/// have been read, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_array_num<T, F>(b: &[u8],
n: usize,
read: F) -> ResultS<(Vec<T>, usize)>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
{
let mut v = Vec::with_capacity(n);
let mut p = 0;
for _ in 0..n {
let (r, s) = read(&b[p..])?;
v.push(r);
p += s;
}
Ok((v, p))
}
/// Applies a read function over a slice with an offset table.
///
/// Applies `read` over each offset in `b`, of which there are `num` amount of
/// starting at `p`, resulting in a vector of its return values. Each iteration
/// reads a 32-bit big endian offset from `b`, and then passes a slice of `b`
/// to `read` starting at that offset. When all offsets have been read, the
/// function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function panics.
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_ofstable<T, F>(b: &[u8],
mut p: usize,
num: usize,
read: F) -> ResultS<Vec<T>>
where T: Sized,
F: Fn(&[u8]) -> ResultS<T>
{
let mut v = Vec::with_capacity(num);
for _ in 0..num {
let ofs = usize_from_u32(u32b(&b[p..p + 4]));
check_data(b, ofs)?;
v.push(read(&b[ofs..])?);
p += 4;
}
Ok(v)
}
impl From<u16> for OptU16
{
#[inline]
fn from(n: u16) -> Self
{
if n == u16::max_value() {
Self(None)
} else {
Self(NonZeroU16::new(n + 1))
}
}
}
impl Into<u16> for OptU16
{
/// Returns the `u16` representation.
///
/// # Examples
///
/// ```
/// use maraiah::bin::OptU16;
///
/// let u16_max = u16::max_value();
///
/// // These type annotations are necessary.
///
/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(500u16)), 500u16);
/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(u16_max)), u16_max);
/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(0u16)), 0u16);
/// ```
#[inline]
fn into(self) -> u16
{
match self.0 {
None => u16::max_value(),
Some(n) => n.get() - 1,
}
}
}
impl OptU16
{
/// Creates an `OptU16` representing `None`.
///
/// # Examples
///
/// ```
/// use maraiah::bin::OptU16;
///
/// assert_eq!(OptU16::none(), OptU16::from(u16::max_value()));
/// ```
#[inline]
pub const fn none() -> Self {Self(None)}
/// Returns the `Option` representation.
///
/// # Examples
///
/// ```
/// use maraiah::bin::OptU16;
///
/// assert_eq!(OptU16::from(500u16).get(), Some(500u16));
/// assert_eq!(OptU16::from(u16::max_value()).get(), None);
/// assert_eq!(OptU16::from(0u16).get(), Some(0u16));
/// ```
#[inline]
pub fn get(self) -> Option<u16>
{
match self.0 {
None => None,
Some(n) => Some(n.get() - 1),
}
}
/// Return the memory representation of this integer as a byte array
/// in big-endian (network) byte order.
#[inline]
pub fn to_be_bytes(self) -> [u8; 2]
{
<Self as Into<u16>>::into(self).to_be_bytes()
}
/// Return the memory representation of this integer as a byte array
/// in little-endian byte order.
#[inline]
pub fn to_le_bytes(self) -> [u8; 2]
{
<Self as Into<u16>>::into(self).to_le_bytes()
}
}
impl fmt::Display for OptU16
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self.get() {
None => write!(f, "None"),
Some(n) => write!(f, "Some({})", n),
}
}
}
impl fmt::Debug for OptU16
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self.get() {
None => write!(f, "OptU16::none()"),
Some(n) => write!(f, "OptU16::from({})", n),
}
}
}
impl PartialEq<[u8; 4]> for Ident
{
#[inline]
fn eq(&self, o: &[u8; 4]) -> bool {self.0 == *o}
}
impl<'a> PartialEq<[u8; 4]> for &'a Ident
{
#[inline]
fn eq(&self, o: &[u8; 4]) -> bool {PartialEq::eq(*self, o)}
}
impl<'a> PartialEq<&'a [u8; 4]> for Ident
{
#[inline]
fn eq(&self, o: &&'a [u8; 4]) -> bool {PartialEq::eq(self, *o)}
}
/// A four-character-code identifier.
///
/// # Examples
///
/// ```
/// use maraiah::bin::Ident;
///
/// assert_eq!(Ident(*b"POLY").0, *b"POLY");
/// assert_eq!(Ident(*b"POLY"), *b"POLY");
/// assert_eq!(Ident(*b"POLY"), b"POLY");
/// assert_eq!(&Ident(*b"POLY"), *b"POLY");
/// assert_eq!(&Ident(*b"POLY"), b"POLY");
/// ```
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]);
/// An object identified by a `u16` which may be `u16::max_value()` to
/// represent a nulled value.
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct OptU16(Option<NonZeroU16>);
// EOF

View File

@ -1,162 +0,0 @@
//! Bit streams.
use crate::err::*;
/// Reads `width` bits from `b` starting at bit `cr_bit` into an integer, most
/// significant bit first.
///
/// # Errors
///
/// The function will return an error if `width` is more than 64, or if
/// `(cr_bit + width - 1) / 8` would overflow an index into `b`.
pub fn read_bits_b(b: &[u8], cr_bit: usize, width: u8) -> ResultS<u64>
{
if width == 0 {
return Ok(0);
}
if width > 64 {
bail!("invalid number of bits");
}
let last = (cr_bit + usize::from(width) - 1) / 8;
if last >= b.len() {
bail!("not enough data");
}
let mut byte_ptr = cr_bit / 8;
let mut bits_ptr = cr_bit % 8;
let mut res = 0;
for _ in 0..width {
res <<= 1;
if b[byte_ptr] & (1 << bits_ptr) != 0 {
res |= 1;
}
bits_ptr += 1;
if bits_ptr > 7 {
bits_ptr = 0;
byte_ptr += 1;
}
}
Ok(res)
}
/// The same as `read_bits_b`, but least-significant bit first.
pub fn read_bits_l(b: &[u8], cr_bit: usize, width: u8) -> ResultS<u64>
{
if width == 0 {
Ok(0)
} else {
let res = read_bits_b(b, cr_bit, width)?;
Ok(reverse_bits(res) >> (64 - width))
}
}
// FIXME: change this to u64::reverse_bits when stabilized
const fn reverse_bits(v: u64) -> u64
{
let v = v >> 1 & 0x5555_5555_5555_5555 | ((v & 0x5555_5555_5555_5555) << 1);
let v = v >> 2 & 0x3333_3333_3333_3333 | ((v & 0x3333_3333_3333_3333) << 2);
let v = v >> 4 & 0x0F0F_0F0F_0F0F_0F0F | ((v & 0x0F0F_0F0F_0F0F_0F0F) << 4);
v.swap_bytes()
}
#[test]
fn bit_tests()
{
const INPUT: &[u8] = &[0b01100101, 0b10101010, 0b00010000, 0b00000000,
0b11111111, 0b11100001, 0b10101100, 0b00110011,
0b10100101, 0b11100000, 0b00000111, 0b00000001,
0b11001010, 0b10101111, 0b00101011, 0b01101010,
0b11010101, 0b10100011, 0b01010101, 0b11000001];
let mut p = 0;
let n = read_bits_b(INPUT, p, 3).unwrap();
assert_eq!(n, 0b101);
p += 3;
let n = read_bits_b(INPUT, p, 63).unwrap();
assert_eq!(n, 0b001100101010100001000000000001111111110000111001101011100110010);
p += 63;
let n = read_bits_b(INPUT, p, 4).unwrap();
assert_eq!(n, 0b1001);
p += 4;
let n = read_bits_b(INPUT, p, 7).unwrap();
assert_eq!(n, 0b0100000);
p += 7;
let n = read_bits_b(INPUT, p, 17).unwrap();
assert_eq!(n, 0b11111100000100000);
p += 17;
let n = read_bits_b(INPUT, p, 27).unwrap();
assert_eq!(n, 0b000101001111110101110101000);
p += 27;
let n = read_bits_b(INPUT, p, 33).unwrap();
assert_eq!(n, 0b101011010101011110001011010101010);
p += 33;
let n = read_bits_b(INPUT, p, 6).unwrap();
assert_eq!(n, 0b000011);
p += 6;
let e = read_bits_b(INPUT, p, 1);
assert!(if let Err(_) = e {true} else {false});
let e = read_bits_b(INPUT, p, 2);
assert!(if let Err(_) = e {true} else {false});
let mut p = 0;
let n = read_bits_l(INPUT, 0, 3).unwrap();
assert_eq!(n, 0b101);
p += 3;
let n = read_bits_l(INPUT, p, 63).unwrap();
assert_eq!(n, 0b010011001110101100111000011111111100000000000100001010101001100);
p += 63;
let n = read_bits_l(INPUT, p, 4).unwrap();
assert_eq!(n, 0b1001);
p += 4;
let n = read_bits_l(INPUT, p, 7).unwrap();
assert_eq!(n, 0b0000010);
p += 7;
let n = read_bits_l(INPUT, p, 17).unwrap();
assert_eq!(n, 0b00000100000111111);
p += 17;
let n = read_bits_l(INPUT, p, 27).unwrap();
assert_eq!(n, 0b000101011101011111100101000);
p += 27;
let n = read_bits_l(INPUT, p, 33).unwrap();
assert_eq!(n, 0b010101010110100011110101010110101);
p += 33;
let n = read_bits_l(INPUT, p, 6).unwrap();
assert_eq!(n, 0b110000);
p += 6;
let e = read_bits_l(INPUT, p, 1);
assert!(if let Err(_) = e {true} else {false});
let e = read_bits_l(INPUT, p, 2);
assert!(if let Err(_) = e {true} else {false});
}
// EOF

View File

@ -1,88 +0,0 @@
#![doc(hidden)]
/// A wrapper for `bitflags!` which adds a `FromStr` implementation.
#[macro_export]
macro_rules! c_bitfield
{
(
$(#[$outer:meta])*
pub struct $t:ident: $ti:ty {
$(
$(#[$inner:ident $($args:tt)*])*
$f:ident = $v:expr
),+
$(,)?
}
) => {
bitflags! {
$(#[$outer])*
pub struct $t: $ti {
$(
$(#[$inner $($args)*])*
const $f = 1 << $v;
)+
}
}
#[allow(unused_qualifications)]
impl std::str::FromStr for $t
{
type Err = $crate::err::ParseFlagError;
fn from_str(s: &str) -> Result<Self, Self::Err>
{
let mut flags = Self::empty();
for s in s.split('|') {
match s {
$(
stringify!($f) => flags.insert(Self::$f),
)+
"(none)" => (),
_ => return Err(Self::Err::new(stringify!($t)))
}
}
Ok(flags)
}
}
}
}
#[cfg(test)]
mod test
{
use crate::err::ParseFlagError;
use std::str::FromStr;
c_bitfield! {
pub struct TestFlag: u16 {
ZERO = 0,
ONE = 1,
TWO = 2,
}
}
#[test]
fn c_bitfield()
{
assert_eq!(TestFlag::from_bits(0), Some(TestFlag::empty()));
assert_eq!(TestFlag::from_bits(1), Some(TestFlag::ZERO));
assert_eq!(TestFlag::from_bits(2), Some(TestFlag::ONE));
assert_eq!(TestFlag::from_bits(4), Some(TestFlag::TWO));
assert_eq!(TestFlag::from_bits(8), None);
assert_eq!(TestFlag::from_str("(none)"), Ok(TestFlag::empty()));
assert_eq!(TestFlag::from_str("ZERO"), Ok(TestFlag::ZERO));
assert_eq!(TestFlag::from_str("ONE"), Ok(TestFlag::ONE));
assert_eq!(TestFlag::from_str("TWO"), Ok(TestFlag::TWO));
assert_eq!(TestFlag::from_str("ZERO|ONE|TWO"), Ok(TestFlag::all()));
assert_eq!(TestFlag::from_str("TWO|ZERO|ONE"), Ok(TestFlag::all()));
assert_eq!(TestFlag::from_str("ONE|ONE|ONE"), Ok(TestFlag::ONE));
assert_eq!(TestFlag::from_str("(none)|(none)"), Ok(TestFlag::empty()));
assert_eq!(TestFlag::from_str("(none)|ONE"), Ok(TestFlag::ONE));
assert_eq!(TestFlag::from_str("THREE"),
Err(ParseFlagError::new("TestFlag")));
}
}
// EOF

View File

@ -1,120 +0,0 @@
#![doc(hidden)]
/// Creates an enumeration and function for converting a representation into the
/// enumeration type.
///
/// The syntax is similar to the `c_bitfield` macro, but each value has the
/// syntax `value = enumeration`. `enum` is used instead of `struct`.
///
/// This will generate an `enum $t` as well as implement `TryFrom` on it which
/// will return `Result<$t, ReprError>`.
///
/// # Examples
///
/// ```
/// use maraiah::{c_enum, err::ReprError};
/// use std::convert::TryFrom;
///
/// c_enum! {
/// enum MyEnum: u16 {
/// Zero = 0,
/// One = 1,
/// Two = 2
/// }
/// }
///
/// assert_eq!(MyEnum::try_from(0), Ok(MyEnum::Zero));
/// assert_eq!(MyEnum::try_from(1), Ok(MyEnum::One));
/// assert_eq!(MyEnum::try_from(2), Ok(MyEnum::Two));
/// assert_eq!(MyEnum::try_from(3), Err(ReprError::new("MyEnum", 3)));
/// assert_eq!(MyEnum::try_from(4), Err(ReprError::new("MyEnum", 4)));
/// assert_eq!(MyEnum::try_from(5), Err(ReprError::new("MyEnum", 5)));
/// ```
#[macro_export]
macro_rules! c_enum
{
(
$(#[$outer:meta])*
$vi:vis enum $t:ident: $ti:ident {
$(
$(#[$inner:meta])*
$en:ident = $va:expr
),+
$(,)?
}
) => {
$(#[$outer])*
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr($ti)]
$vi enum $t {
$(
$(#[$inner])*
$en = $va,
)+
}
#[allow(unused_qualifications)]
impl std::convert::TryFrom<$ti> for $t
{
type Error = $crate::err::ReprError;
/// Returns, if representable, the variant of `Self` from `n`.
fn try_from(n: $ti) -> Result<Self, Self::Error>
{
match n {
$($va => Ok($t::$en),)+
n => Err(Self::Error::new(stringify!($t), n))
}
}
}
#[allow(unused_qualifications)]
impl std::str::FromStr for $t
{
type Err = $crate::err::ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err>
{
match s {
$(
stringify!($en) => Ok($t::$en),
)+
_ => Err(Self::Err::new(stringify!($t)))
}
}
}
};
}
#[cfg(test)]
mod test
{
use crate::err::{ParseEnumError, ReprError};
use std::{convert::TryFrom, str::FromStr};
c_enum! {
enum TestEnum: u16 {
Zero = 0,
One = 1,
Two = 2,
}
}
#[test]
fn c_enum()
{
assert_eq!(TestEnum::try_from(0), Ok(TestEnum::Zero));
assert_eq!(TestEnum::try_from(1), Ok(TestEnum::One));
assert_eq!(TestEnum::try_from(2), Ok(TestEnum::Two));
assert_eq!(TestEnum::try_from(3), Err(ReprError::new("TestEnum", 3)));
assert_eq!(TestEnum::try_from(4), Err(ReprError::new("TestEnum", 4)));
assert_eq!(TestEnum::try_from(5), Err(ReprError::new("TestEnum", 5)));
assert_eq!(TestEnum::from_str("Zero"), Ok(TestEnum::Zero));
assert_eq!(TestEnum::from_str("One"), Ok(TestEnum::One));
assert_eq!(TestEnum::from_str("Two"), Ok(TestEnum::Two));
assert_eq!(TestEnum::from_str("Three"),
Err(ParseEnumError::new("TestEnum")));
}
}
// EOF

View File

@ -1,66 +0,0 @@
//! Checksum functions.
// Accumulator for CRC function.
fn crc_accum(a: u32, _: u32) -> u32
{
if a & 1 == 1 {
ISO_3309_POLYNOMIAL ^ a >> 1
} else {
a >> 1
}
}
// Initializes a CRC array.
// FIXME: use const fn when stabilized
fn crc_init() -> [u32; 256]
{
let mut t = [0; 256];
for (n, v) in t.iter_mut().enumerate() {
*v = (0..8).fold(n as u32, crc_accum);
}
t
}
/// Creates an ADLER32 of all bytes in `b`.
///
/// # Examples
///
/// ```
/// use maraiah::cksum::adler32;
///
/// assert_eq!(adler32(b"Lorem ipsum dolor sit amet"), 0x83D5_09C5);
/// ```
pub fn adler32(b: &[u8]) -> u32
{
let mut x = 1;
let mut y = 0;
for &z in b {
let z = u32::from(z);
x = (x + z) % ADLER32_MODULO;
y = (y + x) % ADLER32_MODULO;
}
(y << 16) | x
}
/// Creates a CRC-32 of all bytes in `b` with the starting sum `s`. The
/// polynomial used is the one from ISO-3309.
///
/// # Examples
///
/// ```
/// use maraiah::cksum::crc32;
///
/// assert_eq!(crc32(b"Lorem ipsum dolor sit amet", !0), 0x5F29_D461);
/// ```
pub fn crc32(b: &[u8], s: u32) -> u32
{
let t = crc_init();
!b.iter().fold(s, |a, &o| a >> 8 ^ t[usize::from(a as u8 ^ o)])
}
const ISO_3309_POLYNOMIAL: u32 = 0xEDB8_8320;
const ADLER32_MODULO: u32 = 0xFFF1;
// EOF

View File

@ -1,425 +0,0 @@
//! DEFLATE loader.
use crate::{bin::check_data, bit::*, err::*};
use std::cmp::Ordering;
/// Loads a ZLIB file header.
pub fn load_zlib_header(b: &[u8]) -> ResultS<usize>
{
const CM: u8 = 0b0000_1111;
const CINFO: u8 = 0b1111_0000;
const FDICT: u8 = 0b0010_0000;
read_data! {
endian: BIG, buf: b, size: 2, start: 0, data {
let fcheck = u16[0];
let cmf = u8[0];
let flg = u8[1];
}
}
let cm = cmf & CM;
let cinfo = cmf & CINFO;
if cm != 8 {
bail!("unknown compression method");
}
if cinfo > 7 << 4 {
bail!("lz77 window size logarithm is invalid");
}
if fcheck % 31 != 0 {
bail!("invalid fcheck");
}
if flg & FDICT != 0 {
bail!("dictionary not supported");
}
Ok(2)
}
/// Loads a GZIP file header.
pub fn load_gzip_header(b: &[u8]) -> ResultS<usize>
{
const FHCRC: u8 = 1 << 1;
const FEXTRA: u8 = 1 << 2;
const FNAME: u8 = 1 << 3;
const FCOMMENT: u8 = 1 << 4;
const FRESERVED: u8 = 0xe0;
read_data! {
endian: LITTLE, buf: b, size: 10, start: 0, data {
let id = u16[0];
let cm = u8[2];
let fl = u8[3];
}
}
if id != 0x8b1f || cm != 8 {
bail!("not gzip format");
}
let mut p = 10;
if fl & FRESERVED != 0 {
bail!("reserved flags set");
}
if fl & FEXTRA != 0 {
read_data! {
endian: LITTLE, buf: b, size: 2, start: p, data {
let xlen = u16[0] usize;
}
}
p += 2 + xlen;
check_data(b, p)?;
}
if fl & FNAME != 0 {
p += skip_zero_terminated_item(&b[p..])?;
}
if fl & FCOMMENT != 0 {
p += skip_zero_terminated_item(&b[p..])?;
}
if fl & FHCRC != 0 {
p += 2;
check_data(b, p)?;
}
Ok(p)
}
fn skip_zero_terminated_item(b: &[u8]) -> ResultS<usize>
{
if let Some(i) = b.iter().position(|&n| n == 0) {
Ok(i + 1)
} else {
bail!("no end of zero terminated item");
}
}
/// Decompresses a DEFLATE compressed bitstream.
pub fn load_deflate(b: &[u8]) -> ResultS<(usize, Vec<u8>)>
{
let mut v = Vec::new();
let mut p = 0;
loop {
let bfinal = read_bits_l(b, p, 1)?;
p += 1;
let btype = read_bits_l(b, p, 2)?;
p += 2;
match btype {
0b10 => p = stream_dynamic(&mut v, b, p)?,
0b01 => p = stream_s_table(&mut v, b, p)?,
0b00 => p = stream_literal(&mut v, b, p)?,
_ => bail!("bad btype"),
}
if bfinal == 1 {
return Ok((p / 8, v));
}
}
}
fn stream_dynamic(v: &mut Vec<u8>, b: &[u8], mut p: usize) -> ResultS<usize>
{
const CODE_ORDERING: [usize; 19] =
[16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
// read header (number of literal alphabet codes, number of distance
// alphabet codes, and number of lengths for decoding the alphabet)
let hlit = read_bits_l(b, p, 5)?;
p += 5;
let hdist = read_bits_l(b, p, 5)?;
p += 5;
let hclen = read_bits_l(b, p, 4)?;
p += 4;
let hlit = 257 + hlit as usize;
let hdist = 1 + hdist as usize;
let hclen = 4 + hclen as usize;
// first, get the huffman coding for the alphabet (which is also compressed)
let mut code_table = [0; 19];
for i in 0..hclen {
let len = read_bits_l(b, p, 3)? as u16;
p += 3;
code_table[CODE_ORDERING[i]] = len;
}
// then, we decode the alphabet (doing both types at the same time, because
// they're encoded the same anyways)
let code_table = HuffmanTable::new(&code_table)?;
let mut alphabet = vec![0; hlit + hdist];
p = read_alphabet(b, p, &mut alphabet, code_table)?;
if alphabet[256] == 0 {
bail!("no way to end block");
}
// build the length and distance tables from this information
let table_len = HuffmanTable::new(&alphabet[0..hlit])?;
let table_dst = HuffmanTable::new(&alphabet[hlit..hlit + hdist])?;
output_tables(v, b, p, table_len, table_dst)
}
#[allow(clippy::needless_range_loop)]
fn stream_s_table(v: &mut Vec<u8>, b: &[u8], p: usize) -> ResultS<usize>
{
let mut len = [0; 288];
for i in 0..144 {len[i] = 8;}
for i in 144..256 {len[i] = 9;}
for i in 256..280 {len[i] = 7;}
for i in 280..288 {len[i] = 8;}
let dst = [5; 30];
let table_len = HuffmanTable::new(&len)?;
let table_dst = HuffmanTable::new(&dst)?;
output_tables(v, b, p, table_len, table_dst)
}
fn stream_literal(v: &mut Vec<u8>, b: &[u8], p: usize) -> ResultS<usize>
{
// copy data directly from byte boundary
let mut p = p / 8 + 1;
read_data! {
endian: LITTLE, buf: b, size: 4, start: p, data {
let len = u16[0] usize;
}
}
p += 4;
v.extend(ok!(b.get(p..p + len), "not enough data")?);
Ok((p + len) * 8)
}
fn read_alphabet(b: &[u8],
mut p: usize,
alphabet: &mut [u16],
code_table: HuffmanTable) -> ResultS<usize>
{
let mut i = 0;
while i < alphabet.len() {
let (bits, sym) = code_table.decode(b, p)?;
p += bits;
match sym {
0..=15 => {
// raw code
alphabet[i] = sym;
i += 1;
}
16 => {
// copy previous code 3-6 times
if i == 0 {
bail!("cannot copy on first alphabet code");
}
let len = usize::from(read_bits_l(b, p, 2)? as u8 + 3);
let lst = alphabet[i - 1];
p += 2;
for _ in 0..len {
alphabet[i] = lst;
i += 1;
}
}
17 => {
// repeat '0' 3-10 times
let len = usize::from(read_bits_l(b, p, 3)? as u8 + 3);
p += 3;
for _ in 0..len {
alphabet[i] = 0;
i += 1;
}
}
18 => {
// repeat '0' 11-138 times
let len = usize::from(read_bits_l(b, p, 7)? as u8 + 11);
p += 7;
for _ in 0..len {
alphabet[i] = 0;
i += 1;
}
}
_ => {
bail!("bad symbol in alphabet");
}
}
if i > alphabet.len() {
bail!("too many codes");
}
}
Ok(p)
}
fn output_tables(v: &mut Vec<u8>,
b: &[u8],
mut p: usize,
table_len: HuffmanTable,
table_dst: HuffmanTable) -> ResultS<usize>
{
const LEN_BASE: [usize; 29] = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19,
23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115,
131, 163, 195, 227, 258];
const LEN_EXTRA_BITS: [u8; 29] = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
0];
const DST_BASE: [usize; 30] = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65,
97, 129, 193, 257, 385, 513, 769, 1025,
1537, 2049, 3073, 4097, 0x1801, 0x2001,
0x3001, 0x4001, 0x6001];
const DST_EXTRA_BITS: [u8; 30] = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13];
loop {
let (bits, sym) = table_len.decode(b, p)?;
p += bits;
match sym.cmp(&256) {
Ordering::Less => {
// direct byte
v.push(sym as u8);
}
Ordering::Equal => {
return Ok(p);
}
Ordering::Greater => {
// this is a <len, dst> pair
let sym = sym - 257;
if sym > 29 {
bail!("invalid fixed code");
}
let sym = usize::from(sym);
// first get the actual length and any extra bits it may have
let bits = LEN_EXTRA_BITS[sym];
let leng = LEN_BASE[sym] + read_bits_l(b, p, bits)? as usize;
p += usize::from(bits);
// decode the distance with its alphabet
let (bits, sym) = table_dst.decode(b, p)?;
p += bits;
let sym = usize::from(sym);
// get the actual distance and any extra bits it may have
let bits = DST_EXTRA_BITS[sym];
let dist = DST_BASE[sym] + read_bits_l(b, p, bits)? as usize;
p += usize::from(bits);
if dist > v.len() {
bail!("bad distance");
}
// copy bytes from earlier
for _ in 0..leng {
v.push(v[v.len() - dist]);
}
}
}
}
}
impl HuffmanTable
{
fn new(table: &[u16]) -> ResultS<Self>
{
let mut syms = vec![0; table.len()];
let mut nums = [0; 16];
// count the number of symbols for each bit length
for &length in table {
nums[usize::from(length)] += 1;
}
if usize::from(nums[0]) == table.len() {
bail!("bad table lengths");
}
// make offsets into the symbol table for each bit count
let mut ofs = [0; 16];
for i in 1..=14 {
ofs[i + 1] = ofs[i] + usize::from(nums[i]);
}
// make the actual bit pattern table
for (n, &length) in table.iter().enumerate() {
// length 0 means this code isn't used, so only try to make bit
// patterns for codes that actually exist
if length != 0 {
// make sure to make each offset unique
let offset = &mut ofs[usize::from(length)];
syms[*offset] = n as u16;
*offset += 1;
}
}
Ok(Self{nums, syms})
}
fn decode(&self, b: &[u8], mut p: usize) -> ResultS<(usize, u16)>
{
let mut code = 0_u16;
let mut first = 0_u16;
let mut index = 0_u16;
for i in 1..=15 {
// add bit from file
code |= read_bits_l(b, p, 1)? as u16;
p += 1;
// check our symbol table for this one (quick tree check)
let count = self.nums[i];
if i32::from(code) - i32::from(count) < i32::from(first) {
return Ok((i, self.syms[usize::from(index + code - first)]));
}
// continue on, trying to find the correct sequence
index += count;
first += count;
first <<= 1;
code <<= 1;
}
Err(ReprError::new("DEFLATE code", code).into())
}
}
struct HuffmanTable {
nums: [u16; 16],
syms: Vec<u16>,
}
// EOF

View File

@ -1,22 +0,0 @@
#![doc(hidden)]
/// Macro-izes `#[doc = x]`.
///
/// # Examples
///
/// ```
/// use maraiah::doc_comment;
///
/// doc_comment! {
/// concat!("Have a nice", " day", "!"),
/// const A: u32 = 5;
/// }
///
/// assert_eq!(A, 5);
/// ```
#[macro_export]
macro_rules! doc_comment {
($x:expr, $($tt:tt)*) => {#[doc = $x] $($tt)*}
}
// EOF

View File

@ -1,167 +0,0 @@
//! Error handling.
pub use failure::{Error, Fail};
use std::fmt;
macro_rules! ok {
($v:expr, $msg:expr) => {
match $v {
Some(v) => Ok(v),
None => Err($crate::err::err_msg($msg)),
}
};
}
macro_rules! flag_ok {
($t:ident$(::$tc:ident)*, $v:expr) => {
{
let v = $v;
match $t$(::$tc)*::from_bits(v) {
Some(v) => Ok(v),
None => Err($crate::err::ReprError::new(stringify!($t), v)),
}
}
};
}
macro_rules! bail {
($e:expr) => {
return Err($crate::err::err_msg($e));
};
}
#[macro_export]
macro_rules! backtrace {
($e:expr) => {
if cfg!(debug_assertions) {
dbg!($e.backtrace());
}
}
}
/// Returns an `Error` with a static string.
///
/// # Examples
///
/// ```
/// use maraiah::err::err_msg;
///
/// assert_eq!(format!("{}", err_msg("oh no not things")),
/// "oh no not things");
/// ```
pub fn err_msg(msg: &'static str) -> Error {ErrMsg(msg).into()}
impl ParseEnumError
{
/// Returns an `Error` with a message for enumeration parsing errata.
///
/// # Examples
///
/// ```
/// use maraiah::err::ParseEnumError;
///
/// assert_eq!(format!("{}", ParseEnumError::new("TypeName")),
/// "could not parse TypeName");
/// ```
#[inline]
pub const fn new(t: &'static str) -> Self
{
Self(t)
}
}
impl ParseFlagError
{
/// Returns an `Error` with a message for flag parsing errata.
///
/// # Examples
///
/// ```
/// use maraiah::err::ParseFlagError;
///
/// assert_eq!(format!("{}", ParseFlagError::new("TypeName")),
/// "could not parse TypeName");
/// ```
#[inline]
pub const fn new(t: &'static str) -> Self
{
Self(t)
}
}
impl ReprError
{
/// Returns an `Error` with a message for representation errata.
///
/// # Examples
///
/// ```
/// use maraiah::err::ReprError;
///
/// assert_eq!(format!("{}", ReprError::new("TypeName", 77)),
/// "bad TypeName (77)");
/// ```
pub fn new<T: Into<i64>>(t: &'static str, n: T) -> Self
{
Self(t, n.into())
}
}
impl Fail for ErrMsg {}
impl Fail for ParseEnumError {}
impl Fail for ParseFlagError {}
impl Fail for ReprError {}
impl fmt::Display for ParseEnumError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "could not parse {}", self.0)
}
}
impl fmt::Display for ParseFlagError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "could not parse {}", self.0)
}
}
impl fmt::Display for ReprError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "bad {} ({})", self.0, self.1)
}
}
impl fmt::Display for ErrMsg
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
f.write_str(self.0)
}
}
#[derive(Clone, Debug)]
struct ErrMsg(&'static str);
/// A parser error for an enumeration.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ParseEnumError(&'static str);
/// A parser error for a bit field.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ParseFlagError(&'static str);
/// A representation error for an integer.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ReprError(&'static str, i64);
/// A generic `failure` based `Result` type.
pub type ResultS<T> = Result<T, Error>;
// EOF

View File

@ -1,96 +0,0 @@
//! Foreign function interface utilities.
use crate::err::*;
pub use std::{ffi::*, os::raw::*, ptr::{null, null_mut}};
/// Creates a C string from a literal.
///
/// This is done by concatenating a null byte with the string and converting it
/// to a pointer.
///
/// # Examples
///
/// ```
/// use maraiah::c_str;
///
/// let st = c_str!("test");
///
/// assert!(!st.is_null());
///
/// unsafe {
/// assert_eq!(std::slice::from_raw_parts(st, 5), &[116, 101, 115, 116, 0]);
/// }
/// ```
#[macro_export]
macro_rules! c_str {
($s:expr) => {
concat!($s, "\0").as_ptr() as $crate::ffi::NT
};
}
/// Returns [`null`] as a [`*const c_void`].
///
/// [`null`]: fn.null.html
/// [`*const c_void`]: enum.c_void.html
#[inline]
pub const fn null_void() -> *const c_void {null()}
/// Returns [`null_mut`] as a [`*mut c_void`].
///
/// [`null_mut`]: fn.null_mut.html
/// [`*mut c_void`]: enum.c_void.html
#[inline]
pub const fn null_mut_void() -> *mut c_void {null_mut()}
impl CStringVec
{
/// Creates a new `CStringVec` from an iterator.
#[inline]
pub fn new_from_iter<'a, I>(it: I) -> ResultS<Self>
where I: Iterator<Item = &'a str>
{
let mut v = Self::default();
for st in it {
v.push(CString::new(st)?);
}
Ok(v)
}
/// Pushes a new `CString`.
#[inline]
pub fn push(&mut self, st: CString)
{
self.cv.insert(self.cv.len() - 1, st.as_ptr());
self.sv.push(st);
}
/// Returns the FFI pointer.
#[inline]
pub fn as_ptr(&self) -> *const NT {self.cv.as_ptr()}
/// Returns the FFI pointer mutably.
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut NT {self.cv.as_mut_ptr()}
}
impl Default for CStringVec
{
/// Creates a new empty CStringVec.
#[inline]
fn default() -> Self {Self{sv: Vec::new(), cv: vec![null()]}}
}
/// An owned null-terminated string vector.
#[derive(Debug)]
pub struct CStringVec {
sv: Vec<CString>,
cv: Vec<NT>,
}
/// A null-terminated byte string pointer.
pub type NT = *const c_char;
// EOF

View File

@ -1,67 +0,0 @@
//! File utilities.
use crate::{err::*, machdr};
use std::{fs, path::Path, io::{SeekFrom, prelude::*}};
/// Confirms that the path `p` is a folder.
pub fn validate_folder_path(p: &str) -> ResultS<()>
{
let at = fs::metadata(p)?;
if at.is_dir() {
Ok(())
} else {
Err(err_msg("not a directory"))
}
}
/// Opens the file at `path` and skips past any macintosh headers.
pub fn open_mac<P: AsRef<Path>>(path: P) -> ResultS<fs::File>
{
let mut fp = fs::File::open(path)?;
machdr::skip_mac_header(&mut fp);
Ok(fp)
}
impl<T> Drop for SeekBackToStart<T>
where T: Seek
{
fn drop(&mut self) {if self.fl {let _ = self.seek(SeekFrom::Start(0));}}
}
impl<T> std::ops::Deref for SeekBackToStart<T>
where T: Seek
{
type Target = T;
fn deref(&self) -> &Self::Target {&self.sc}
}
impl<T> std::ops::DerefMut for SeekBackToStart<T>
where T: Seek
{
fn deref_mut(&mut self) -> &mut Self::Target {&mut self.sc}
}
impl<T> SeekBackToStart<T>
where T: Seek
{
/// Creates a new `SeekBackToStart` object.
pub fn new(sc: T) -> Self {Self{sc, fl: true}}
/// Sets the seek-back flag.
pub fn set_seek(&mut self, fl: bool) {self.fl = fl;}
/// Returns the seek-back flag.
pub fn get_seek(&self) -> bool {self.fl}
}
/// Seeks back to the starting position of the inner object when losing scope,
/// unless a flag is set.
pub struct SeekBackToStart<T: Seek> {
sc: T,
fl: bool,
}
// EOF

View File

@ -1,639 +0,0 @@
//! Fixed point numbers.
#![allow(clippy::use_self)]
#![allow(clippy::cast_lossless)]
use std::{fmt::{self, Write}, ops::*};
macro_rules! fixed_ref_unop {
(impl $imp:ident, $method:ident for $t:ty) => {
impl $imp for &$t
{
type Output = <$t as $imp>::Output;
#[inline]
fn $method(self) -> <$t as $imp>::Output {$imp::$method(*self)}
}
};
}
macro_rules! fixed_ref_binop {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<$u> for &'a $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, o)
}
}
impl<'a> $imp<&'a $u> for $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(self, *o)
}
}
impl<'a, 'b> $imp<&'a $u> for &'b $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, *o)
}
}
};
}
macro_rules! fixed_ref_op_assign {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<&'a $u> for $t
{
#[inline]
fn $method(&mut self, o: &'a $u) {$imp::$method(self, *o);}
}
};
}
macro_rules! define_fixed_types {
(
$(
$(#[$outer:meta])*
struct $t:ident ( $ti:ident, $bytes:expr ) :
$tu:ident, $tb:ident, $frac_bits:expr; $test:ident
)*
) => {$(
$(#[$outer])*
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct $t($ti);
impl $t
{
/// The number of fractional bits in this type.
#[inline]
pub const fn frac_bits() -> $tu {$frac_bits}
/// The unsigned mask for the fractional bits.
#[inline]
pub const fn frac_mask() -> $tu {(1 << $t::frac_bits()) - 1}
/// The integer mask for the fractional bits.
#[inline]
pub const fn frac_mask_i() -> $ti {(1 << $t::frac_bits()) - 1}
/// The representation of `1.0` in this type, unsigned.
#[inline]
pub const fn one() -> $tu {1 << $t::frac_bits()}
/// The representation of `1.0` in this type, signed.
#[inline]
pub const fn one_i() -> $ti {1 << $t::frac_bits()}
/// Returns the largest value that can be represented.
#[inline]
pub const fn max_value() -> $t {$t($ti::max_value())}
/// Returns the smallest value that can be represented.
#[inline]
pub const fn min_value() -> $t {$t($ti::min_value())}
/// Returns the integer part of a number.
#[inline]
pub const fn trunc(self) -> $t {$t(self.0 & !$t::frac_mask_i())}
/// Returns the fractional part of a number.
#[inline]
pub const fn fract(self) -> $t {$t(self.0 & $t::frac_mask_i())}
/// Returns the integer part of a number as an integer.
#[inline]
pub const fn integ(self) -> $ti {self.0 >> $t::frac_bits()}
/// Returns the number of ones in the bit representation of self.
#[inline]
pub const fn count_ones(self) -> u32 {self.0.count_ones()}
/// Returns the number of zeros in the bit representation of self.
#[inline]
pub const fn count_zeros(self) -> u32 {self.0.count_zeros()}
/// Returns the number of leading zeros in the bit representation of
/// self.
#[inline]
pub const fn leading_zeros(self) -> u32 {self.0.leading_zeros()}
/// Returns the number of trailing zeros in the bit representation of
/// self.
#[inline]
pub const fn trailing_zeros(self) -> u32 {self.0.trailing_zeros()}
/// Rotates all bits left by `n`.
#[inline]
pub const fn rotate_left(self, n: u32) -> $t
{
$t(self.0.rotate_left(n))
}
/// Rotates all bits right by `n`.
#[inline]
pub const fn rotate_right(self, n: u32) -> $t
{
$t(self.0.rotate_right(n))
}
/// Reverses the byte order of the bit representation of self.
#[inline]
pub const fn swap_bytes(self) -> $t {$t(self.0.swap_bytes())}
/// Raises self to the power of `exp`.
#[inline]
pub fn pow(self, exp: u32) -> $t {$t(self.0.pow(exp))}
/// Returns the absolute value of self.
#[inline]
pub fn abs(self) -> $t {$t(self.0.abs())}
/// Returns a number representing sign of self.
#[inline]
pub fn signum(self) -> $t {$t(self.0.signum() << $t::frac_bits())}
/// Returns true if self is positive and false if the number is zero
/// or negative.
#[inline]
pub const fn is_positive(self) -> bool {self.0.is_positive()}
/// Returns true if self is negative and false if the number is zero
/// or positive.
#[inline]
pub const fn is_negative(self) -> bool {self.0.is_negative()}
/// Return the memory representation of this integer as a byte array
/// in big-endian (network) byte order.
#[allow(clippy::missing_const_for_fn)] // HACK: clippy is wrong
#[inline]
pub fn to_be_bytes(self) -> [u8; $bytes] {self.0.to_be_bytes()}
/// Return the memory representation of this integer as a byte array
/// in little-endian byte order.
#[allow(clippy::missing_const_for_fn)] // HACK: clippy is wrong
#[inline]
pub fn to_le_bytes(self) -> [u8; $bytes] {self.0.to_le_bytes()}
/// Create a value from its representation as a byte array in
/// big-endian byte order.
#[allow(clippy::missing_const_for_fn)] // HACK: clippy is wrong
#[inline]
pub fn from_be_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_be_bytes(b))
}
/// Create a value from its representation as a byte array in
/// little-endian byte order.
#[allow(clippy::missing_const_for_fn)] // HACK: clippy is wrong
#[inline]
pub fn from_le_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_le_bytes(b))
}
/// Creates a value of this type with the bit pattern `bits`.
#[inline]
pub const fn from_bits(bits: $tu) -> $t {$t(bits as $ti)}
/// Creates a value of this type with the integral portion `n`.
#[inline]
pub const fn from_int(n: $ti) -> $t {$t(n << $t::frac_bits())}
/// Creates a value of this type from a fraction.
#[inline]
pub const fn from_frac(x: $ti, y: $ti) -> $t
{
$t($t::one_i() * x / y + 1)
}
/// Returns the raw bit pattern.
#[inline]
pub const fn to_bits(self) -> $tu {self.0 as $tu}
/// Sets the raw bit pattern to `bits`.
#[inline]
pub fn set_bits(&mut self, bits: $tu) {self.0 = bits as $ti}
#[inline]
const fn mul_i(x: $ti, y: $ti) -> $ti {x * y}
#[inline]
const fn div_i(x: $ti, y: $ti) -> $ti {x / y}
#[inline]
const fn div_k(x: $ti, y: $ti) -> $ti
{
(x as $tb * $t::one() as $tb / y as $tb) as $ti
}
#[inline]
const fn mul_k(x: $ti, y: $ti) -> $ti
{
(x as $tb * y as $tb / $t::one() as $tb) as $ti
}
}
#[cfg(test)]
mod $test {
use super::$t;
#[test]
fn basic_ops()
{
let one = $t::one();
let two = 2 << $t::frac_bits();
let twelve = 12 << $t::frac_bits();
assert_eq!(($t::from(1) + $t::from(1)).to_bits(), two);
assert_eq!(($t::from(2) - $t::from(1)).to_bits(), one);
assert_eq!(($t::from(6) * $t::from(2)).to_bits(), twelve);
assert_eq!(($t::from(6) * 2) .to_bits(), twelve);
}
#[test]
fn fractions()
{
let three_pt_5 = 3 << $t::frac_bits() | $t::one() / 2;
let one_pt_2 = 1 << $t::frac_bits() | $t::one() / 5;
let two_pt_4 = one_pt_2 * 2;
assert_eq!(($t::from(7) / $t::from(2)) .to_bits(), three_pt_5);
assert_eq!(($t::from(7) / 2) .to_bits(), three_pt_5);
assert_eq!(($t::from_bits(one_pt_2) * 2).to_bits(), two_pt_4);
}
#[test]
fn printing()
{
assert_eq!(format!("{}", $t::from(6)), "6.0");
assert_eq!(format!("{:2.3}", $t::from(7) / 2), " 3.500");
}
}
impl From<$ti> for $t
{
#[inline]
fn from(n: $ti) -> $t {$t::from_int(n)}
}
impl Add<$t> for $t
{
type Output = $t;
#[inline]
fn add(self, o: $t) -> $t {$t(self.0 + o.0)}
}
fixed_ref_binop! {impl Add, add for $t, $t}
impl Sub<$t> for $t
{
type Output = $t;
#[inline]
fn sub(self, o: $t) -> $t {$t(self.0 - o.0)}
}
fixed_ref_binop! {impl Sub, sub for $t, $t}
impl Mul<$t> for $t
{
type Output = $t;
#[inline]
fn mul(self, o: $t) -> $t {$t($t::mul_k(self.0, o.0))}
}
fixed_ref_binop! {impl Mul, mul for $t, $t}
impl Mul<$ti> for $t
{
type Output = $t;
#[inline]
fn mul(self, o: $ti) -> $t {$t($t::mul_i(self.0, o))}
}
fixed_ref_binop! {impl Mul, mul for $t, $ti}
impl Div<$t> for $t
{
type Output = $t;
#[inline]
fn div(self, o: $t) -> $t {$t($t::div_k(self.0, o.0))}
}
fixed_ref_binop! {impl Div, div for $t, $t}
impl Div<$ti> for $t
{
type Output = $t;
#[inline]
fn div(self, o: $ti) -> $t {$t($t::div_i(self.0, o))}
}
fixed_ref_binop! {impl Div, div for $t, $ti}
impl BitAnd<$t> for $t
{
type Output = $t;
#[inline]
fn bitand(self, o: $t) -> $t {$t(self.0 & o.0)}
}
fixed_ref_binop! {impl BitAnd, bitand for $t, $t}
impl BitAnd<$ti> for $t
{
type Output = $t;
#[inline]
fn bitand(self, o: $ti) -> $t {$t(self.0 & o)}
}
fixed_ref_binop! {impl BitAnd, bitand for $t, $ti}
impl BitOr<$t> for $t
{
type Output = $t;
#[inline]
fn bitor(self, o: $t) -> $t {$t(self.0 | o.0)}
}
fixed_ref_binop! {impl BitOr, bitor for $t, $t}
impl BitOr<$ti> for $t
{
type Output = $t;
#[inline]
fn bitor(self, o: $ti) -> $t {$t(self.0 | o)}
}
fixed_ref_binop! {impl BitOr, bitor for $t, $ti}
impl BitXor<$t> for $t
{
type Output = $t;
#[inline]
fn bitxor(self, o: $t) -> $t {$t(self.0 ^ o.0)}
}
fixed_ref_binop! {impl BitXor, bitxor for $t, $t}
impl BitXor<$ti> for $t
{
type Output = $t;
#[inline]
fn bitxor(self, o: $ti) -> $t {$t(self.0 ^ o)}
}
fixed_ref_binop! {impl BitXor, bitxor for $t, $ti}
impl Shl<$ti> for $t
{
type Output = $t;
#[inline]
fn shl(self, o: $ti) -> $t {$t(self.0 << o)}
}
fixed_ref_binop! {impl Shl, shl for $t, $ti}
impl Shr<$ti> for $t
{
type Output = $t;
#[inline]
fn shr(self, o: $ti) -> $t {$t(self.0 >> o)}
}
fixed_ref_binop! {impl Shr, shr for $t, $ti}
impl AddAssign<$t> for $t
{
#[inline]
fn add_assign(&mut self, o: $t) {self.0 += o.0}
}
fixed_ref_op_assign! {impl AddAssign, add_assign for $t, $t}
impl SubAssign<$t> for $t
{
#[inline]
fn sub_assign(&mut self, o: $t) {self.0 -= o.0}
}
fixed_ref_op_assign! {impl SubAssign, sub_assign for $t, $t}
impl MulAssign<$t> for $t
{
#[inline]
fn mul_assign(&mut self, o: $t) {self.0 = (*self * o).0}
}
fixed_ref_op_assign! {impl MulAssign, mul_assign for $t, $t}
impl MulAssign<$ti> for $t
{
#[inline]
fn mul_assign(&mut self, o: $ti) {self.0 = (*self * o).0}
}
fixed_ref_op_assign! {impl MulAssign, mul_assign for $t, $ti}
impl DivAssign<$t> for $t
{
#[inline]
fn div_assign(&mut self, o: $t) {self.0 = (*self / o).0}
}
fixed_ref_op_assign! {impl DivAssign, div_assign for $t, $t}
impl DivAssign<$ti> for $t
{
#[inline]
fn div_assign(&mut self, o: $ti) {self.0 = (*self / o).0}
}
fixed_ref_op_assign! {impl DivAssign, div_assign for $t, $ti}
impl BitAndAssign<$t> for $t
{
#[inline]
fn bitand_assign(&mut self, o: $t) {self.0 = (*self & o).0}
}
fixed_ref_op_assign! {impl BitAndAssign, bitand_assign for $t, $t}
impl BitAndAssign<$ti> for $t
{
#[inline]
fn bitand_assign(&mut self, o: $ti) {self.0 = (*self & o).0}
}
fixed_ref_op_assign! {impl BitAndAssign, bitand_assign for $t, $ti}
impl BitOrAssign<$t> for $t
{
#[inline]
fn bitor_assign(&mut self, o: $t) {self.0 = (*self | o).0}
}
fixed_ref_op_assign! {impl BitOrAssign, bitor_assign for $t, $t}
impl BitOrAssign<$ti> for $t
{
#[inline]
fn bitor_assign(&mut self, o: $ti) {self.0 = (*self | o).0}
}
fixed_ref_op_assign! {impl BitOrAssign, bitor_assign for $t, $ti}
impl BitXorAssign<$t> for $t
{
#[inline]
fn bitxor_assign(&mut self, o: $t) {self.0 = (*self ^ o).0}
}
fixed_ref_op_assign! {impl BitXorAssign, bitxor_assign for $t, $t}
impl BitXorAssign<$ti> for $t
{
#[inline]
fn bitxor_assign(&mut self, o: $ti) {self.0 = (*self ^ o).0}
}
fixed_ref_op_assign! {impl BitXorAssign, bitxor_assign for $t, $ti}
impl ShlAssign<$ti> for $t
{
#[inline]
fn shl_assign(&mut self, o: $ti) {self.0 = (*self << o).0}
}
fixed_ref_op_assign! {impl ShlAssign, shl_assign for $t, $ti}
impl ShrAssign<$ti> for $t
{
#[inline]
fn shr_assign(&mut self, o: $ti) {self.0 = (*self >> o).0}
}
fixed_ref_op_assign! {impl ShrAssign, shr_assign for $t, $ti}
impl Neg for $t
{
type Output = $t;
#[inline]
fn neg(self) -> $t {$t(-self.0)}
}
fixed_ref_unop! {impl Neg, neg for $t}
impl Not for $t
{
type Output = $t;
#[inline]
fn not(self) -> $t {$t(!self.0)}
}
fixed_ref_unop! {impl Not, not for $t}
impl fmt::Display for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
let prec = f.precision().unwrap_or(1);
let widt = f.width().unwrap_or(0);
write!(f, "{:widt$}.", self.0 >> $t::frac_bits(), widt = widt)?;
let mut k = self.to_bits();
for _ in 0..prec {
k &= $t::frac_mask();
k *= 10;
let d = k >> $t::frac_bits();
let d = d % 10;
f.write_char(char::from(d as u8 + b'0'))?;
}
Ok(())
}
}
impl fmt::Debug for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f,
concat!(stringify!($t), "::from_bits({})"),
self.to_bits())
}
}
)*};
}
define_fixed_types! {
/// A fixed point type representing an angle.
///
/// The format of this type is `0.9s`, but because of the implementation,
/// the real format is `7.9s`.
struct Angle(i16, 2) : u16, i32, 9; angle_tests
/// A fixed point type representing a world unit.
///
/// The format of this type is `5.10s`. This has caused eternal suffering.
struct Unit(i16, 2) : u16, i32, 10; unit_tests
/// A generic fixed point type.
///
/// The format of this type is `15.16s`.
struct Fixed(i32, 4) : u32, i64, 16; fixed_tests
/// A generic, long fixed point type.
///
/// The format of this type is `31.32s`.
struct FixedLong(i64, 8) : u64, i128, 32; fixed_long_tests
}
impl FixedLong
{
/// Creates a value of this type from a `Unit`.
#[inline]
pub fn from_unit(n: Unit) -> Self {Self(i64::from(n.to_bits()) << 22)}
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn fixed_overflow() {Fixed::from(i16::max_value() as i32) + Fixed::from(1);}
// EOF

View File

@ -1,260 +0,0 @@
//! Image and color representations.
pub mod pict;
pub mod ppm;
pub mod tga;
/// Creates a RGB8 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::image::{r5g5b5_to_rgb8, Color8};
///
/// assert_eq!(r5g5b5_to_rgb8(0x2345), Color8::new(64, 208, 40));
/// ```
#[inline]
pub const fn r5g5b5_to_rgb8(rgb: u16) -> Color8
{
let r = (rgb >> 10) as u8 & 0x1f;
let g = (rgb >> 5) as u8 & 0x1f;
let b = rgb as u8 & 0x1f;
Color8::new(r << 3, g << 3, b << 3)
}
/// Creates a RGB16 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::image::{r5g5b5_to_rgb16, Color16};
///
/// assert_eq!(r5g5b5_to_rgb16(0x2345),
/// Color16::new(0x4000, 0xD000, 0x2800));
/// ```
#[inline]
pub const fn r5g5b5_to_rgb16(rgb: u16) -> Color16
{
let r = rgb >> 10 & 0x1f;
let g = rgb >> 5 & 0x1f;
let b = rgb & 0x1f;
Color16::new(r << 11, g << 11, b << 11)
}
/// Creates a RGB16 color from a RGB8 format color.
///
/// # Examples
///
/// ```
/// use maraiah::image;
///
/// let inpl = image::r5g5b5_to_rgb8(0x2345);
/// let inph = image::r5g5b5_to_rgb16(0x2345);
///
/// assert_eq!(inph, image::rgb8_to_rgb16(inpl));
/// ```
pub fn rgb8_to_rgb16(cr: Color8) -> Color16
{
Color16::new(cr.r(), cr.g(), cr.b())
}
/// A generic color matrix image.
pub trait Image
{
/// The type of color this image uses for each pixel.
type Output: Color;
/// Returns the width of the image.
fn w(&self) -> usize;
/// Returns the height of the image.
fn h(&self) -> usize;
/// Returns the color of the pixel at column `x` at row `y`.
///
/// # Panics
///
/// Panics if `x` is greater than the width of the image or `y` is greater
/// than the height of the image.
fn index(&self, x: usize, y: usize) -> &Self::Output;
/// The same as `index`, but will not panic if out of bounds.
fn get(&self, x: usize, y: usize) -> Option<&Self::Output>
{
if x < self.w() && y < self.h() {
Some(self.index(x, y))
} else {
None
}
}
}
/// A generic color matrix image, which may be mutated.
pub trait ImageMut: Image
{
/// Returns the color of the pixel at column `x` at row `y`.
///
/// # Panics
///
/// Panics if `x` is greater than the width of the image or `y` is greater
/// than the height of the image.
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output;
/// The same as `index_mut`, but will not panic if out of bounds.
fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Self::Output>
{
if x < self.w() && y < self.h() {
Some(self.index_mut(x, y))
} else {
None
}
}
}
/// Any color which may be represented as RGBA16.
pub trait Color: Sized + Copy + Clone + Eq + PartialEq
{
/// Returns the red component.
fn r(&self) -> u16;
/// Returns the green component.
fn g(&self) -> u16;
/// Returns the blue component.
fn b(&self) -> u16;
/// Returns the alpha component.
fn a(&self) -> u16;
}
impl Image16
{
/// Creates a new Image16 with no canvas.
pub fn new(w: usize, h: usize) -> Self
{
Self{w, h, cr: Vec::with_capacity(w * h)}
}
/// Creates a new Image16 with an empty canvas.
pub fn new_empty(w: usize, h: usize) -> Self
{
Self{w, h, cr: vec![Color16::new(0, 0, 0); w * h]}
}
}
impl Image for Image16
{
type Output = Color16;
fn w(&self) -> usize {self.w}
fn h(&self) -> usize {self.h}
fn index(&self, x: usize, y: usize) -> &Self::Output
{
&self.cr[x + y * self.w]
}
}
impl ImageMut for Image16
{
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output
{
&mut self.cr[x + y * self.w]
}
}
impl Image8
{
/// Creates a new Image8 with no canvas.
pub fn new(w: usize, h: usize) -> Self
{
Self{w, h, cr: Vec::with_capacity(w * h)}
}
/// Creates a new Image8 with an empty canvas.
pub fn new_empty(w: usize, h: usize) -> Self
{
Self{w, h, cr: vec![Color8::new(0, 0, 0); w * h]}
}
}
impl Image for Image8
{
type Output = Color8;
fn w(&self) -> usize {self.w}
fn h(&self) -> usize {self.h}
fn index(&self, x: usize, y: usize) -> &Self::Output
{
&self.cr[x + y * self.w]
}
}
impl ImageMut for Image8
{
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output
{
&mut self.cr[x + y * self.w]
}
}
impl Color16
{
pub const fn new(r: u16, g: u16, b: u16) -> Self {Self(r, g, b)}
}
impl Color for Color16
{
fn r(&self) -> u16 {self.0}
fn g(&self) -> u16 {self.1}
fn b(&self) -> u16 {self.2}
fn a(&self) -> u16 {u16::max_value()}
}
impl Color8
{
pub const fn new(r: u8, g: u8, b: u8) -> Self {Self(r, g, b)}
}
impl Color for Color8
{
fn r(&self) -> u16 {u16::from(self.0) << 8}
fn g(&self) -> u16 {u16::from(self.1) << 8}
fn b(&self) -> u16 {u16::from(self.2) << 8}
fn a(&self) -> u16 {u16::max_value()}
}
/// An RGB16 color.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Color16(u16, u16, u16);
/// An RGB8 color.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Color8(u8, u8, u8);
/// An RGB16 image.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Image16 {
w: usize,
h: usize,
/// The raw color data for this image.
pub cr: Vec<Color16>,
}
/// An RGB8 image.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Image8 {
w: usize,
h: usize,
/// The raw color data for this image.
pub cr: Vec<Color8>,
}
// EOF

View File

@ -1,117 +0,0 @@
//! QuickDraw PICT format loader.
pub mod clut;
pub mod pm;
pub mod rle;
use crate::{bin::*, err::*, image::*};
/// Load a `PICT` image.
pub fn read(b: &[u8]) -> ResultS<Image8>
{
read_data! {
endian: BIG, buf: b, size: 10, start: 0, data {
let h = u16[6] usize;
let w = u16[8] usize;
}
}
if w * h > 16_000_000 {
bail!("image is too large");
}
let im = Image8::new(w, h);
let mut p = 10; // size of header
while p < b.len() {
read_data! {
endian: BIG, buf: b, size: 2, start: p, data {
let op = u16[0];
}
}
p += 2;
match op {
0x0098 => {
// PackBitsRect
return pm::area::read(im, &b[p..], true, false);
}
0x0099 => {
// PackBitsRgn
return pm::area::read(im, &b[p..], true, true);
}
0x009a => {
// DirectBitsRect
return pm::area::read(im, &b[p..], false, false);
}
0x009b => {
// DirectBitsRgn
return pm::area::read(im, &b[p..], false, true);
}
0x8200 => {
// CompressedQuickTime
unimplemented!();
}
0x00ff => {
// OpEndPic
break;
}
// help i'm trapped in an awful metafile format from the 80s
0x0000 | // NoOp
0x001c | // HiliteMode
0x001e | // DefHilite
0x0038 | // FrameSameRect
0x0039 | // PaintSameRect
0x003a | // EraseSameRect
0x003b | // InvertSameRect
0x003c | // FillSameRect
0x8000 | // Reserved
0x8100 => (), // Reserved
0x0003 | // TxFont
0x0004 | // TxFace
0x0005 | // TxMode
0x0008 | // PnMode
0x000d | // TxSize
0x0011 | // VersionOp
0x0015 | // PnLocHFrac
0x0016 | // ChExtra
0x0023 | // ShortLineFrom
0x00a0 => p += 2, // ShortComment
0x0006 | // SpExtra
0x0007 | // PnSize
0x000b | // OvSize
0x000c | // Origin
0x000e | // FgCol
0x000f | // BkCol
0x0021 => p += 4, // LineFrom
0x001a | // RGBFgCol
0x001b | // RGBBkCol
0x001d | // TxRatio
0x0022 => p += 6, // ShortLine
0x0002 | // BkPat
0x0009 | // PnPat
0x0010 | // TxRatio
0x0020 | // Line
0x002e | // GlyphState
0x0030 | // FrameRect
0x0031 | // PaintRect
0x0032 | // EraseRect
0x0033 | // InvertRect
0x0034 => p += 8, // FillRect
0x002d => p += 10, // LineJustify
0x0001 => p += usize::from(u16b(&b[p.. ]) & !1), // Clip
0x00a1 => p += usize::from(u16b(&b[p+2..]) & !1) + 2, // LongComment
0x100..=
0x7fff => p += usize::from(op >> 8) * 2, // Reserved
_ => {
bail!("invalid op in PICT");
}
}
}
Err(err_msg("no image in data"))
}
// EOF

View File

@ -1,42 +0,0 @@
//! QuickDraw PICT color lookup tables.
use crate::{err::*, image::*};
/// Read a `ColorTable` structure.
pub fn read(b: &[u8]) -> ResultS<(Vec<Color8>, usize)>
{
read_data! {
endian: BIG, buf: b, size: 8, start: 0, data {
let dev = u16[4];
let num = u16[6] usize;
}
}
let dev = dev & 0x8000 != 0;
let num = num + 1;
let mut p = 8;
let mut clut = vec![Color8::new(0, 0, 0); num];
for i in 0..num {
read_data! {
endian: BIG, buf: b, size: 8, start: p, data {
let n = u16[0] usize;
let r = u8[2];
let g = u8[4];
let b = u8[6];
}
}
// with device mapping, we ignore the index entirely
let n = if dev {i} else {n};
*ok!(clut.get_mut(n), "invalid index")? = Color8::new(r, g, b);
p += 8;
}
Ok((clut, p))
}
// EOF

View File

@ -1,9 +0,0 @@
//! QuickDraw PICT `PixMap` data.
pub mod area;
pub mod head;
pub mod ind;
pub mod r5g5b5;
pub mod rgb8;
// EOF

View File

@ -1,21 +0,0 @@
//! QuickDraw PICT `PixMap` areas.
use crate::{err::*, image::{*, pict::pm}};
/// Process a `CopyBits` operation.
pub fn read(im: Image8, b: &[u8], pack: bool, clip: bool) -> ResultS<Image8>
{
let p = if pack {0} else {4};
let (b, hdr) = pm::head::read(&b[p..], pack, clip, &im)?;
match hdr.depth {
pm::head::Depth::_1 |
pm::head::Depth::_2 |
pm::head::Depth::_4 |
pm::head::Depth::_8 => pm::ind::read(im, b, hdr),
pm::head::Depth::_16 => pm::r5g5b5::read(im, b, hdr),
pm::head::Depth::_32 => pm::rgb8::read(im, b, hdr),
}
}
// EOF

View File

@ -1,86 +0,0 @@
//! QuickDraw PICT `PixMap` headers.
use crate::{bin::*, err::*, image::*};
/// Reads a `PixMap` header.
pub fn read<'a>(b: &'a [u8],
pack: bool,
clip: bool,
im: &Image8) -> ResultS<(&'a [u8], Header)>
{
read_data! {
endian: BIG, buf: b, size: 36, start: 0, data {
let pt_fl = u16[0];
let top = u16[2] usize;
let left = u16[4] usize;
let bottom = u16[6] usize;
let right = u16[8] usize;
let pack_t = u16[12] enum PackType;
let depth = u16[28] enum Depth;
}
}
if pt_fl & 0x8000 == 0 {
bail!("PICT1 not supported");
}
if right - left != im.w() || bottom - top != im.h() {
bail!("image bounds are incorrect");
}
let mut p = 46;
// get CLUT if packed
let clut = if pack {
let (clut, sz) = pict::clut::read(&b[p..])?;
p += sz;
Some(clut)
} else {
None
};
p += 18; // srcRect, dstRect, mode
if clip {
p += usize::from(u16b(&b[p..])); // maskRgn
}
let rle = pack_t == PackType::Default ||
pack_t == PackType::Rle16 && depth == Depth::_16 ||
pack_t == PackType::Rle32 && depth == Depth::_32;
let pitch = usize::from(pt_fl & 0x3FFF);
Ok((&b[p..], Header{pitch, pack_t, depth, clut, rle}))
}
pub struct Header {
pub pitch: usize,
pub pack_t: PackType,
pub depth: Depth,
pub clut: Option<Vec<Color8>>,
pub rle: bool,
}
c_enum! {
pub enum Depth: u16 {
_1 = 1,
_2 = 2,
_4 = 4,
_8 = 8,
_16 = 16,
_32 = 32,
}
}
c_enum! {
pub enum PackType: u16 {
Default = 0,
None = 1,
NoPad = 2,
Rle16 = 3,
Rle32 = 4,
}
}
// EOF

View File

@ -1,86 +0,0 @@
//! QuickDraw PICT indexed `PixMap`s.
use crate::{bin::check_data, err::*, image::{*, pict::{pm, rle}}};
/// Reads an indexed `PixMap`.
pub fn read(mut im: Image8,
b: &[u8],
hdr: pm::head::Header) -> ResultS<Image8>
{
let clut = ok!(hdr.clut, "no CLUT in indexed mode")?;
let mut p = 0;
if hdr.pitch < 8 && hdr.depth == pm::head::Depth::_8 {
// uncompressed 8-bit colormap indices
for _ in 0..im.h() {
for _ in 0..im.w() {
let idx = usize::from(b[p]);
im.cr.push(ok!(clut.get(idx), "bad index")?.clone());
p += 1;
}
}
Ok(im)
} else if hdr.rle {
// RLE compressed 1, 2, 4 or 8 bit colormap indices
for _ in 0..im.h() {
let (d, pp) = rle::read::<u8>(&b[p..], hdr.pitch)?;
let d = if hdr.depth < pm::head::Depth::_8 {
expand_data(d, hdr.depth)?
} else {
d
};
check_data(&d, im.w())?;
p += pp;
for &idx in &d {
im.cr.push(ok!(clut.get(usize::from(idx)), "bad index")?.clone());
}
}
Ok(im)
} else {
bail!("invalid configuration")
}
}
/// Expand packed pixel data based on bit depth.
pub fn expand_data(b: Vec<u8>, depth: pm::head::Depth) -> ResultS<Vec<u8>>
{
let mut o = Vec::with_capacity(match depth {
pm::head::Depth::_4 => b.len() * 2,
pm::head::Depth::_2 => b.len() * 4,
pm::head::Depth::_1 => b.len() * 8,
_ => bail!("invalid bit depth"),
});
for ch in b {
match depth {
pm::head::Depth::_4 => {
for i in (0..=1).rev() {
o.push(ch >> (i * 4) & 0xF_u8);
}
}
pm::head::Depth::_2 => {
for i in (0..=3).rev() {
o.push(ch >> (i * 2) & 0x3_u8);
}
}
pm::head::Depth::_1 => {
for i in (0..=7).rev() {
o.push(ch >> i & 0x1_u8);
}
}
_ => bail!("invalid bit depth"),
}
}
Ok(o)
}
// EOF

View File

@ -1,44 +0,0 @@
//! QuickDraw PICT R5G5B5 `PixMap`s.
use crate::{bin::{check_data, u16b}, err::*, image::{*, pict::{pm, rle}}};
/// Reads a R5G5B5 `PixMap`.
pub fn read(mut im: Image8,
b: &[u8],
hdr: pm::head::Header) -> ResultS<Image8>
{
let mut p = 0;
if hdr.pitch < 8 || hdr.pack_t == pm::head::PackType::None {
// uncompressed R5G5B5
for _ in 0..im.h() {
for _ in 0..im.w() {
let cr = u16b(&b[p..]);
im.cr.push(r5g5b5_to_rgb8(cr));
p += 2;
}
}
Ok(im)
} else if hdr.rle {
// RLE compressed R5G5B5
for _ in 0..im.h() {
let (d, pp) = rle::read::<u16>(&b[p..], hdr.pitch)?;
check_data(&d, im.w())?;
p += pp;
for &cr in &d {
im.cr.push(r5g5b5_to_rgb8(cr));
}
}
Ok(im)
} else {
bail!("invalid configuration")
}
}
// EOF

View File

@ -1,62 +0,0 @@
//! QuickDraw PICT RGB8 `PixMap`s.
use crate::{bin::check_data, err::*, image::{*, pict::{pm, rle}}};
/// Reads a RGB8 `PixMap`.
pub fn read(mut im: Image8,
b: &[u8],
hdr: pm::head::Header) -> ResultS<Image8>
{
let mut p = 0;
if hdr.pitch < 8 ||
hdr.pack_t == pm::head::PackType::None ||
hdr.pack_t == pm::head::PackType::NoPad
{
// uncompressed RGB8 or XRGB8
for _ in 0..im.h() {
for _ in 0..im.w() {
if hdr.pack_t != pm::head::PackType::NoPad {
p += 1;
}
read_data! {
endian: BIG, buf: b, size: 3, start: p, data {
let r = u8[0];
let g = u8[1];
let b = u8[2];
}
}
im.cr.push(Color8::new(r, g, b));
p += 3;
}
}
Ok(im)
} else if hdr.rle {
// RLE compressed RGB8
let pitch = hdr.pitch - im.w(); // remove padding byte from pitch
for _ in 0..im.h() {
let (d, pp) = rle::read::<u8>(&b[p..], pitch)?;
check_data(&d, im.w() * 3)?;
p += pp;
for x in 0..im.w() {
let r = d[x];
let g = d[x * 2];
let b = d[x * 3];
im.cr.push(Color8::new(r, g, b));
}
}
Ok(im)
} else {
bail!("invalid configuration")
}
}
// EOF

View File

@ -1,94 +0,0 @@
//! QuickDraw PICT format run length encoded data.
use crate::{bin::*, err::*};
/// Read run-length encoded data.
pub fn read<T>(b: &[u8], pitch: usize) -> ResultS<(Vec<T>, usize)>
where T: ReadRleData
{
let mut p = 0;
let mut o = Vec::with_capacity(pitch);
let sz = if pitch > 250 {
(usize::from(u16b(b)) + 2, p += 2).0
} else {
(usize::from(b[0]) + 1, p += 1).0
};
while p < sz {
let szf = b[p];
let cmp = szf & 0x80 != 0;
let len = usize::from(if cmp {!szf + 2} else {szf + 1});
p += 1;
o.reserve(len);
T::read_rle_data(b, &mut p, cmp, len, &mut o);
}
if o.len() == pitch {
Ok((o, p))
} else {
Err(err_msg("incorrect size for compressed scanline"))
}
}
pub trait ReadRleData: Sized
{
// Read a sequence of packed RLE data.
fn read_rle_data(b: &[u8],
p: &mut usize,
cmp: bool,
len: usize,
out: &mut Vec<Self>);
}
impl ReadRleData for u16
{
fn read_rle_data(b: &[u8],
p: &mut usize,
cmp: bool,
len: usize,
out: &mut Vec<Self>)
{
if cmp {
let d = u16b(&b[*p..*p + 2]);
*p += 2;
for _ in 0..len {
out.push(d);
}
} else {
for _ in 0..len {
let d = u16b(&b[*p..*p + 2]);
*p += 2;
out.push(d);
}
}
}
}
impl ReadRleData for u8
{
fn read_rle_data(b: &[u8],
p: &mut usize,
cmp: bool,
len: usize,
out: &mut Vec<Self>)
{
if cmp {
let d = b[*p];
*p += 1;
for _ in 0..len {
out.push(d);
}
} else {
for _ in 0..len {
let d = b[*p];
*p += 1;
out.push(d);
}
}
}
}
// EOF

View File

@ -1,69 +0,0 @@
//! Portable Pixel Map format images.
use crate::{err::*, fixed::FixedLong, image::*};
use std::io;
/// Writes a PPM file from an image.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_ppm(out: &mut impl io::Write, im: &impl Image) -> ResultS<()>
{
write!(out, "P3\n{} {}\n{}\n", im.w(), im.h(), u16::max_value())?;
for y in 0..im.h() {
for x in 0..im.w() {
let cr = im.index(x, y);
write!(out, "{} {} {} ", cr.r(), cr.g(), cr.b())?;
}
out.write_all(b"\n")?;
}
Ok(())
}
/// Reads a PPM file into an image.
pub fn read_ppm(inp: &[u8]) -> ResultS<Image16>
{
let mut it = inp.split(|&n| n == b'\n' || n == b'\r' || n == b' ');
if ok!(it.next(), "no magic number")? != b"P3" {
bail!("not P3 format");
}
let mut get_num = move || -> ResultS<u16> {
let st = loop {
match ok!(it.next(), "no more strings")? {
b"" => {}
st => break st
}
};
let st = unsafe {std::str::from_utf8_unchecked(st)};
let nu = u16::from_str_radix(st, 10)?;
Ok(nu)
};
let width = get_num()?;
let height = get_num()?;
let depth = i64::from(get_num()?);
let mut im = Image16::new(usize::from(width), usize::from(height));
for _ in 0..height * width {
let r = FixedLong::from_int(get_num()?.into());
let g = FixedLong::from_int(get_num()?.into());
let b = FixedLong::from_int(get_num()?.into());
let r = (r / depth * 0xFFFF).integ() as u16;
let g = (g / depth * 0xFFFF).integ() as u16;
let b = (b / depth * 0xFFFF).integ() as u16;
im.cr.push(Color16::new(r, g, b));
}
Ok(im)
}
// EOF

View File

@ -1,41 +0,0 @@
//! TARGA format images.
use crate::{err::*, image::*};
use std::io;
/// Writes a TGA file from an image.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_tga(out: &mut impl io::Write, im: &impl Image) -> ResultS<()>
{
// id len, color map type, image type
out.write_all(&[0, 0, 2])?;
// color map spec
out.write_all(&[0, 0, 0, 0, 0])?;
// x origin
out.write_all(&[0, 0])?;
// y origin
out.write_all(&[0, 0])?;
// width
out.write_all(&u16::to_le_bytes(im.w() as u16))?;
// height
out.write_all(&u16::to_le_bytes(im.h() as u16))?;
// depth, descriptor
out.write_all(&[32, 0])?;
for y in (0..im.h()).rev() {
for x in 0..im.w() {
let cr = im.index(x, y);
out.write_all(&[(cr.b() >> 8) as u8,
(cr.g() >> 8) as u8,
(cr.r() >> 8) as u8,
(cr.a() >> 8) as u8])?;
}
}
Ok(())
}
// EOF

View File

@ -1,116 +0,0 @@
//! Macintosh archived format header utilities.
use crate::file::SeekBackToStart;
use std::io::{SeekFrom, prelude::*};
/// Skips over an Apple Single header. Returns true if one was found.
pub fn skip_apple_single<R>(fp: &mut R) -> bool
where R: Read + Seek
{
let mut fp = SeekBackToStart::new(fp);
let mut magic = [0; 8];
let magic = if fp.read(&mut magic).is_ok() {magic} else {return false;};
// check magic numbers
if magic != [0, 5, 22, 0, 0, 2, 0, 0] {
return false;
}
let mut num = [0; 2];
let num = if fp.read(&mut num).is_ok() {num} else {return false;};
let num = u64::from(u16::from_be_bytes(num));
if fp.seek(SeekFrom::Start(26 + 12 * num)).is_err() |
fp.seek(SeekFrom::Start(26)).is_err() {
return false;
}
// get the resource fork (entity 1)
for _ in 0..num {
let mut ent = [0; 4];
let mut ofs = [0; 4];
let mut len = [0; 4];
let ent = if fp.read(&mut ent).is_ok() {ent} else {return false;};
let ofs = if fp.read(&mut ofs).is_ok() {ofs} else {return false;};
let len = if fp.read(&mut len).is_ok() {len} else {return false;};
let ent = u32::from_be_bytes(ent);
let ofs = u64::from(u32::from_be_bytes(ofs));
let len = u64::from(u32::from_be_bytes(len));
if ent == 1 {
if fp.seek(SeekFrom::Start(ofs + len)).is_ok() &
fp.seek(SeekFrom::Start(ofs)).is_ok() {
fp.set_seek(false);
return true;
} else {
return false;
}
}
}
// no resource fork
false
}
/// Skips over a Mac Binary II header. Returns true if one was found.
pub fn skip_macbin<R>(fp: &mut R) -> bool
where R: Read + Seek
{
let mut fp = SeekBackToStart::new(fp);
let mut head = [0; 128];
let head = if fp.read(&mut head).is_ok() {head} else {return false;};
// check legacy version, length, zero fill, and macbin2 version. I swear,
// this isn't *completely* magic
if head[0] != 0 || head[1] > 63 || head[74] != 0 || head[123] > 129 {
return false;
}
let mut crc = 0;
// check header crc
for &byte in head.iter().take(124) {
for j in 8..16 {
let d = u16::from(byte) << j;
if (d ^ crc) & 0x8000 == 0 {
crc <<= 1;
} else {
crc = crc << 1 ^ 0x1021;
}
}
}
// if ok, resource fork follows
if crc == u16::from_be_bytes([head[124], head[125]]) {
fp.set_seek(false);
true
} else {
false
}
}
/// Reads a `MacBin` or `AppleSingle` header if there is one and skips past it
/// from the start of the header to the resource fork (if one is found.) Returns
/// true if either one was found.
pub fn skip_mac_header<R>(fp: &mut R) -> bool
where R: Read + Seek
{
if skip_macbin(fp) {
return true;
}
let _ = fp.seek(SeekFrom::Start(0));
if skip_apple_single(fp) {
return true;
}
let _ = fp.seek(SeekFrom::Start(0));
false
}
// EOF

View File

@ -1,39 +0,0 @@
//! Marathon Map format handling.
pub mod ambi;
pub mod attk;
pub mod bonk;
pub mod damg;
pub mod data;
pub mod entr;
pub mod epnt;
pub mod fxpx;
pub mod head;
pub mod iidx;
pub mod lins;
pub mod lite;
pub mod ltfn;
pub mod medi;
pub mod minf;
pub mod mnpx;
pub mod name;
pub mod note;
pub mod objs;
pub mod plac;
pub mod plat;
pub mod pnts;
pub mod poly;
pub mod prpx;
pub mod pxpx;
pub mod sids;
pub mod stex;
pub mod term;
pub mod trig;
pub mod trmf;
pub mod trmg;
pub mod wppx;
/// The number of game ticks per second.
pub const TICKS_PER_SECOND: u16 = 30;
// EOF

View File

@ -1,26 +0,0 @@
//! `SoundAmbi` type.
use crate::err::*;
/// Reads an `ambi` chunk.
pub fn read(b: &[u8]) -> ResultS<(SoundAmbi, usize)>
{
read_data! {
endian: BIG, buf: b, size: 16, start: 0, data {
let index = u16[2];
let volume = u16[4];
}
}
Ok((SoundAmbi{index, volume}, 16))
}
/// An ambient sound definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SoundAmbi {
pub index: u16,
pub volume: u16,
}
// EOF

View File

@ -1,38 +0,0 @@
//! `Attack` type.
use crate::{bin::OptU16, err::*, fixed::{Angle, Unit}};
/// Reads an `Attack` object.
pub fn read(b: &[u8]) -> ResultS<Attack>
{
read_data! {
endian: BIG, buf: b, size: 16, start: 0, data {
let ptype = OptU16[0];
let rep = u16[2];
let error = Angle[4];
let range = Unit[6];
let shape = u16[8];
let ofs_x = Unit[10];
let ofs_y = Unit[12];
let ofs_z = Unit[14];
}
}
Ok(Attack{ptype, rep, error, range, shape, ofs_x, ofs_y, ofs_z})
}
/// The definition of a monster's attack.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Attack {
pub ptype: OptU16,
pub rep: u16,
pub error: Angle,
pub range: Unit,
pub shape: u16,
pub ofs_x: Unit,
pub ofs_y: Unit,
pub ofs_z: Unit,
}
// EOF

View File

@ -1,52 +0,0 @@
//! `SoundRand` type.
use crate::{err::*, fixed::{Angle, Fixed}};
/// Reads a `bonk` chunk.
pub fn read(b: &[u8]) -> ResultS<(SoundRand, usize)>
{
read_data! {
endian: BIG, buf: b, size: 32, start: 0, data {
let flags = u16[0] flag SoundRandFlags;
let index = u16[2];
let vol_nrm = u16[4];
let vol_dta = u16[6];
let prd_nrm = u16[8];
let prd_dta = u16[10];
let yaw_nrm = Angle[12];
let yaw_dta = Angle[14];
let pit_nrm = Fixed[16];
let pit_dta = Fixed[20];
}
}
Ok((SoundRand{flags, index, vol_nrm, vol_dta, prd_nrm, prd_dta, yaw_nrm,
yaw_dta, pit_nrm, pit_dta}, 32))
}
/// A randomly played sound definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SoundRand {
pub flags: SoundRandFlags,
pub index: u16,
pub vol_nrm: u16,
pub vol_dta: u16,
pub prd_nrm: u16,
pub prd_dta: u16,
pub yaw_nrm: Angle,
pub yaw_dta: Angle,
pub pit_nrm: Fixed,
pub pit_dta: Fixed,
}
c_bitfield! {
/// Flags for `SoundRand`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct SoundRandFlags: u16 {
NO_DIRECTION = 0,
_2 = 1,
}
}
// EOF

View File

@ -1,103 +0,0 @@
//! `Damage` type.
use crate::{err::*, fixed::Fixed};
/// Reads a `Damage` object.
pub fn read(b: &[u8]) -> ResultS<Damage>
{
read_data! {
endian: BIG, buf: b, size: 12, start: 0, data {
let dtype = u16[0] enum DamageType;
let flags = u16[2] flag DamageFlags;
let dmg_base = u16[4];
let dmg_rand = u16[6];
let scale = Fixed[8];
}
}
Ok(Damage{dtype, flags, dmg_base, dmg_rand, scale})
}
/// A damage definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Damage {
pub dtype: DamageType,
pub flags: DamageFlags,
pub dmg_base: u16,
pub dmg_rand: u16,
pub scale: Fixed,
}
c_bitfield! {
/// The composite type of damage taken by something.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct DamageTypeFlags: u32 {
EXPLOSION = 0,
ELECTRICAL_STAFF = 1,
PROJECTILE = 2,
ABSORBED = 3,
FLAME = 4,
HOUND_CLAWS = 5,
ALIEN_PROJECTILE = 6,
HULK_SLAP = 7,
COMPILER_BOLT = 8,
FUSION_BOLT = 9,
HUNTER_BOLT = 10,
FIST = 11,
TELEPORTER = 12,
DEFENDER = 13,
YETI_CLAWS = 14,
YETI_PROJECTILE = 15,
CRUSHING = 16,
LAVA = 17,
SUFFOCATION = 18,
GOO = 19,
ENERGY_DRAIN = 20,
OXYGEN_DRAIN = 21,
HUMMER_BOLT = 22,
SHOTGUN_PROJECTILE = 23,
}
}
c_bitfield! {
/// Flags for `Damage`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct DamageFlags: u16 {
ALIEN = 0,
}
}
c_enum! {
/// A named type of damage taken by something.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum DamageType: u16 {
Explosion = 0,
ElectricalStaff = 1,
Projectile = 2,
Absorbed = 3,
Flame = 4,
HoundClaws = 5,
AlienProjectile = 6,
HulkSlap = 7,
CompilerBolt = 8,
FusionBolt = 9,
HunterBolt = 10,
Fist = 11,
Teleporter = 12,
Defender = 13,
YetiClaws = 14,
YetiProjectile = 15,
Crushing = 16,
Lava = 17,
Suffocation = 18,
Goo = 19,
EnergyDrain = 20,
OxygenDrain = 21,
HummerBolt = 22,
ShotgunProjectile = 23,
None = 0xFFFF,
}
}
// EOF

View File

@ -1,197 +0,0 @@
//! Map file entry data.
use crate::{bin::*, err::*, image::{self, pict}, map};
use super::{entr, head};
use std::collections::BTreeMap;
/// Reads all chunks in an entry.
pub fn read(head: &head::Header, b: &[u8]) -> ResultS<EntryData>
{
let mut data = EntryData::default();
let old = head.old_data();
let rd_cminf = if old {map::minf::read_old} else {map::minf::read};
let rd_csids = if old {map::sids::read_old} else {map::sids::read};
let rd_cpoly = if old {map::poly::read_old} else {map::poly::read};
let rd_clite = if old {map::lite::read_old} else {map::lite::read};
let mut p = 0;
while p < b.len() {
read_data! {
endian: BIG, buf: b, size: head.size_chunk(), start: p, data {
let iden = Ident[0];
let size = u32[8] usize;
}
}
let beg = p + head.size_chunk();
let end = beg + size;
let buf = ok!(b.get(beg..end), "not enough data")?;
match &iden.0 {
b"EPNT" => data.epnt = Some(rd_array(buf, map::epnt::read)?),
b"FXpx" => data.fxpx = Some(rd_array(buf, map::fxpx::read)?),
b"LINS" => data.lins = Some(rd_array(buf, map::lins::read)?),
b"LITE" => data.lite = Some(rd_array(buf, rd_clite)?),
b"MNpx" => data.mnpx = Some(rd_array(buf, map::mnpx::read)?),
b"Minf" => data.minf = Some(rd_cminf(buf)?),
b"NAME" => data.name = Some(rd_array(buf, map::name::read)?),
b"NOTE" => data.note = Some(rd_array(buf, map::note::read)?),
b"OBJS" => data.objs = Some(rd_array(buf, map::objs::read)?),
b"PICT" => data.pict = Some(pict::read(buf)?),
b"PNTS" => data.pnts = Some(rd_array(buf, map::pnts::read)?),
b"POLY" => data.poly = Some(rd_array(buf, rd_cpoly)?),
b"PRpx" => data.prpx = Some(rd_array(buf, map::prpx::read)?),
b"PXpx" => data.pxpx = Some(rd_array(buf, map::pxpx::read)?),
b"SIDS" => data.sids = Some(rd_array(buf, rd_csids)?),
b"WPpx" => data.wppx = Some(rd_array(buf, map::wppx::read)?),
b"ambi" => data.ambi = Some(rd_array(buf, map::ambi::read)?),
b"bonk" => data.bonk = Some(rd_array(buf, map::bonk::read)?),
b"iidx" => data.iidx = Some(rd_array(buf, map::iidx::read)?),
b"medi" => data.medi = Some(rd_array(buf, map::medi::read)?),
b"plac" => data.plac = Some(rd_array(buf, map::plac::read)?),
b"plat" => data.plat = Some(rd_array(buf, map::plat::read)?),
b"term" => data.term = Some(rd_array(buf, map::term::read)?),
_ => data.unkn.push((iden, buf.to_vec())),
}
p = end;
}
Ok(data)
}
/// Reads all of the data from an entry map.
pub fn read_all(head: &head::Header,
map: &entr::EntryMap<'_>) -> ResultS<EntryDataMap>
{
let mut data_map = EntryDataMap::new();
for (&index, entry) in map {
data_map.insert(index, read(head, entry.data)?);
}
Ok(data_map)
}
impl EntryData
{
pub fn get_type(&self) -> EntryType
{
if self.has_minf() {
EntryType::Map
} else if self.has_fxpx() ||
self.has_mnpx() ||
self.has_prpx() ||
self.has_pxpx() ||
self.has_wppx() {
EntryType::Physics
} else if self.has_pict() {
EntryType::Image
} else {
EntryType::Other
}
}
pub fn has_epnt(&self) -> bool {self.epnt.is_some()}
pub fn has_fxpx(&self) -> bool {self.fxpx.is_some()}
pub fn has_lins(&self) -> bool {self.lins.is_some()}
pub fn has_lite(&self) -> bool {self.lite.is_some()}
pub fn has_mnpx(&self) -> bool {self.mnpx.is_some()}
pub fn has_minf(&self) -> bool {self.minf.is_some()}
pub fn has_name(&self) -> bool {self.name.is_some()}
pub fn has_note(&self) -> bool {self.note.is_some()}
pub fn has_objs(&self) -> bool {self.objs.is_some()}
pub fn has_pict(&self) -> bool {self.pict.is_some()}
pub fn has_pnts(&self) -> bool {self.pnts.is_some()}
pub fn has_poly(&self) -> bool {self.poly.is_some()}
pub fn has_prpx(&self) -> bool {self.prpx.is_some()}
pub fn has_pxpx(&self) -> bool {self.pxpx.is_some()}
pub fn has_sids(&self) -> bool {self.sids.is_some()}
pub fn has_wppx(&self) -> bool {self.wppx.is_some()}
pub fn has_ambi(&self) -> bool {self.ambi.is_some()}
pub fn has_bonk(&self) -> bool {self.bonk.is_some()}
pub fn has_iidx(&self) -> bool {self.iidx.is_some()}
pub fn has_medi(&self) -> bool {self.medi.is_some()}
pub fn has_plac(&self) -> bool {self.plac.is_some()}
pub fn has_plat(&self) -> bool {self.plat.is_some()}
pub fn has_term(&self) -> bool {self.term.is_some()}
pub fn num_unknown(&self) -> usize {self.unkn.len()}
pub fn len(&self) -> usize
{
self.num_unknown() +
usize::from(self.has_epnt()) +
usize::from(self.has_fxpx()) +
usize::from(self.has_lins()) +
usize::from(self.has_lite()) +
usize::from(self.has_mnpx()) +
usize::from(self.has_minf()) +
usize::from(self.has_name()) +
usize::from(self.has_note()) +
usize::from(self.has_objs()) +
usize::from(self.has_pict()) +
usize::from(self.has_pnts()) +
usize::from(self.has_poly()) +
usize::from(self.has_prpx()) +
usize::from(self.has_pxpx()) +
usize::from(self.has_sids()) +
usize::from(self.has_wppx()) +
usize::from(self.has_ambi()) +
usize::from(self.has_bonk()) +
usize::from(self.has_iidx()) +
usize::from(self.has_medi()) +
usize::from(self.has_plac()) +
usize::from(self.has_plat())
}
pub fn is_empty(&self) -> bool {self.len() == 0}
}
/// The abstract type of an entry.
#[derive(Debug, Eq, PartialEq)]
pub enum EntryType {
Other,
Map,
Image,
Physics,
}
/// The loaded data of a Map file entry.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Default, Eq, PartialEq)]
pub struct EntryData {
/** `EPNT` chunk data. */ pub epnt: Option<Vec<map::epnt::Endpoint>>,
/** `FXpx` chunk data. */ pub fxpx: Option<Vec<map::fxpx::Effect>>,
/** `LINS` chunk data. */ pub lins: Option<Vec<map::lins::Line>>,
/** `LITE` chunk data. */ pub lite: Option<Vec<map::lite::Light>>,
/** `MNpx` chunk data. */ pub mnpx: Option<Vec<map::mnpx::Monster>>,
/** `Minf` chunk data. */ pub minf: Option<map::minf::Info>,
/** `NAME` chunk data. */ pub name: Option<Vec<String>>,
/** `NOTE` chunk data. */ pub note: Option<Vec<map::note::Note>>,
/** `OBJS` chunk data. */ pub objs: Option<Vec<map::objs::Object>>,
/** `PICT` chunk data. */ pub pict: Option<image::Image8>,
/** `PNTS` chunk data. */ pub pnts: Option<Vec<map::pnts::Point>>,
/** `POLY` chunk data. */ pub poly: Option<Vec<map::poly::Polygon>>,
/** `PRpx` chunk data. */ pub prpx: Option<Vec<map::prpx::Projectile>>,
/** `PXpx` chunk data. */ pub pxpx: Option<Vec<map::pxpx::Physics>>,
/** `SIDS` chunk data. */ pub sids: Option<Vec<map::sids::Side>>,
/** `WPpx` chunk data. */ pub wppx: Option<Vec<map::wppx::Weapon>>,
/** `ambi` chunk data. */ pub ambi: Option<Vec<map::ambi::SoundAmbi>>,
/** `bonk` chunk data. */ pub bonk: Option<Vec<map::bonk::SoundRand>>,
/** `iidx` chunk data. */ pub iidx: Option<Vec<u16>>,
/** `medi` chunk data. */ pub medi: Option<Vec<map::medi::Media>>,
/** `plac` chunk data. */ pub plac: Option<Vec<map::plac::ObjectFreq>>,
/** `plat` chunk data. */ pub plat: Option<Vec<map::plat::Platform>>,
/** `term` chunk data. */ pub term: Option<Vec<map::term::Terminal>>,
pub unkn: Vec<(Ident, Vec<u8>)>,
}
/// A map of indexed entries.
pub type EntryDataMap = BTreeMap<u16, EntryData>;
// EOF

View File

@ -1,70 +0,0 @@
//! Map file entry type.
use crate::err::*;
use super::head;
use std::collections::BTreeMap;
/// Read an entry from a Map file.
pub fn read(map: &head::Map, i: usize) -> ResultS<(u16, Entry<'_>)>
{
let size = map.head().size_entry();
let b = map.dir();
let sta = size * i;
read_data! {
endian: BIG, buf: b, size: size, start: sta, data {
let offset = u32[0] usize;
let dsize = u32[4] usize;
}
}
let (index, app_data) = if !map.head().old_wad() {
read_data! {
endian: BIG, buf: b, size: size, start: sta, data {
let index = u16[8];
let app_data = u8[10; map.head().size_appl()];
}
}
(index, app_data)
} else {
(i as u16, &b[0..0])
};
let data = &map.data()[offset..offset + dsize];
Ok((index, Entry{data, app_data}))
}
/// Reads all entries in a Map file.
pub fn read_all(map: &head::Map) -> ResultS<EntryMap<'_>>
{
let mut entries = EntryMap::new();
for i in 0..map.num_ent() {
let (index, entry) = read(map, i)?;
if entries.contains_key(&index) {
bail!("entry index already exists");
}
entries.insert(index, entry);
}
Ok(entries)
}
/// An entry containing chunked data and application-specific data.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Entry<'a> {
/// The data in this `Entry`.
pub data: &'a [u8],
/// The application specific data for this Entry.
pub app_data: &'a [u8],
}
/// A map of indexed entries.
pub type EntryMap<'a> = BTreeMap<u16, Entry<'a>>;
// EOF

View File

@ -1,49 +0,0 @@
//! `Endpoint` type.
use super::pnts;
use crate::{err::*, fixed::Unit};
/// Reads an `EPNT` chunk.
pub fn read(b: &[u8]) -> ResultS<(Endpoint, usize)>
{
read_data! {
endian: BIG, buf: b, size: 16, start: 0, data {
let flags = u16[0] flag EndpointFlags;
let hei_hi = Unit[2];
let hei_lo = Unit[4];
let pos = pnts::read_o[6; 4];
let support = u16[10];
}
}
Ok((Endpoint{flags, hei_hi, hei_lo, pos, support}, 16))
}
/// Converts a vector of `Endpoint`s to a vector of `Point`s.
pub fn to_pnts(v: &[Endpoint]) -> Vec<pnts::Point>
{
v.iter().map(|p| p.into()).collect()
}
/// A pre-processed point in world-space.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Endpoint {
pub flags: EndpointFlags,
pub hei_hi: Unit,
pub hei_lo: Unit,
pub pos: pnts::Point,
pub support: u16,
}
c_bitfield! {
/// Flags for `Endpoint`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct EndpointFlags: u16 {
SOLID = 0,
SAME_HEIGHT = 1,
TRANSPARENT = 2,
}
}
// EOF

View File

@ -1,46 +0,0 @@
//! `Effect` type.
use crate::{bin::OptU16, err::*, fixed::Fixed};
/// Reads a `FXpx` chunk.
pub fn read(b: &[u8]) -> ResultS<(Effect, usize)>
{
read_data! {
endian: BIG, buf: b, size: 14, start: 0, data {
let collection = u16[0];
let shape = u16[2];
let pitch = Fixed[4];
let flags = u16[8] flag EffectFlags;
let delay = OptU16[10];
let delay_snd = OptU16[12];
}
}
Ok((Effect{collection, shape, pitch, flags, delay, delay_snd}, 14))
}
/// An effect definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Effect {
pub collection: u16,
pub shape: u16,
pub pitch: Fixed,
pub flags: EffectFlags,
pub delay: OptU16,
pub delay_snd: OptU16,
}
c_bitfield! {
/// Flags for an effect.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct EffectFlags: u16 {
END_ON_LOOP = 0,
END_ON_XFER_LOOP = 1,
SOUND_ONLY = 2,
MAKE_TWIN_VISIBLE = 3,
MEDIA_EFFECT = 4,
}
}
// EOF

View File

@ -1,150 +0,0 @@
//! Map file header.
use crate::{err::*, text::mac_roman_cstr};
use std::io::prelude::*;
/// Reads a Map file.
pub fn read(rd: &mut impl Read) -> ResultS<Map>
{
let mut data = Vec::new();
rd.read_to_end(&mut data)?;
read_data! {
endian: BIG, buf: &data, size: 128, start: 0, data {
let ver_wad = u16[0] enum Ver;
let ver_data = u16[2];
let name = mac_roman_cstr[4; 64] no_try;
let dir_ofs = u32[72] usize;
let num_ent = u16[76] usize;
let size_appl = u16[78] usize;
let size_wcnk = u16[80] usize;
let size_went = u16[82] usize;
}
}
let head = Header::new(size_appl, ver_data, ver_wad, Some(name));
if !head.old_wad() && head.size_entry_base() != size_went {
bail!("invalid entry size");
}
if !head.old_wad() && head.size_chunk() != size_wcnk {
bail!("invalid chunk size");
}
let map = Map{head, data, dir_ofs, num_ent};
if map.dir_end() > map.data.len() {
bail!("not enough data in map file");
}
Ok(map)
}
impl Header
{
/// Creates a new `Header`.
pub fn new(size_appl: usize,
ver_data: u16,
ver_wad: Ver,
name: Option<String>) -> Self
{
let name = name.unwrap_or_default();
Self{name, size_appl, ver_data, ver_wad}
}
/// Returns `true` if the data is in Marathon 1 format.
#[inline]
pub const fn old_data(&self) -> bool {self.ver_data() == 0}
/// Returns `true` if the Map file is in Marathon 1 format.
#[inline]
pub fn old_wad(&self) -> bool {self.ver_wad() == Ver::Base}
/// The data version of this file.
#[inline]
pub const fn ver_data(&self) -> u16 {self.ver_data}
/// The format version of this file.
#[inline]
pub const fn ver_wad(&self) -> Ver {self.ver_wad}
/// The size of each `Entry`'s `appdata` field.
#[inline]
pub const fn size_appl(&self) -> usize {self.size_appl}
/// The size of each `Entry`'s data.
#[inline]
pub fn size_entry_base(&self) -> usize {if self.old_wad() {8} else {10}}
/// The size of each `Entry`.
#[inline]
pub fn size_entry(&self) -> usize {self.size_entry_base() + self.size_appl()}
/// The size of each chunk's header.
#[inline]
pub fn size_chunk(&self) -> usize {if self.old_wad() {12} else {16}}
}
impl Map
{
/// The header for this map.
#[inline]
pub const fn head(&self) -> &Header {&self.head}
/// The data section of this map.
#[inline]
pub fn data(&self) -> &[u8] {&self.data[..]}
/// The directory section of this map.
#[inline]
pub fn dir(&self) -> &[u8] {&self.data[self.dir_beg()..self.dir_end()]}
/// The number of entries in the directory.
#[inline]
pub const fn num_ent(&self) -> usize {self.num_ent}
#[inline]
const fn dir_beg(&self) -> usize {self.dir_ofs}
#[inline]
fn dir_end(&self) -> usize
{
self.dir_ofs + self.head.size_entry() * self.num_ent
}
}
/// A Map header.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Header {
/// The original name of this file.
pub name: String,
size_appl: usize,
ver_data: u16,
ver_wad: Ver,
}
/// A Map file.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Map {
head: Header,
data: Vec<u8>,
dir_ofs: usize,
num_ent: usize,
}
c_enum! {
/// The version of a Map file.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum Ver: u16 {
Base = 0,
Dir = 1,
Over = 2,
Inf = 4,
}
}
// EOF

View File

@ -1,13 +0,0 @@
//! `iidx` chunk.
use crate::{bin::{check_data, u16b}, err::*};
/// Reads an `iidx` chunk.
pub fn read(b: &[u8]) -> ResultS<(u16, usize)>
{
check_data(b, 2)?;
Ok((u16b(b), 2))
}
// EOF

View File

@ -1,49 +0,0 @@
//! `Line` type.
use crate::{bin::OptU16, err::*};
/// Reads a `LINS` chunk.
pub fn read(b: &[u8]) -> ResultS<(Line, usize)>
{
read_data! {
endian: BIG, buf: b, size: 32, start: 0, data {
let pnt_beg = u16[0];
let pnt_end = u16[2];
let flags = u16[4] flag LineFlags;
let side_fr = OptU16[12];
let side_bk = OptU16[14];
let poly_fr = OptU16[16];
let poly_bk = OptU16[18];
}
}
Ok((Line{flags, pnt_beg, pnt_end, side_fr, side_bk, poly_fr, poly_bk}, 32))
}
/// A line segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Line {
pub flags: LineFlags,
pub pnt_beg: u16,
pub pnt_end: u16,
pub side_fr: OptU16,
pub side_bk: OptU16,
pub poly_fr: OptU16,
pub poly_bk: OptU16,
}
c_bitfield! {
/// Flags for `Line`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct LineFlags: u16 {
TRANS_SIDE = 9,
ELEV_VAR = 10,
ELEVATION = 11,
LANDSCAPE = 12,
TRANSPARENT = 13,
SOLID = 14,
}
}
// EOF

View File

@ -1,398 +0,0 @@
//! `Light` type.
use super::{ltfn, TICKS_PER_SECOND};
use crate::{err::*, fixed::Fixed};
/// Reads a `LITE` chunk.
pub fn read(b: &[u8]) -> ResultS<(Light, usize)>
{
read_data! {
endian: BIG, buf: b, size: 100, start: 0, data {
let ltype = u16[0] enum LightType;
let flags = u16[2] flag LightFlags;
let phase = i16[4];
let act_pri = ltfn::read[6; 14];
let act_sec = ltfn::read[20; 14];
let act_mid = ltfn::read[34; 14];
let ina_pri = ltfn::read[48; 14];
let ina_sec = ltfn::read[62; 14];
let ina_mid = ltfn::read[76; 14];
let tag = u16[90];
}
}
Ok((Light{ltype, flags, phase, act_pri, act_sec, act_mid, ina_pri, ina_sec,
ina_mid, tag}, 100))
}
/// Reads an old `LITE` chunk.
pub fn read_old(b: &[u8]) -> ResultS<(Light, usize)>
{
read_data! {
endian: BIG, buf: b, size: 32, start: 0, data {
let ltype = u16[2] usize;
let mode = u16[4];
let phase = i16[6];
let min = Fixed[8];
let max = Fixed[12];
let prd = u16[16];
}
}
if OLD_LIGHT_DEFINITIONS.len() < ltype {
bail!("bad old light type");
}
let lite = &OLD_LIGHT_DEFINITIONS[ltype];
let on = mode == 0 || mode == 1;
let strobe = ltype == 3;
let flags = if on {lite.flags | LightFlags::INIT_ACTIVE} else {lite.flags};
// modify each old light function accordingly
let old_lfun = move |func: &ltfn::LightFunc| -> ltfn::LightFunc {
ltfn::LightFunc{ftype: func.ftype,
prd_nrm: if strobe {prd / 4 + 1} else {func.prd_nrm},
prd_dta: func.prd_dta,
val_nrm: if func.val_nrm > 0.into() {max} else {min},
val_dta: func.val_dta}
};
Ok((Light{flags,
phase,
act_pri: old_lfun(&lite.act_pri),
act_sec: old_lfun(&lite.act_sec),
act_mid: old_lfun(&lite.act_mid),
ina_pri: old_lfun(&lite.ina_pri),
ina_sec: old_lfun(&lite.ina_sec),
ina_mid: old_lfun(&lite.ina_mid),
tag: 0,
..*lite}, 32))
}
/// A dynamic polygon light.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Light {
pub ltype: LightType,
pub flags: LightFlags,
pub phase: i16,
pub act_pri: ltfn::LightFunc,
pub act_sec: ltfn::LightFunc,
pub act_mid: ltfn::LightFunc,
pub ina_pri: ltfn::LightFunc,
pub ina_sec: ltfn::LightFunc,
pub ina_mid: ltfn::LightFunc,
pub tag: u16,
}
c_bitfield! {
/// Flags for `Light`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct LightFlags: u16 {
INIT_ACTIVE = 0,
SLAVE_VALUE = 1,
STATELESS = 2,
}
}
c_enum! {
/// The type of a `Light`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum LightType: u16 {
Normal = 0,
Strobe = 1,
Media = 2,
}
}
const OLD_LIGHT_DEFINITIONS: [Light; 8] = [
// Normal
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
tag: 0},
// Rheostat
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 3,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 3,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Flourescent
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Fluorescent,
prd_nrm: TICKS_PER_SECOND * 3,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Strobe
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Flicker
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Flicker,
prd_nrm: TICKS_PER_SECOND * 3,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Pulsate
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2 - 1,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2 - 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2 - 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Smooth,
prd_nrm: TICKS_PER_SECOND * 2,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Annoying
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Random,
prd_nrm: 2,
prd_dta: 1,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: 2,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Random,
prd_nrm: 1,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
// Energy Efficient
Light{ltype: LightType::Normal,
flags: LightFlags::SLAVE_VALUE,
phase: 0,
act_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
act_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
act_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Linear,
prd_nrm: TICKS_PER_SECOND * 2,
prd_dta: 0,
val_nrm: Fixed::from_int(1),
val_dta: Fixed::from_int(0)},
ina_pri: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_sec: ltfn::LightFunc{ftype: ltfn::LightFuncType::Constant,
prd_nrm: TICKS_PER_SECOND,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
ina_mid: ltfn::LightFunc{ftype: ltfn::LightFuncType::Linear,
prd_nrm: TICKS_PER_SECOND * 2,
prd_dta: 0,
val_nrm: Fixed::from_int(0),
val_dta: Fixed::from_int(0)},
tag: 0},
];
// EOF

View File

@ -1,57 +0,0 @@
//! `LightFunc` type.
use crate::{err::*, fixed::Fixed};
/// Reads a `LightFunc` object.
pub fn read(b: &[u8]) -> ResultS<LightFunc>
{
read_data! {
endian: BIG, buf: b, size: 14, start: 0, data {
let ftype = u16[0] enum LightFuncType;
let prd_nrm = u16[2];
let prd_dta = u16[4];
let val_nrm = Fixed[6];
let val_dta = Fixed[10];
}
}
Ok(LightFunc{ftype, prd_nrm, prd_dta, val_nrm, val_dta})
}
/// Writes a `LightFunc` object.
pub fn write(v: &LightFunc) -> Vec<u8>
{
let mut o = Vec::with_capacity(14);
o.extend(&(v.ftype as u16).to_be_bytes());
o.extend(&v.prd_nrm.to_be_bytes());
o.extend(&v.prd_dta.to_be_bytes());
o.extend(&v.val_nrm.to_be_bytes());
o.extend(&v.val_dta.to_be_bytes());
o
}
/// A light function.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LightFunc {
pub ftype: LightFuncType,
pub prd_nrm: u16,
pub prd_dta: u16,
pub val_nrm: Fixed,
pub val_dta: Fixed,
}
c_enum! {
/// The type of function for a `LightFunc`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum LightFuncType: u16 {
Constant = 0,
Linear = 1,
Smooth = 2,
Flicker = 3,
Random = 4,
Fluorescent = 5,
}
}
// EOF

View File

@ -1,71 +0,0 @@
//! `Light` type.
use super::pnts;
use crate::{bin::OptU16,
err::*,
fixed::{Angle, Fixed, Unit},
xfer::TransferMode};
/// Reads a `medi` chunk.
pub fn read(b: &[u8]) -> ResultS<(Media, usize)>
{
read_data! {
endian: BIG, buf: b, size: 32, start: 0, data {
let mtype = u16[0] enum MediaType;
let flags = u8[3] flag MediaFlags;
let control = u16[4];
let dir = Angle[6];
let mag = Unit[8];
let hei_lo = Unit[10];
let hei_hi = Unit[12];
let orig = pnts::read_o[14; 4];
let hei_nrm = Unit[18];
let min_lt = Fixed[20];
let texture = OptU16[24];
let xfer = u16[26] enum TransferMode;
}
}
Ok((Media{mtype, flags, control, dir, mag, hei_lo, hei_hi, orig, hei_nrm,
min_lt, texture, xfer}, 32))
}
/// A media, as in a part of a polygon which goes up the middle of the wall.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Media {
pub mtype: MediaType,
pub flags: MediaFlags,
pub control: u16,
pub dir: Angle,
pub mag: Unit,
pub hei_lo: Unit,
pub hei_hi: Unit,
pub orig: pnts::Point,
pub hei_nrm: Unit,
pub min_lt: Fixed,
pub texture: OptU16,
pub xfer: TransferMode,
}
c_enum! {
/// The liquid type of a `Media`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum MediaType: u16 {
Water = 0,
Lava = 1,
Goo = 2,
Sewage = 3,
Jjaro = 4,
}
}
c_bitfield! {
/// Flags for `Media`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct MediaFlags: u8 {
OBSTRUCT = 0,
}
}
// EOF

View File

@ -1,131 +0,0 @@
//! `Info` type.
use crate::{err::*, text::*};
/// Reads a `Minf` chunk.
pub fn read(b: &[u8]) -> ResultS<Info>
{
read_data! {
endian: BIG, buf: b, size: 88, start: 0, data {
let texture_id = u16[0];
let physics_id = u16[2];
let skypict_id = u16[4];
let miss_flags = u16[6] flag MissionFlags;
let envi_flags = u16[8] flag EnvironmentFlags;
let level_name = mac_roman_cstr[18; 66] no_try;
let entr_flags = u32[84] flag EntryFlags;
}
}
Ok(Info{texture_id, physics_id, skypict_id, miss_flags, envi_flags,
entr_flags, level_name})
}
/// Writes a `Minf` chunk.
pub fn write(v: &Info) -> Vec<u8>
{
let mut o = Vec::with_capacity(4);
o.extend(&v.texture_id.to_be_bytes());
o.extend(&v.physics_id.to_be_bytes());
o.extend(&v.skypict_id.to_be_bytes());
o.extend(&v.miss_flags.bits().to_be_bytes());
o.extend(&v.envi_flags.bits().to_be_bytes());
o.extend(&pad_zero(to_mac_roman(&v.level_name), 66));
o.extend(&v.entr_flags.bits().to_be_bytes());
o
}
/// Reads an old `Minf` chunk.
pub fn read_old(b: &[u8]) -> ResultS<Info>
{
let minf = read(b)?;
let mut entr_flags = if minf.entr_flags.is_empty() {
EntryFlags::SOLO
} else {
minf.entr_flags
};
if entr_flags.intersects(EntryFlags::SOLO | EntryFlags::CARNAGE) {
entr_flags.insert(EntryFlags::CO_OP)
}
Ok(Info{entr_flags, ..minf})
}
impl Default for Info
{
fn default() -> Self
{
Self{texture_id: 0,
physics_id: 1,
skypict_id: 0,
miss_flags: MissionFlags::empty(),
envi_flags: EnvironmentFlags::empty(),
entr_flags: EntryFlags::SOLO,
level_name: "Map".to_string()}
}
}
/// Static map information.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Info {
pub texture_id: u16,
pub physics_id: u16,
pub skypict_id: u16,
pub miss_flags: MissionFlags,
pub envi_flags: EnvironmentFlags,
pub entr_flags: EntryFlags,
pub level_name: String,
}
c_bitfield! {
/// Static environment flags.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct EnvironmentFlags: u16 {
VACUUM = 0,
MAGNETIC = 1,
REBELLION = 2,
LOW_GRAV = 3,
M1_GLUE = 4,
LAVA_FLOOR = 5,
REBELLION2 = 6,
MUSIC = 7,
TERM_PAUSE = 8,
M1_MONSTER = 9,
M1_WEPS = 10,
}
}
c_bitfield! {
/// Static entry point flags.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct EntryFlags: u32 {
SOLO = 0,
CO_OP = 1,
CARNAGE = 2,
KTMWTB = 3,
KOTH = 4,
DEFENSE = 5,
RUGBY = 6,
CTF = 7,
}
}
c_bitfield! {
/// Static mission flags.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct MissionFlags: u16 {
EXTERMINATION = 0,
EXPLORATION = 1,
RETRIEVAL = 2,
REPAIR = 3,
RESCUE = 4,
M1_EXPLORATION = 5,
M1_RESCUE = 6,
M1_REPAIR = 7,
}
}
// EOF

View File

@ -1,200 +0,0 @@
//! `Monster` type.
use crate::{bin::OptU16, err::*, fixed::{Fixed, Unit}};
use super::{attk, damg};
/// Reads a `MNpx` chunk.
pub fn read(b: &[u8]) -> ResultS<(Monster, usize)>
{
read_data! {
endian: BIG, buf: b, size: 156, start: 0, data {
let collection = u16[0];
let vitality = u16[2];
let dty_immune = u32[4] flag damg::DamageTypeFlags;
let dty_weak = u32[8] flag damg::DamageTypeFlags;
let flags = u32[12] flag MonsterFlags;
let cls_self = u32[16] flag MonsterClass;
let cls_friend = u32[20];
let cls_enemy = u32[24];
let snd_pitch = Fixed[28];
let snd_see_enemy = OptU16[32];
let snd_see_friend = OptU16[34];
let snd_seeclear = OptU16[36];
let snd_kill = OptU16[38];
let snd_apologize = OptU16[40];
let snd_amicide = OptU16[42];
let snd_flaming = OptU16[44];
let snd_active = OptU16[46];
let snd_active_mask = u16[48];
let typ_item = OptU16[50];
let radius = Unit[52];
let height = Unit[54];
let height_hover = Unit[56];
let ledge_min = Unit[58];
let ledge_max = Unit[60];
let ext_vel_scale = Fixed[62];
let fxt_impact = OptU16[66];
let fxt_impact_melee = OptU16[68];
let fxt_trail = OptU16[70];
let half_fov_horz = u16[72];
let half_fov_vert = u16[74];
let view_range = Unit[76];
let view_range_dark = Unit[78];
let intelligence = u16[80];
let speed = u16[82];
let gravity = u16[84];
let vel_terminal = u16[86];
let door_try_mask = u16[88];
let expl_radius = OptU16[90];
let expl_damage = damg::read[92; 12];
let seq_hit = OptU16[104];
let seq_dying_hard = OptU16[106];
let seq_dying_soft = OptU16[108];
let seq_dead_hard = OptU16[110];
let seq_dead_soft = OptU16[112];
let seq_standing = u16[114];
let seq_moving = u16[116];
let seq_tele_in = OptU16[118];
let seq_tele_out = OptU16[120];
let atk_frequency = u16[122];
let atk_melee = attk::read[124; 16];
let atk_range = attk::read[140; 16];
}
}
// friend and enemy fields MUST truncate because the original source code
// used `-1` to represent "all classes" which should be invalid normally
let cls_friend = MonsterClass::from_bits_truncate(cls_friend);
let cls_enemy = MonsterClass::from_bits_truncate(cls_enemy);
Ok((Monster{collection, vitality, dty_immune, dty_weak, flags, cls_self,
cls_friend, cls_enemy, snd_pitch, snd_see_enemy, snd_see_friend,
snd_seeclear, snd_kill, snd_apologize, snd_amicide, snd_flaming,
snd_active, snd_active_mask, typ_item, radius, height,
height_hover, ledge_min, ledge_max, ext_vel_scale, fxt_impact,
fxt_impact_melee, fxt_trail, half_fov_horz, half_fov_vert,
view_range, view_range_dark, intelligence, speed, gravity,
vel_terminal, door_try_mask, expl_radius, expl_damage, seq_hit,
seq_dying_hard, seq_dying_soft, seq_dead_hard, seq_dead_soft,
seq_standing, seq_moving, seq_tele_in, seq_tele_out,
atk_frequency, atk_melee, atk_range}, 156))
}
/// A monster definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Monster {
pub collection: u16,
pub vitality: u16,
pub dty_immune: damg::DamageTypeFlags,
pub dty_weak: damg::DamageTypeFlags,
pub flags: MonsterFlags,
pub cls_self: MonsterClass,
pub cls_friend: MonsterClass,
pub cls_enemy: MonsterClass,
pub snd_pitch: Fixed,
pub snd_see_enemy: OptU16,
pub snd_see_friend: OptU16,
pub snd_seeclear: OptU16,
pub snd_kill: OptU16,
pub snd_apologize: OptU16,
pub snd_amicide: OptU16,
pub snd_flaming: OptU16,
pub snd_active: OptU16,
pub snd_active_mask: u16,
pub typ_item: OptU16,
pub radius: Unit,
pub height: Unit,
pub height_hover: Unit,
pub ledge_min: Unit,
pub ledge_max: Unit,
pub ext_vel_scale: Fixed,
pub fxt_impact: OptU16,
pub fxt_impact_melee: OptU16,
pub fxt_trail: OptU16,
pub half_fov_horz: u16,
pub half_fov_vert: u16,
pub view_range: Unit,
pub view_range_dark: Unit,
pub intelligence: u16,
pub speed: u16,
pub gravity: u16,
pub vel_terminal: u16,
pub door_try_mask: u16,
pub expl_radius: OptU16,
pub expl_damage: damg::Damage,
pub seq_hit: OptU16,
pub seq_dying_hard: OptU16,
pub seq_dying_soft: OptU16,
pub seq_dead_hard: OptU16,
pub seq_dead_soft: OptU16,
pub seq_standing: u16,
pub seq_moving: u16,
pub seq_tele_in: OptU16,
pub seq_tele_out: OptU16,
pub atk_frequency: u16,
pub atk_melee: attk::Attack,
pub atk_range: attk::Attack,
}
c_bitfield! {
/// Flags for a monster.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct MonsterFlags: u32 {
IGNORE_LOS = 0,
FLYING = 1,
ALIEN = 2,
MAJOR = 3,
MINOR = 4,
NO_OMIT = 5,
FLOATS = 6,
NO_ATTACK = 7,
SNIPE = 8,
INVISIBLE = 9,
SUBTLY_INVISIBLE = 10,
KAMIKAZE = 11,
BERSERKER = 12,
ENLARGED = 13,
DELAYED_DEATH = 14,
FIRE_SYMMETRICAL = 15,
NUCLEAR_DEATH = 16,
NO_FIRE_BACKWARDS = 17,
CAN_DIE_IN_FLAMES = 18,
WAIT_FOR_GOOD_SHOT = 19,
TINY = 20,
FAST_ATTACK = 21,
LIKES_WATER = 22,
LIKES_SEWAGE = 23,
LIKES_LAVA = 24,
LIKES_GOO = 25,
TELE_UNDER_MEDIA = 26,
USE_RANDOM_WEAPON = 27,
}
}
c_bitfield! {
/// The composite type of a monster.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct MonsterClass: u32 {
PLAYER = 0,
CIVILIAN = 1,
MADD = 2,
POSSESSED_HUMMER = 3,
DEFENDER = 4,
FIGHTER = 5,
TROOPER = 6,
HUNTER = 7,
ENFORCER = 8,
JUGGERNAUT = 9,
HUMMER = 10,
COMPILER = 11,
CYBORG = 12,
ASSIMILATED = 13,
TICK = 14,
YETI = 15,
}
}
// EOF

View File

@ -1,11 +0,0 @@
//! `NAME` chunk.
use crate::{err::*, text::mac_roman_cstr};
/// Reads a `NAME` chunk.
pub fn read(b: &[u8]) -> ResultS<(String, usize)>
{
Ok((mac_roman_cstr(b), b.len()))
}
// EOF

View File

@ -1,29 +0,0 @@
//! `Note` type.
use super::pnts;
use crate::{err::*, text::*};
/// Reads a `NOTE` chunk.
pub fn read(b: &[u8]) -> ResultS<(Note, usize)>
{
read_data! {
endian: BIG, buf: b, size: 72, start: 0, data {
let pos = pnts::read_o[2; 4];
let poly = u16[6];
let text = mac_roman_cstr[8; 64] no_try;
}
}
Ok((Note{pos, poly, text}, 72))
}
/// Overhead map annotations.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Note {
pub pos: pnts::Point,
pub poly: u16,
pub text: String,
}
// EOF

View File

@ -1,57 +0,0 @@
//! `Object` type.
use crate::{err::*, fixed::{Angle, Unit}};
/// Reads an `OBJS` chunk.
pub fn read(b: &[u8]) -> ResultS<(Object, usize)>
{
read_data! {
endian: BIG, buf: b, size: 16, start: 0, data {
let group = u16[0];
let index = u16[2];
let angle = Angle[4];
let poly = u16[6];
let pos_x = Unit[8];
let pos_y = Unit[10];
let pos_z = Unit[12];
let flags = u16[14];
}
}
let bias = flags & 0xF0_00;
let flags = flags & 0x0F_FF;
let bias = bias >> 12;
let flags = flag_ok!(ObjectFlags, flags)?;
Ok((Object{group, index, angle, poly, pos_x, pos_y, pos_z, flags, bias}, 16))
}
/// An object in the world.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Object {
pub group: u16,
pub index: u16,
pub angle: Angle,
pub poly: u16,
pub pos_x: Unit,
pub pos_y: Unit,
pub pos_z: Unit,
pub flags: ObjectFlags,
pub bias: u16,
}
c_bitfield! {
/// Flags for `Object`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct ObjectFlags: u16 {
INVISIBLE = 0,
CEILING = 1,
BLIND = 2,
DEAF = 3,
FLOATING = 4,
NET_ONLY = 5,
}
}
// EOF

View File

@ -1,46 +0,0 @@
//! `ObjectFreq` type.
use crate::err::*;
/// Reads a `plac` chunk.
pub fn read(b: &[u8]) -> ResultS<(ObjectFreq, usize)>
{
read_data! {
endian: BIG, buf: b, size: 12, start: 0, data {
let flags = u16[0] flag ObjectFreqFlags;
let cnt_ini = u16[2];
let cnt_min = u16[4];
let cnt_max = u16[6];
let cnt_rnd = u16[8];
let chance = u16[10];
}
}
Ok((ObjectFreq{flags, cnt_ini, cnt_min, cnt_max, cnt_rnd, chance}, 12))
}
/// The difficulty definition for various object types.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObjectFreq {
pub flags: ObjectFreqFlags,
pub cnt_ini: u16,
pub cnt_min: u16,
pub cnt_max: u16,
pub cnt_rnd: u16,
pub chance: u16,
}
c_bitfield! {
/// Flags for `ObjectFreq`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct ObjectFreqFlags: u16 {
RANDOM_LOCATION = 0,
_3 = 3,
_4 = 4,
_5 = 5,
_6 = 6,
}
}
// EOF

View File

@ -1,72 +0,0 @@
//! `Platform` type.
use crate::{err::*, fixed::Unit};
/// Reads a `plat` chunk.
pub fn read(b: &[u8]) -> ResultS<(Platform, usize)>
{
read_data! {
endian: BIG, buf: b, size: 32, start: 0, data {
let ptype = u16[0];
let speed = u16[2];
let delay = u16[4];
let hei_max = Unit[6];
let hei_min = Unit[8];
let flags = u32[10] flag PlatformFlags;
let index = u16[14];
let tag = u16[16];
}
}
Ok((Platform{ptype, speed, delay, hei_min, hei_max, flags, index, tag}, 32))
}
/// Extra information for polygons with platforms.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Platform {
pub ptype: u16,
pub speed: u16,
pub delay: u16,
pub hei_min: Unit,
pub hei_max: Unit,
pub flags: PlatformFlags,
pub index: u16,
pub tag: u16,
}
c_bitfield! {
/// Flags for `Platform`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct PlatformFlags: u32 {
INIT_ACTIVE = 0,
INIT_EXTENDED = 1,
STOP_AT_EACH_LEVEL = 2,
STOP_AT_INIT_LEVEL = 3,
START_ADJ_ON_STOP = 4,
EXTENDS_FLOOR_TO_CEIL = 5,
COMES_FROM_FLOOR = 6,
COMES_FROM_CEIL = 7,
CAUSES_DAMAGE = 8,
NO_ACTIVATE_PARENT = 9,
ACTIVATES_ONCE = 10,
ACTIVATES_LIGHT = 11,
DEACTIVATES_LIGHT = 12,
PLAYER_CONTROLS = 13,
MONSTER_CONTROLS = 14,
REVERSE_ON_OBSTRUCT = 15,
NO_EXT_DEACTIVATION = 16,
USE_POLYGON_HEIGHTS = 17,
DELAYED_ACTIVATION = 18,
START_ADJ_ON_START = 19,
STOP_ADJ_ON_START = 20,
STOP_ADJ_ON_STOP = 21,
SLOW = 22,
START_AT_EACH_LEVEL = 23,
LOCKED = 24,
SECRET = 25,
DOOR = 26,
}
}
// EOF

View File

@ -1,54 +0,0 @@
//! `Point` type.
use super::epnt;
use crate::{err::*, fixed::Unit};
/// Reads a `Point` object.
pub fn read_o(b: &[u8]) -> ResultS<Point>
{
read_data! {
endian: BIG, buf: b, size: 4, start: 0, data {
let x = Unit[0];
let y = Unit[2];
}
}
Ok(Point{x, y})
}
/// Writes a `Point` object.
pub fn write_o(v: Point) -> Vec<u8>
{
let mut o = Vec::with_capacity(4);
o.extend(&v.x.to_be_bytes());
o.extend(&v.y.to_be_bytes());
o
}
/// Reads a `PNTS` chunk.
pub fn read(b: &[u8]) -> ResultS<(Point, usize)> {Ok((read_o(b)?, 4))}
impl From<&Point> for Point
{
fn from(pnts: &Point) -> Self {*pnts}
}
impl From<epnt::Endpoint> for Point
{
fn from(epnt: epnt::Endpoint) -> Self {epnt.pos}
}
impl From<&epnt::Endpoint> for Point
{
fn from(epnt: &epnt::Endpoint) -> Self {epnt.pos}
}
/// A point in world-space.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Point {
pub x: Unit,
pub y: Unit,
}
// EOF

View File

@ -1,190 +0,0 @@
//! `Polygon` type.
use super::pnts;
use crate::{bin::OptU16, err::*, fixed::Unit, xfer::TransferMode};
/// Reads a polygon for either M1 or M2.
pub fn read_poly_inter(b: &[u8]) -> ResultS<Polygon>
{
read_data! {
endian: BIG, buf: b, size: 128, start: 0, data {
let tex_flr = OptU16[40];
let tex_cei = OptU16[42];
let hei_flr = Unit[44];
let hei_cei = Unit[46];
let lit_flr = u16[48];
let lit_cei = u16[50];
let xfr_flr = u16[64] enum TransferMode;
let xfr_cei = u16[66] enum TransferMode;
}
}
Ok(Polygon{tex_flr, tex_cei, hei_flr, hei_cei, lit_flr, lit_cei, xfr_flr,
xfr_cei, ..Polygon::default()})
}
/// Reads a `POLY` chunk.
pub fn read(b: &[u8]) -> ResultS<(Polygon, usize)>
{
read_data! {
endian: BIG, buf: b, size: 128, start: 0, data {
let ptype = u16[0];
let pdata = u16[4];
let ori_flr = pnts::read_o[108; 4];
let ori_cei = pnts::read_o[112; 4];
let med_ind = OptU16[116];
let med_ctl = u16[118];
let snd_amb = OptU16[122];
let snd_ind = u16[120];
let snd_rnd = OptU16[124];
}
}
let poly = read_poly_inter(b)?;
let ptype = PolygonType::new(ptype, pdata)?;
Ok((Polygon{ptype, ori_flr, ori_cei, med_ind, med_ctl, snd_ind, snd_amb,
snd_rnd, ..poly}, 128))
}
/// Reads an old `POLY` chunk.
pub fn read_old(b: &[u8]) -> ResultS<(Polygon, usize)>
{
read_data! {
endian: BIG, buf: b, size: 128, start: 0, data {
let ptype = u16[0];
let pdata = u16[4];
}
}
let poly = read_poly_inter(b)?;
let ptype = PolygonType::new_old(ptype, pdata)?;
Ok((Polygon{ptype, ..poly}, 128))
}
impl PolygonType
{
/// Creates a `PolygonType` from a `n`/`pdata` pair.
pub fn new(n: u16, pdata: u16) -> Result<Self, ReprError>
{
match n {
0 => Ok(PolygonType::Normal),
1 => Ok(PolygonType::ImpassItem),
2 => Ok(PolygonType::ImpassMons),
3 => Ok(PolygonType::Hill),
4 => Ok(PolygonType::Base),
5 => Ok(PolygonType::Platform(pdata)),
6 => Ok(PolygonType::TrigLightOn(pdata)),
7 => Ok(PolygonType::TrigPlatOn(pdata)),
8 => Ok(PolygonType::TrigLightOff(pdata)),
9 => Ok(PolygonType::TrigPlatOff(pdata)),
10 => Ok(PolygonType::Teleporter(pdata)),
11 => Ok(PolygonType::ZoneBorder),
12 => Ok(PolygonType::Goal),
13 => Ok(PolygonType::TrigMonsVis),
14 => Ok(PolygonType::TrigMonsInv),
15 => Ok(PolygonType::TrigMonsDual),
16 => Ok(PolygonType::TrigItems),
17 => Ok(PolygonType::MustExplore),
18 => Ok(PolygonType::AutoExit),
19 => Ok(PolygonType::OuchMinor),
20 => Ok(PolygonType::OuchMajor),
21 => Ok(PolygonType::Glue),
22 => Ok(PolygonType::GlueTrigger(pdata)),
23 => Ok(PolygonType::GlueSuper),
n => Err(ReprError::new("PolygonType", n)),
}
}
/// Creates a `PolygonType` from a Marathon 1 compatible `n`/`pdata` pair.
fn new_old(n: u16, pdata: u16) -> Result<Self, ReprError>
{
match n {
0 => Ok(PolygonType::Normal),
1 => Ok(PolygonType::ImpassItem),
2 => Ok(PolygonType::ImpassMons),
3 => Ok(PolygonType::OuchMinor),
4 => Ok(PolygonType::OuchMajor),
5 => Ok(PolygonType::Platform(pdata)),
6 => Ok(PolygonType::TrigLightOn(pdata)),
7 => Ok(PolygonType::TrigPlatOn(pdata)),
8 => Ok(PolygonType::TrigLightOff(pdata)),
9 => Ok(PolygonType::TrigPlatOff(pdata)),
10 => Ok(PolygonType::Teleporter(pdata)),
11 => Ok(PolygonType::Glue),
12 => Ok(PolygonType::GlueTrigger(pdata)),
13 => Ok(PolygonType::GlueSuper),
14 => Ok(PolygonType::MustExplore),
15 => Ok(PolygonType::AutoExit),
n => Err(ReprError::new("PolygonType", n)),
}
}
}
impl Default for PolygonType
{
fn default() -> Self {PolygonType::Normal}
}
/// A polygon segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Polygon {
pub ptype: PolygonType,
pub tex_flr: OptU16,
pub tex_cei: OptU16,
pub hei_flr: Unit,
pub hei_cei: Unit,
pub lit_flr: u16,
pub lit_cei: u16,
pub xfr_flr: TransferMode,
pub xfr_cei: TransferMode,
pub ori_flr: pnts::Point,
pub ori_cei: pnts::Point,
pub med_ind: OptU16,
pub med_ctl: u16,
pub snd_ind: u16,
pub snd_amb: OptU16,
pub snd_rnd: OptU16,
}
/// The action type of a `Polygon`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolygonType {
Normal,
ImpassItem,
ImpassMons,
Hill,
Base,
Platform(u16),
TrigLightOn(u16),
TrigPlatOn(u16),
TrigLightOff(u16),
TrigPlatOff(u16),
Teleporter(u16),
ZoneBorder,
Goal,
TrigMonsVis,
TrigMonsInv,
TrigMonsDual,
TrigItems,
MustExplore,
AutoExit,
OuchMinor,
OuchMajor,
Glue,
GlueTrigger(u16),
GlueSuper,
}
c_bitfield! {
/// Flags for `Polygon`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct PolygonFlags: u16 {
DETACHED = 14,
}
}
// EOF

View File

@ -1,89 +0,0 @@
//! `Projectile` type.
use crate::{bin::OptU16, err::*, fixed::{Fixed, Unit}};
use super::damg;
/// Reads a `PRpx` chunk.
pub fn read(b: &[u8]) -> ResultS<(Projectile, usize)>
{
read_data! {
endian: BIG, buf: b, size: 48, start: 0, data {
let collection = OptU16[0];
let shape = u16[2];
let fxt_explode = OptU16[4];
let fxt_exp_media = OptU16[6];
let fxt_trail = OptU16[8];
let tic_trail = u16[10];
let max_trail = OptU16[12];
let typ_media = OptU16[14];
let radius = Unit[16];
let dmg_rad = Unit[18];
let dmg_def = damg::read[20; 12];
let flags = u32[32] flag ProjectileFlags;
let speed = Unit[36];
let range = Unit[38];
let snd_pitch = Fixed[40];
let snd_fly = OptU16[44];
let snd_bounce = OptU16[46];
}
}
Ok((Projectile{collection, shape, fxt_explode, fxt_exp_media, fxt_trail,
tic_trail, max_trail, typ_media, radius, dmg_rad, dmg_def,
flags, speed, range, snd_pitch, snd_fly, snd_bounce}, 48))
}
/// A projectile definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Projectile {
pub collection: OptU16,
pub shape: u16,
pub fxt_explode: OptU16,
pub fxt_exp_media: OptU16,
pub fxt_trail: OptU16,
pub tic_trail: u16,
pub max_trail: OptU16,
pub typ_media: OptU16,
pub radius: Unit,
pub dmg_rad: Unit,
pub dmg_def: damg::Damage,
pub flags: ProjectileFlags,
pub speed: Unit,
pub range: Unit,
pub snd_pitch: Fixed,
pub snd_fly: OptU16,
pub snd_bounce: OptU16,
}
c_bitfield! {
/// Flags for a projectile.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct ProjectileFlags: u32 {
GUIDED = 0,
STOP_ON_LOOP = 1,
PERSISTENT = 2,
ALIEN = 3,
GRAVITY = 4,
NO_HORZ_ERROR = 5,
NO_VERT_ERROR = 6,
TOGGLE_PANELS = 7,
POS_VERT_ERROR = 8,
MELEE = 9,
RIPPER = 10,
PASS_TRANS_RANDOM = 11,
PASS_TRANS_MORE = 12,
DOUBLE_GRAVITY = 13,
REBOUND_FLOOR = 14,
THROUGH_MEDIA = 15,
BECOME_ITEM = 16,
BLOODY = 17,
WANDER_HORZ = 18,
WANDER_VERT = 19,
USE_LOW_GRAV = 20,
PASS_MEDIA = 21,
}
}
// EOF

View File

@ -1,77 +0,0 @@
//! `Physics` type.
use crate::{err::*, fixed::Fixed};
/// Reads a `PXpx` chunk.
pub fn read(b: &[u8]) -> ResultS<(Physics, usize)>
{
read_data! {
endian: BIG, buf: b, size: 104, start: 0, data {
let vel_fwd = Fixed[0];
let vel_bkw = Fixed[4];
let vel_prp = Fixed[8];
let acc_nrm = Fixed[12];
let dec_nrm = Fixed[16];
let dec_air = Fixed[20];
let acc_grv = Fixed[24];
let acc_cli = Fixed[28];
let vel_trm = Fixed[32];
let dec_ext = Fixed[36];
let acc_ang = Fixed[40];
let dec_ang = Fixed[44];
let vel_ang = Fixed[48];
let vel_rec = Fixed[52];
let fng_vel = Fixed[56];
let fng_max = Fixed[60];
let ele_max = Fixed[64];
let dec_xng = Fixed[68];
let stp_dta = Fixed[72];
let stp_amp = Fixed[76];
let ply_rad = Fixed[80];
let ply_hei = Fixed[84];
let ply_dhi = Fixed[88];
let ply_cam = Fixed[92];
let ply_spl = Fixed[96];
let ply_hcm = Fixed[100];
}
}
Ok((Physics{acc_ang, acc_cli, acc_grv, acc_nrm, dec_air, dec_ang, dec_ext,
dec_nrm, dec_xng, ele_max, fng_max, fng_vel, ply_cam, ply_dhi,
ply_hcm, ply_hei, ply_rad, ply_spl, stp_amp, stp_dta, vel_ang,
vel_bkw, vel_fwd, vel_prp, vel_rec, vel_trm}, 104))
}
/// Static physics information.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Physics {
pub acc_ang: Fixed,
pub acc_cli: Fixed,
pub acc_grv: Fixed,
pub acc_nrm: Fixed,
pub dec_air: Fixed,
pub dec_ang: Fixed,
pub dec_ext: Fixed,
pub dec_nrm: Fixed,
pub dec_xng: Fixed,
pub ele_max: Fixed,
pub fng_max: Fixed,
pub fng_vel: Fixed,
pub ply_cam: Fixed,
pub ply_dhi: Fixed,
pub ply_hcm: Fixed,
pub ply_hei: Fixed,
pub ply_rad: Fixed,
pub ply_spl: Fixed,
pub stp_amp: Fixed,
pub stp_dta: Fixed,
pub vel_ang: Fixed,
pub vel_bkw: Fixed,
pub vel_fwd: Fixed,
pub vel_prp: Fixed,
pub vel_rec: Fixed,
pub vel_trm: Fixed,
}
// EOF

View File

@ -1,84 +0,0 @@
//! `Side` type.
use super::stex;
use crate::{bin::OptU16, err::*, fixed::Fixed, xfer::TransferMode};
/// Reads a `SIDS` chunk.
pub fn read(b: &[u8]) -> ResultS<(Side, usize)>
{
read_data! {
endian: BIG, buf: b, size: 64, start: 0, data {
let stype = u16[0] enum SideType;
let flags = u8[3] flag SideFlags;
let tex_pri = stex::read[4; 6];
let tex_sec = stex::read[10; 6];
let tex_tra = stex::read[16; 6];
let paneltyp = u16[38];
let paneldat = i16[40];
let xfer_pri = u16[42] enum TransferMode;
let xfer_sec = u16[44] enum TransferMode;
let xfer_tra = u16[46] enum TransferMode;
let shade = Fixed[48];
}
}
Ok((Side{stype, flags, tex_pri, tex_sec, tex_tra, paneltyp, paneldat,
xfer_pri, xfer_sec, xfer_tra, shade}, 64))
}
/// Reads an old `SIDS` chunk.
pub fn read_old(b: &[u8]) -> ResultS<(Side, usize)>
{
let (side, siz) = read(b)?;
Ok((Side{tex_tra: stex::SideTex{tex_id: OptU16::none(), ..side.tex_tra},
shade: 0.into(),
flags: side.flags | SideFlags::ITEM_OPT,
..side}, siz))
}
/// One side of a line segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Side {
pub stype: SideType,
pub flags: SideFlags,
pub tex_pri: stex::SideTex,
pub tex_sec: stex::SideTex,
pub tex_tra: stex::SideTex,
pub paneltyp: u16,
pub paneldat: i16,
pub xfer_pri: TransferMode,
pub xfer_sec: TransferMode,
pub xfer_tra: TransferMode,
pub shade: Fixed,
}
c_bitfield! {
/// Flags for `Side`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct SideFlags: u8 {
STATUS = 0,
PANEL = 1,
REPAIR = 2,
ITEM_USE = 3,
LIGHTED = 4,
CAN_DESTROY = 5,
HIT_ONLY = 6,
ITEM_OPT = 7,
}
}
c_enum! {
/// The texture type of a `Side`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum SideType: u16 {
Full = 0,
High = 1,
Low = 2,
Composite = 3,
Split = 4,
}
}
// EOF

View File

@ -1,36 +0,0 @@
//! `SideTex` type.
use crate::{bin::OptU16, err::*};
use super::pnts;
/// Reads a `SideTex` object.
pub fn read(b: &[u8]) -> ResultS<SideTex>
{
read_data! {
endian: BIG, buf: b, size: 6, start: 0, data {
let offs = pnts::read_o[0; 4];
let tex_id = OptU16[4];
}
}
Ok(SideTex{offs, tex_id})
}
/// Writes a `SideTex` object.
pub fn write(v: &SideTex) -> Vec<u8>
{
let mut o = Vec::with_capacity(6);
o.extend(pnts::write_o(v.offs));
o.extend(&v.tex_id.to_be_bytes());
o
}
/// The texture of a side segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SideTex {
pub offs: pnts::Point,
pub tex_id: OptU16,
}
// EOF

View File

@ -1,55 +0,0 @@
//! `Terminal` type.
use super::{trmf, trmg};
use crate::{bin, err::*, text};
/// Reads a `term` chunk.
pub fn read(b: &[u8]) -> ResultS<(Terminal, usize)>
{
read_data! {
endian: BIG, buf: b, size: 10, start: 0, data {
let end = u16[0] usize;
let encoded = u16[2];
let lines = u16[4];
let group_n = u16[6] usize;
let face_n = u16[8] usize;
}
}
let (i_grp, x) = bin::rd_array_num(&b[10..], group_n, trmg::read)?;
let (faces, y) = bin::rd_array_num(&b[10 + x..], face_n, trmf::read)?;
let text = ok!(b.get(10 + x + y..end), "not enough data")?;
let text = if encoded != 0 {
text::fuck_string(text)
} else {
text.to_vec()
};
let mut groups = Vec::with_capacity(group_n);
for grp in &i_grp {
let flags = grp.flags;
let ttype = grp.ttype;
let lines = grp.lines;
let beg = grp.beg;
let len = grp.len;
let text = ok!(text.get(beg..beg + len), "bad offset")?;
let text = text::mac_roman_cstr(text);
groups.push(trmg::Group{flags, ttype, lines, text});
}
Ok((Terminal{lines, groups, faces}, end))
}
/// A terminal definition, with collections of groups and faces.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Terminal {
pub lines: u16,
pub groups: Vec<trmg::Group>,
pub faces: Vec<trmf::Face>,
}
// EOF

View File

@ -1,73 +0,0 @@
//! `Monster` type.
use crate::{bin::OptU16, err::*, fixed::Unit};
/// Reads a `Trigger` object.
pub fn read(b: &[u8]) -> ResultS<Trigger>
{
read_data! {
endian: BIG, buf: b, size: 36, start: 0, data {
let magazine = u16[0];
let typ_ammo = OptU16[2];
let tic_round = OptU16[4];
let tic_recover = u16[6];
let tic_charge = u16[8];
let recoil = Unit[10];
let snd_fire = OptU16[12];
let snd_click = OptU16[14];
let snd_charge = OptU16[16];
let snd_casing = OptU16[18];
let snd_reload = OptU16[20];
let snd_charged = OptU16[22];
let typ_proj = u16[24];
let theta = u16[26];
let dx = i16[28];
let dz = i16[30];
let typ_casing = u16[32] enum CasingType;
let burst = u16[34];
}
}
Ok(Trigger{burst, dx, dz, magazine, recoil, snd_casing, snd_charge,
snd_charged, snd_click, snd_fire, snd_reload, theta, tic_charge,
tic_recover, tic_round, typ_ammo, typ_casing, typ_proj})
}
/// The definition of one of two triggers for a weapon.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Trigger {
pub burst: u16,
pub dx: i16,
pub dz: i16,
pub magazine: u16,
pub recoil: Unit,
pub snd_casing: OptU16,
pub snd_charge: OptU16,
pub snd_charged: OptU16,
pub snd_click: OptU16,
pub snd_fire: OptU16,
pub snd_reload: OptU16,
pub theta: u16,
pub tic_charge: u16,
pub tic_recover: u16,
pub tic_round: OptU16,
pub typ_ammo: OptU16,
pub typ_casing: CasingType,
pub typ_proj: u16,
}
c_enum! {
/// A bullet shell casing emitted by a weapon.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum CasingType: u16 {
Rifle = 0,
Pistol = 1,
PistolLeft = 2,
PistolRight = 3,
SMG = 4,
None = 0xFFFF,
}
}
// EOF

View File

@ -1,28 +0,0 @@
//! `Face` type.
use crate::err::*;
/// Reads a `Face`.
pub fn read(b: &[u8]) -> ResultS<(Face, usize)>
{
read_data! {
endian: BIG, buf: b, size: 6, start: 0, data {
let start = u16[0] usize;
let face = u16[2];
let color = u16[4];
}
}
Ok((Face{start, face, color}, 6))
}
/// A text face.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Face {
pub start: usize,
pub face: u16,
pub color: u16,
}
// EOF

View File

@ -1,102 +0,0 @@
//! `Group` type.
use crate::err::*;
/// Reads an `InterGroup`.
pub fn read(b: &[u8]) -> ResultS<(InterGroup, usize)>
{
read_data! {
endian: BIG, buf: b, size: 12, start: 0, data {
let flags = u16[0] flag GroupFlags;
let ttype = u16[2];
let pdata = u16[4];
let beg = u16[6] usize;
let len = u16[8] usize;
let lines = u16[10];
}
}
let ttype = match ttype {
0 => GroupType::Logon(pdata),
1 => GroupType::Unfinished,
2 => GroupType::Success,
3 => GroupType::Failure,
4 => GroupType::Info,
5 => GroupType::End,
6 => GroupType::TeleInter(pdata),
7 => GroupType::TeleIntra(pdata),
8 => GroupType::Checkpoint(pdata),
9 => GroupType::Sound(pdata),
10 => GroupType::Movie(pdata),
11 => GroupType::Track(pdata),
12 => GroupType::Pict(pdata),
13 => GroupType::Logoff(pdata),
14 => GroupType::Camera(pdata),
15 => GroupType::Static(pdata),
16 => GroupType::Tag(pdata),
n => return Err(ReprError::new("GroupType", n).into()),
};
Ok((InterGroup{flags, ttype, lines, beg, len}, 12))
}
impl Default for GroupType
{
fn default() -> Self {GroupType::Unfinished}
}
/// Interim structure.
#[derive(Debug, Eq, PartialEq)]
pub struct InterGroup {
pub flags: GroupFlags,
pub ttype: GroupType,
pub lines: u16,
pub beg: usize,
pub len: usize,
}
/// A terminal command grouping.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Group {
pub flags: GroupFlags,
pub ttype: GroupType,
pub lines: u16,
pub text: String,
}
/// The command of a `Group`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GroupType {
Logon(u16),
Unfinished,
Success,
Failure,
Info,
End,
TeleInter(u16),
TeleIntra(u16),
Checkpoint(u16),
Sound(u16),
Movie(u16),
Track(u16),
Pict(u16),
Logoff(u16),
Camera(u16),
Static(u16),
Tag(u16),
}
c_bitfield! {
/// Flags for `Group`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct GroupFlags: u16 {
/// Draws the picture on the right.
DRAW_ON_RIGHT = 0,
/// Draws the picture in the center.
DRAW_CENTER = 1,
}
}
// EOF

View File

@ -1,108 +0,0 @@
//! `Weapon` type.
use crate::{bin::OptU16, err::*, fixed::Fixed};
use super::trig;
/// Reads a `WPpx` chunk.
pub fn read(b: &[u8]) -> ResultS<(Weapon, usize)>
{
read_data! {
endian: BIG, buf: b, size: 134, start: 0, data {
let typ_item = u16[0];
let typ_powerup = OptU16[2];
let typ_weapon = u16[4] enum WeaponType;
let flags = u16[6] flag WeaponFlags;
let lit_value = Fixed[8];
let lit_decay = u16[12];
let hei_idle = Fixed[14];
let amp_bob = Fixed[18];
let hei_kick = Fixed[22];
let hei_reload = Fixed[26];
let wid_idle = Fixed[30];
let amp_horz = Fixed[34];
let collection = u16[38];
let frm_idle = u16[40];
let frm_firing = u16[42];
let frm_reload = OptU16[44];
let frm_charge = OptU16[48];
let frm_charged = OptU16[50];
let tic_ready = u16[52];
let tic_load_beg = u16[54];
let tic_load_mid = u16[56];
let tic_load_end = u16[58];
let tic_powerup = u16[60];
let trig_pri = trig::read[62; 36];
let trig_sec = trig::read[98; 36];
}
}
Ok((Weapon{amp_bob, amp_horz, collection, flags, frm_charge, frm_charged,
frm_firing, frm_idle, frm_reload, hei_idle, hei_kick, hei_reload,
lit_decay, lit_value, tic_load_beg, tic_load_end, tic_load_mid,
tic_powerup, tic_ready, trig_pri, trig_sec, typ_item,
typ_powerup, typ_weapon, wid_idle}, 134))
}
/// A weapon definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Weapon {
pub amp_bob: Fixed,
pub amp_horz: Fixed,
pub collection: u16,
pub flags: WeaponFlags,
pub frm_charge: OptU16,
pub frm_charged: OptU16,
pub frm_firing: u16,
pub frm_idle: u16,
pub frm_reload: OptU16,
pub hei_idle: Fixed,
pub hei_kick: Fixed,
pub hei_reload: Fixed,
pub lit_decay: u16,
pub lit_value: Fixed,
pub tic_load_beg: u16,
pub tic_load_end: u16,
pub tic_load_mid: u16,
pub tic_powerup: u16,
pub tic_ready: u16,
pub trig_pri: trig::Trigger,
pub trig_sec: trig::Trigger,
pub typ_item: u16,
pub typ_powerup: OptU16,
pub typ_weapon: WeaponType,
pub wid_idle: Fixed,
}
c_bitfield! {
/// Flags for a weapon.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct WeaponFlags: u16 {
AUTOMATIC = 0,
REMOVE_AFTER_USE = 1,
INSTANT_CASING = 2,
OVERLOADS = 3,
RANDOM_AMMO = 4,
TEMPORARY_POWER = 5,
RELOAD_ONE_HAND = 6,
FIRE_OUT_OF_PHASE = 7,
FIRE_UNDER_MEDIA = 8,
TRIGGER_SAME_AMMO = 9,
SECONDARY_FLIP = 10,
}
}
c_enum! {
/// The type of functionality a weapon provides.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum WeaponType: u16 {
Melee = 0,
Normal = 1,
DualFunc = 2,
DualPistol = 3,
Multipurpose = 4,
}
}
// EOF

View File

@ -1,50 +0,0 @@
//! Meta-information of this crate.
macro_rules! meta_str {
($($(#[$outer:meta])* $name:ident = $e:expr;)*) => {
$($(#[$outer])* pub const fn $name() -> &'static str {$e})*
pub mod ffi
{
$(
doc_comment! {
concat!("FFI variant of [`",
stringify!($name),
"`]\n\n[`",
stringify!($name),
"`]: ../fn.",
stringify!($name),
".html"),
pub const fn $name() -> crate::ffi::NT {c_str!($e)}
}
)*
}
}
}
meta_str! {
/// The authors of this crate, `:` separated.
authors = env!("CARGO_PKG_AUTHORS");
/// The description of this crate.
description = env!("CARGO_PKG_DESCRIPTION");
/// The home page of this crate.
homepage = env!("CARGO_PKG_HOMEPAGE");
/// The full license text of this crate.
license_text = include_str!("../LICENSE");
/// The name of this crate.
name = env!("CARGO_PKG_NAME");
/// The repository of this crate.
repository = env!("CARGO_PKG_REPOSITORY");
/// The full version of this crate.
version = env!("CARGO_PKG_VERSION");
/// The major version of this crate.
version_major = env!("CARGO_PKG_VERSION_MAJOR");
/// The minor version of this crate.
version_minor = env!("CARGO_PKG_VERSION_MINOR");
/// The patch version of this crate.
version_patch = env!("CARGO_PKG_VERSION_PATCH");
/// The pre-release version of this crate.
version_pre = env!("CARGO_PKG_VERSION_PRE");
}
// EOF

View File

@ -1,59 +0,0 @@
//! Marathon Shapes format handling.
pub mod bmap;
pub mod clut;
pub mod coll;
pub mod fram;
pub mod sequ;
use crate::{bin::usize_from_u32, err::*};
use std::io::prelude::*;
/// Reads a collection at an offset provided by the Shapes header.
pub fn read_coll_at_offset(b: &[u8],
ofs: u32,
len: usize) -> ResultS<Option<coll::Collection>>
{
if ofs != u32::max_value() {
let ofs = usize_from_u32(ofs);
let dat = ok!(b.get(ofs..ofs + len), "bad offset")?;
Ok(Some(coll::read(dat)?))
} else {
Ok(None)
}
}
/// Read all of the collections in a Shapes file.
pub fn read(fp: &mut impl Read) -> ResultS<Collections>
{
let mut b = Vec::new();
fp.read_to_end(&mut b)?;
let mut collections = vec![(None, None); 32];
for (i, co) in collections.iter_mut().enumerate() {
read_data! {
endian: BIG, buf: &b, size: 32, start: i * 32, data {
let lo_ofs = u32[4];
let lo_len = u32[8] usize;
let hi_ofs = u32[12];
let hi_len = u32[16] usize;
}
}
let lo = read_coll_at_offset(&b, lo_ofs, lo_len)?;
let hi = read_coll_at_offset(&b, hi_ofs, hi_len)?;
*co = (lo, hi);
}
Ok(collections)
}
/// A collection, which may have low- and high-definition variations, or none.
pub type CollectionDef = (Option<coll::Collection>, Option<coll::Collection>);
/// The set of all collections in a Shapes file.
pub type Collections = Vec<CollectionDef>;
// EOF

View File

@ -1,140 +0,0 @@
//! Shapes file bitmap type.
use crate::{err::*, image::Image};
use super::clut;
/// Reads a `Bitmap`.
pub fn read(b: &[u8]) -> ResultS<Bitmap>
{
read_data! {
endian: BIG, buf: b, size: 26, start: 0, data {
let width = u16[0] usize;
let height = u16[2] usize;
let compr = u16[4];
let flags = u16[6] flag BmpFlags;
let depth = u16[8];
}
}
let compr = compr == u16::max_value();
let alpha = flags.contains(BmpFlags::TRANSPARENT);
let cmajr = flags.contains(BmpFlags::COLUMN_MAJOR);
if depth != 8 {
bail!("invalid bit depth (should always be 8)");
}
let mut bmp = Bitmap::new(width, height, alpha, cmajr);
let mut p = 30 + if cmajr {4 * width} else {4 * height};
let scanlines = if cmajr {width} else {height};
let pitch = if cmajr {height} else {width};
if compr {
// compressed scanlines (transparency RLE)
for _ in 0..scanlines {
read_data! {
endian: BIG, buf: b, size: 4, start: p, data {
let fst = u16[0] usize;
let lst = u16[2] usize;
}
}
let end = lst - fst;
p += 4;
if lst < fst || fst > pitch || lst > pitch {
bail!("invalid compressed scanline");
}
for _ in 0..fst {
bmp.cr.push(0);
}
bmp.cr.extend(ok!(b.get(p..p + end), "not enough data")?);
for _ in lst..pitch {
bmp.cr.push(0);
}
p += end;
}
} else {
// simple copy
bmp.cr.extend(ok!(b.get(p..p + width * height), "not enough data")?);
}
Ok(bmp)
}
impl Bitmap
{
/// Creates an empty bitmap.
pub fn new(w: usize, h: usize, alpha: bool, cmajr: bool) -> Self
{
Self{w, h, alpha, cmajr, cr: Vec::with_capacity(w * h)}
}
}
impl<'a, 'b> ImageShp<'a, 'b>
{
/// Creates an `ImageShp` with the given bitmap.
#[inline]
pub const fn new(bmp: &'a Bitmap, clut: &'b [clut::ColorShp]) -> Self
{
Self{bmp, clut}
}
}
impl Image for ImageShp<'_, '_>
{
type Output = clut::ColorShp;
fn w(&self) -> usize {self.bmp.w}
fn h(&self) -> usize {self.bmp.h}
fn index(&self, x: usize, y: usize) -> &Self::Output
{
static TRANSLUCENT_COLOR: clut::ColorShp = clut::ColorShp::Translucent;
let cr = usize::from(if self.bmp.cmajr {
self.bmp.cr[y + x * self.bmp.h]
} else {
self.bmp.cr[x + y * self.bmp.w]
});
if self.bmp.alpha && cr == 0 {
&TRANSLUCENT_COLOR
} else {
self.clut.get(cr).unwrap_or(&TRANSLUCENT_COLOR)
}
}
}
/// An unpacked Shape bitmap.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Bitmap {
w: usize,
h: usize,
cr: Vec<u8>,
alpha: bool,
cmajr: bool,
}
/// An image from a Shape. This mainly just exists so that `Bitmap` can use the
/// `Image` trait.
#[derive(Debug, Eq, PartialEq)]
pub struct ImageShp<'a, 'b> {
bmp: &'a Bitmap,
clut: &'b [clut::ColorShp],
}
c_bitfield! {
pub struct BmpFlags: u16 {
TRANSPARENT = 14,
COLUMN_MAJOR = 15,
}
}
// EOF

View File

@ -1,114 +0,0 @@
//! Shapes file color lookup tables.
use crate::{err::*, image::Color};
/// Reads a color from a color table into `clut`.
pub fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()>
{
read_data! {
endian: BIG, buf: b, size: 8, start: 0, data {
let flag = u8[0];
let ind = u8[1];
let r = u16[2];
let g = u16[4];
let b = u16[6];
}
}
let cr = ok!(clut.get_mut(usize::from(ind)), "bad index")?;
*cr = match flag {
128 => ColorShp::Lit {r, g, b},
0 => ColorShp::Opaque{r, g, b},
_ => {
return Err(err_msg("invalid flag in color"));
}
};
Ok(())
}
/// Reads color tables from `b`.
pub fn read(b: &[u8],
tab_ofs: usize,
tab_num: usize,
clr_num: usize) -> ResultS<Vec<Clut>>
{
let end = tab_num * clr_num * 8;
let b = ok!(b.get(tab_ofs..tab_ofs + end), "bad offset")?;
let mut v = vec![vec![ColorShp::Translucent; clr_num]; tab_num];
let mut p = 0;
for clut in v.iter_mut().take(tab_num) {
for _ in 0..clr_num {
read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?;
p += 8;
}
}
Ok(v)
}
impl Color for ColorShp
{
fn r(&self) -> u16
{
match *self {
ColorShp::Translucent => 0,
ColorShp::Opaque{r, ..} |
ColorShp::Lit {r, ..} => r,
}
}
fn g(&self) -> u16
{
match *self {
ColorShp::Translucent => 0,
ColorShp::Opaque{g, ..} |
ColorShp::Lit {g, ..} => g,
}
}
fn b(&self) -> u16
{
match *self {
ColorShp::Translucent => 0,
ColorShp::Opaque{b, ..} |
ColorShp::Lit {b, ..} => b,
}
}
fn a(&self) -> u16
{
match *self {
ColorShp::Translucent => 0,
ColorShp::Opaque{..} |
ColorShp::Lit {..} => u16::max_value(),
}
}
}
/// A color in a `Clut`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ColorShp {
/// A completely translucent color.
Translucent,
/// An opaque color which may be shaded.
Opaque{/** The red component. */ r: u16,
/** The green component. */ g: u16,
/** The blue component. */ b: u16},
/// An opaque color which may not be shaded.
Lit{/** The red component. */ r: u16,
/** The green component. */ g: u16,
/** The blue component. */ b: u16},
}
/// A color collection.
pub type Clut = Vec<ColorShp>;
// EOF

View File

@ -1,68 +0,0 @@
//! Shapes file collection type.
use crate::{bin::rd_ofstable, err::*};
use super::{bmap, clut, fram, sequ};
/// Reads a `Collection`.
pub fn read(b: &[u8]) -> ResultS<Collection>
{
read_data! {
endian: BIG, buf: b, size: 544, start: 0, data {
let version = u16[0];
let cl_type = u16[2] enum CollectionType;
let clr_num = u16[6] usize;
let tab_num = u16[8] usize;
let tab_ofs = u32[10] usize;
let seq_num = u16[14] usize;
let seq_ofs = u32[16] usize;
let frm_num = u16[20] usize;
let frm_ofs = u32[22] usize;
let bmp_num = u16[26] usize;
let bmp_ofs = u32[28] usize;
}
}
if version != 3 {
bail!("invalid collection definition");
}
let tabs = clut::read(b, tab_ofs, tab_num, clr_num)?;
let bmps = rd_ofstable(b, bmp_ofs, bmp_num, bmap::read)?;
let frms = rd_ofstable(b, frm_ofs, frm_num, fram::read)?;
let seqs = rd_ofstable(b, seq_ofs, seq_num, sequ::read)?;
Ok(Collection{ctyp: cl_type, tabs, bmps, frms, seqs})
}
/// A collection of color tables, bitmaps, frames and sequences.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Collection {
/// The type of collection this is.
pub ctyp: CollectionType,
/// All of the color tables in this collection.
pub tabs: Vec<clut::Clut>,
/// All of the bitmaps in this collection.
pub bmps: Vec<bmap::Bitmap>,
/// All of the frames in this collection.
pub frms: Vec<fram::Frame>,
/// All of the sequences in this collection.
pub seqs: Vec<sequ::Sequence>,
}
c_enum! {
/// The type of a collection.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum CollectionType: u16 {
Unused = 0,
Wall = 1,
Object = 2,
Interface = 3,
Scenery = 4,
}
}
// EOF

View File

@ -1,70 +0,0 @@
//! Shapes file frame type.
use crate::{err::*, fixed::*};
/// Reads a `Frame`.
pub fn read(b: &[u8]) -> ResultS<Frame>
{
read_data! {
endian: BIG, buf: b, size: 36, start: 0, data {
let flags = u16[0] flag FrameFlags;
let min_lt = Fixed[2];
let bmp_ind = u16[6] usize;
let wrl_l = Unit[16];
let wrl_r = Unit[18];
let wrl_t = Unit[20];
let wrl_b = Unit[22];
let wrl_x = Unit[24];
let wrl_y = Unit[26];
}
}
Ok(Frame{flags, min_lt, bmp_ind, wrl_l, wrl_r, wrl_t, wrl_b, wrl_x, wrl_y})
}
/// A frame, also known as a low level shape.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Frame {
/// The flags for this frame.
pub flags: FrameFlags,
/// The minimum light level for this frame.
pub min_lt: Fixed,
/// The index of the bitmap this frame uses.
pub bmp_ind: usize,
/// The left translation for this frame.
pub wrl_l: Unit,
/// The right translation for this frame.
pub wrl_r: Unit,
/// The top translation for this frame.
pub wrl_t: Unit,
/// The bottom translation for this frame.
pub wrl_b: Unit,
/// The X translation for this frame.
pub wrl_x: Unit,
/// The Y translation for this frame.
pub wrl_y: Unit,
}
c_bitfield! {
/// Flags for `Frame`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct FrameFlags: u16 {
/// The player's torso will obscure the player's legs.
OBSCURE = 13,
/// The bitmap will be flipped on the vertical axis.
FLIP_Y = 14,
/// The bitmap will be flipped on the horizontal axis.
FLIP_X = 15,
}
}
// EOF

View File

@ -1,86 +0,0 @@
//! Shapes file sequence type.
use crate::{bin::OptU16, err::*,
text::{mac_roman_conv, pascal_str},
xfer::TransferMode};
/// Reads a `Sequence`.
pub fn read(b: &[u8]) -> ResultS<Sequence>
{
read_data! {
endian: BIG, buf: b, size: 88, start: 0, data {
let name = u8[4; 34];
let v_type = u16[38] enum ViewType;
let frames = u16[40];
let ticks = u16[42];
let key = u16[44];
let xfer = u16[46] enum TransferMode;
let xfer_pd = u16[48];
let snd_beg = OptU16[50];
let snd_key = OptU16[52];
let snd_end = OptU16[54];
let loop_f = u16[58];
}
}
let name = mac_roman_conv(ok!(pascal_str(name), "bad string")?);
Ok(Sequence{name, v_type, frames, ticks, key, xfer, xfer_pd, snd_beg,
snd_key, snd_end, loop_f})
}
/// A sequence, also known as a high level shape.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Sequence {
/// The display name for this sequence.
pub name: String,
/// The view type for each frame in this sequence.
pub v_type: ViewType,
/// The number of frames in this sequence.
pub frames: u16,
/// The number of ticks each frame in this sequence takes.
pub ticks: u16,
/// The key frame index for this sequence.
pub key: u16,
/// The transfer mode to play over this sequence.
pub xfer: TransferMode,
/// The period in game ticks the transfer mode plays over.
pub xfer_pd: u16,
/// The sound to play at the beginning of this sequence.
pub snd_beg: OptU16,
/// The sound to play at the key frame of this sequence.
pub snd_key: OptU16,
/// The sound to play at the end of this sequence.
pub snd_end: OptU16,
/// Which frame to loop on.
pub loop_f: u16,
}
c_enum! {
/// The type of or number of views for a sequence.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum ViewType: u16 {
Anim = 1,
Anim8from2 = 2,
Anim4from3 = 3,
Anim4 = 4,
Anim8from5 = 5,
Anim8 = 8,
Anim5from3 = 9,
Still = 10,
Anim5 = 11,
}
}
// EOF

View File

@ -1,54 +0,0 @@
//! Marathon Sounds format handling.
pub mod defs;
pub mod snds;
use crate::{bin::Ident, err::*};
use std::{collections::BTreeMap, io::prelude::*};
/// Reads all sounds from a Sound file.
pub fn read(fp: &mut impl Read) -> ResultS<Vec<SoundTable>>
{
let mut b = Vec::new();
fp.read_to_end(&mut b)?;
read_data! {
endian: BIG, buf: &b, size: 260, start: 0, data {
let version = u32[0];
let magic = Ident[4];
let src_num = u16[8] usize;
let snd_num = u16[10] usize;
}
}
if version != 1 || magic != b"snd2" {
bail!("bad sound header");
}
let mut sc = Vec::with_capacity(src_num);
for i in 0..src_num {
let p = 260 + i * 64;
let mut st = SoundTable::new();
for _ in 0..snd_num {
if let Some((ofs, idx, mut def)) = defs::read(&b[p..p + 64])? {
for &ofs in &ofs {
def.sounds.push(snds::read(&b[ofs..])?);
}
st.insert(idx, def);
}
}
sc.push(st);
}
Ok(sc)
}
/// A table of sound definitions.
pub type SoundTable = BTreeMap<u16, defs::SoundDef>;
// EOF

View File

@ -1,96 +0,0 @@
//! Sounds format definition type.
use crate::{bin::{u32b, usize_from_u32}, err::*, fixed::*, sound::Sound16};
/// Reads a sound definition.
pub fn read(b: &[u8]) -> ResultS<Option<(Vec<usize>, u16, SoundDef)>>
{
read_data! {
endian: BIG, buf: b, size: 64, start: 0, data {
let index = u16[0];
let volume = u16[2] enum Volume;
let flags = u16[4] flag SoundFlags;
let chance = u16[6];
let pitch_lo = Fixed[8];
let pitch_hi = Fixed[12];
let n_sounds = u16[16] usize;
let grp_ofs = u32[20] usize;
}
}
if index == u16::max_value() {
return Ok(None);
}
if n_sounds > 5 {
bail!("too many sounds");
}
let mut ofs = Vec::with_capacity(n_sounds);
let mut p = 36;
for _ in 0..n_sounds {
ofs.push(grp_ofs + usize_from_u32(u32b(&b[p..])));
p += 4;
}
let sounds = Vec::with_capacity(n_sounds);
Ok(Some((ofs, index,
SoundDef{volume, flags, chance, pitch_lo, pitch_hi, sounds})))
}
/// A sound definition containing one, many or no sounds.
#[derive(Debug, Eq, PartialEq)]
pub struct SoundDef {
/// The volume type for this sound.
pub volume: Volume,
/// The flags for this sound.
pub flags: SoundFlags,
/// The chance out of `u16::max_value()` that this sound will not play.
pub chance: u16,
/// The low random pitch bound.
pub pitch_lo: Fixed,
/// The high random pitch bound.
pub pitch_hi: Fixed,
/// All of the sounds in this collection.
pub sounds: Vec<Sound16>,
}
c_bitfield! {
/// Flags for `SoundDef`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub struct SoundFlags: u16 {
/// The sound will not restart when trying to play over itself.
NO_RESTART = 0,
/// The sound will not switch channels when trying to play over itself.
NO_CHANNEL_SWITCH = 1,
/// The pitch variance will be halved.
LESS_PITCH_CHANGE = 2,
/// The pitch variance will be nullified.
NO_PITCH_CHANGE = 3,
/// The sound will play even when completely obstructed by walls.
NO_OBSTRUCTION = 4,
/// The sound will play even when completely obstructed by media.
NO_MEDIA_OBSTRUCT = 5,
/// The sound will have special stereo effects.
AMBIENT = 6,
}
}
c_enum! {
/// The type of volume this sound has.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum Volume: u16 {
Quiet = 0,
Normal = 1,
Loud = 2,
}
}
// EOF

View File

@ -1,47 +0,0 @@
//! Sounds file audio type.
use crate::{err::*, sound::Sound16};
/// Reads a sound.
pub fn read(b: &[u8]) -> ResultS<Sound16>
{
read_data! {
endian: BIG, buf: b, size: 21, start: 0, data {
let len = u32[4] usize;
let rate = u16[8];
let lp_beg = u32[12] usize;
let lp_end = u32[16] usize;
let magic = u8[20];
}
}
match magic {
0 => {
let stream = &b[22..22 + len];
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
}
0xFF => {
read_data! {
endian: BIG, buf: b, size: 42, start: 22, data {
let len = u32[0] usize;
let bps = u16[26];
}
}
match bps {
16 => {
let stream = &b[63..63 + len * 2];
Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream))
}
8 => {
let stream = &b[63..63 + len];
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
}
_ => bail!("bad bits per sample"),
}
}
_ => bail!("invalid magic number"),
}
}
// EOF

View File

@ -1,98 +0,0 @@
//! Sound representation.
pub mod wav;
/// Any PCM stream which may be represented as a 16-bit PCM stream.
pub trait Sound
{
/// Returns the sample rate.
fn rate(&self) -> u16;
/// Returns the number of samples.
fn len(&self) -> usize;
/// Returns the `n`th sample.
///
/// # Panics
///
/// Panics if `n` exceeds the length of this sound.
fn index(&self, n: usize) -> i16;
/// Returns the number of the first sample of the loop, or `0`.
fn lp_beg(&self) -> usize;
/// Returns the number of the last sample of the loop, or `0`.
fn lp_end(&self) -> usize;
/// Returns `true` if there are no samples in this sound.
fn is_empty(&self) -> bool {self.len() == 0}
/// The same as `index`, but will not panic if out of bounds.
fn get(&self, n: usize) -> Option<i16>
{
if n < self.len() {
Some(self.index(n))
} else {
None
}
}
}
impl Sound16
{
/// Creates a new `Sound16`.
pub fn new(rate: u16, lp_beg: usize, lp_end: usize, len: usize) -> Self
{
Self{rate, lp_beg, lp_end, data: Vec::with_capacity(len)}
}
/// Creates a new `Sound16` from an unsigned 8-bit stream.
pub fn new_from_8(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8]) -> Self
{
let mut snd = Self::new(rate, lp_beg, lp_end, b.len());
for &sample in b {
snd.data.push(Self::sample_from_8(sample));
}
snd
}
/// Creates a new `Sound16` from a signed 16-bit stream.
pub fn new_from_16(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8]) -> Self
{
let mut snd = Self::new(rate, lp_beg, lp_end, b.len() / 2);
for i in (0..b.len()).step_by(2) {
snd.data.push(i16::from_le_bytes([b[i], b[i + 1]]));
}
snd
}
/// Creates a signed 16-bit sample from an unsigned 8-bit sample.
pub fn sample_from_8(sample: u8) -> i16 {(i16::from(sample) - 0x80) << 8}
}
impl Sound for Sound16
{
fn rate(&self) -> u16 {self.rate}
fn len(&self) -> usize {self.data.len()}
fn index(&self, p: usize) -> i16 {self.data[p]}
fn lp_beg(&self) -> usize {self.lp_beg}
fn lp_end(&self) -> usize {self.lp_end}
}
/// A 16-bit PCM stream.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Sound16 {
rate: u16,
lp_beg: usize,
lp_end: usize,
/// The raw signed PCM data of this sound.
pub data: Vec<i16>,
}
// EOF

View File

@ -1,45 +0,0 @@
//! RIFF WAVE format sounds.
use crate::{err::*, sound::*};
use std::io;
/// Writes a WAVE file from a sound.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_wav(out: &mut impl io::Write, snd: &impl Sound) -> ResultS<()>
{
let smp_rate = u32::from(snd.rate());
let smp_size = smp_rate * 2;
let dat_size = snd.len() as u32 * 2;
out.write_all(b"RIFF")?;
out.write_all(&u32::to_le_bytes(36 + dat_size))?;
out.write_all(b"WAVE")?;
out.write_all(b"fmt ")?;
out.write_all(&u32::to_le_bytes(16))?;
// PCM
out.write_all(&u16::to_le_bytes(1))?;
// mono
out.write_all(&u16::to_le_bytes(1))?;
out.write_all(&u32::to_le_bytes(smp_rate))?;
out.write_all(&u32::to_le_bytes(smp_size))?;
// block alignment
out.write_all(&u16::to_le_bytes(2))?;
// bits per sample
out.write_all(&u16::to_le_bytes(16))?;
out.write_all(b"data")?;
out.write_all(&u32::to_le_bytes(dat_size))?;
for p in 0..snd.len() {
let sample = snd.index(p);
out.write_all(&sample.to_le_bytes())?;
}
Ok(())
}
// EOF

View File

@ -1,218 +0,0 @@
//! Text conversion utilities.
/// Formats a binary size string for any given number.
///
/// # Examples
///
/// ```
/// use maraiah::text::to_binsize;
///
/// assert_eq!(to_binsize(5000), "5kB".to_string());
/// ```
pub fn to_binsize(n: u64) -> String
{
const NAMES: [&str; 4] = ["kB", "MB", "GB", "TB"];
// empty size
if n == 0 {
return String::from("empty");
}
// terabytes, gigabytes, megabytes, kilobytes
for i in (1..=4).rev() {
let pow = 1000_u64.pow(i as u32);
if n >= pow {
return format!("{:1}{}", n / pow, NAMES[i - 1]);
}
}
// or, just bytes
format!("{} {}", n, if n == 1 {"byte"} else {"bytes"})
}
/// Encodes or decodes a string in the terminal encryption format.
pub fn fuck_string(s: &[u8]) -> Vec<u8>
{
let mut v = s.to_vec();
let l = s.len();
let mut p = 0;
for _ in 0..l / 4 {
p += 2;
v[p] ^= 0xfe;
v[p + 1] ^= 0xed;
p += 2;
}
for _ in 0..l % 4 {
v[p] ^= 0xfe;
p += 1;
}
v
}
/// Reads a Pascal-style byte string with bounds checking.
///
/// # Examples
///
/// ```
/// use maraiah::text::pascal_str;
///
/// assert_eq!(pascal_str(b"\x0bhello world"), b"hello world"[..].into());
/// assert_eq!(pascal_str(b"\x0chello world"), None);
/// assert_eq!(pascal_str(&[]), None);
/// ```
pub fn pascal_str(b: &[u8]) -> Option<&[u8]>
{
let s = usize::from(*b.get(0)?);
b.get(1..=s)
}
/// Converts input from Mac Roman to a Unicode string.
///
/// # Examples
///
/// ```
/// use maraiah::text::mac_roman_conv;
///
/// assert_eq!(mac_roman_conv(b"p\x8cth"), "påth");
/// assert_eq!(mac_roman_conv(b"I\xd5ve"), "Ive");
/// ```
pub fn mac_roman_conv(s: &[u8]) -> String
{
let mut v = String::with_capacity(s.len());
for &c in s.iter() {
let c = match c {
0x80..=0xFF => TR[usize::from(c) & 0x7F],
b'\r' => '\n',
c => char::from(c),
};
v.push(c);
}
v
}
/// Converts a C-style string from Mac Roman to Unicode.
///
/// # Examples
///
/// ```
/// use maraiah::text::mac_roman_cstr;
///
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0ed"), "Ive awaken");
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0"), "Ive awaken");
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken"), "Ive awaken");
/// ```
#[inline]
pub fn mac_roman_cstr(s: &[u8]) -> String
{
if let Some(n) = memchr::memchr(0, s) {
mac_roman_conv(&s[..n])
} else {
mac_roman_conv(s)
}
}
/// Converts input from a Unicode string to Mac Roman.
///
/// # Examples
///
/// ```
/// use maraiah::text::to_mac_roman;
///
/// assert_eq!(to_mac_roman("påth"), b"p\x8cth".to_vec());
/// assert_eq!(to_mac_roman("Ive\n"), b"I\xd5ve\r".to_vec());
/// ```
pub fn to_mac_roman(s: &str) -> Vec<u8>
{
let mut v = Vec::with_capacity(s.len());
for c in s.chars() {
let c = match c {
'\n' => b'\r',
'\0'..='\x7f' => c as u8,
c => {
if let Some(c) = TR.iter().position(|&o| o == c) {
c as u8 + 0x80
} else {
0x7f
}
}
};
v.push(c);
}
v
}
/// Pads the output with zeroes.
///
/// # Examples
///
/// ```
/// use maraiah::text::{pad_zero, to_mac_roman};
///
/// let s = to_mac_roman("påth");
///
/// assert_eq!(pad_zero(s.clone(), 8), b"p\x8cth\0\0\0\0".to_vec());
/// assert_eq!(pad_zero(s.clone(), 6), b"p\x8cth\0\0".to_vec());
/// assert_eq!(pad_zero(s.clone(), 4), b"p\x8cth".to_vec());
/// assert_eq!(pad_zero(s.clone(), 2), b"p\x8cth".to_vec());
/// assert_eq!(pad_zero(s.clone(), 0), b"p\x8cth".to_vec());
/// ```
pub fn pad_zero(mut v: Vec<u8>, n: usize) -> Vec<u8>
{
for _ in v.len()..n {
v.push(0);
}
v
}
const TR: [char; 128] =
['\u{00c4}', '\u{00c5}', '\u{00c7}', '\u{00c9}', '\u{00d1}', '\u{00d6}',
'\u{00dc}', '\u{00e1}', '\u{00e0}', '\u{00e2}', '\u{00e4}', '\u{00e3}',
'\u{00e5}', '\u{00e7}', '\u{00e9}', '\u{00e8}', '\u{00ea}', '\u{00eb}',
'\u{00ed}', '\u{00ec}', '\u{00ee}', '\u{00ef}', '\u{00f1}', '\u{00f3}',
'\u{00f2}', '\u{00f4}', '\u{00f6}', '\u{00f5}', '\u{00fa}', '\u{00f9}',
'\u{00fb}', '\u{00fc}', '\u{2020}', '\u{00b0}', '\u{00a2}', '\u{00a3}',
'\u{00a7}', '\u{2022}', '\u{00b6}', '\u{00df}', '\u{00ae}', '\u{00a9}',
'\u{2122}', '\u{00b4}', '\u{00a8}', '\u{2260}', '\u{00c6}', '\u{00d8}',
'\u{221e}', '\u{00b1}', '\u{2264}', '\u{2265}', '\u{00a5}', '\u{00b5}',
'\u{2202}', '\u{2211}', '\u{220f}', '\u{03c0}', '\u{222b}', '\u{00aa}',
'\u{00ba}', '\u{03a9}', '\u{00e6}', '\u{00f8}', '\u{00bf}', '\u{00a1}',
'\u{00ac}', '\u{221a}', '\u{0192}', '\u{2248}', '\u{2206}', '\u{00ab}',
'\u{00bb}', '\u{2026}', '\u{00a0}', '\u{00c0}', '\u{00c3}', '\u{00d5}',
'\u{0152}', '\u{0153}', '\u{2013}', '\u{2014}', '\u{201c}', '\u{201d}',
'\u{2018}', '\u{2019}', '\u{00f7}', '\u{25ca}', '\u{00ff}', '\u{0178}',
'\u{2044}', '\u{20ac}', '\u{2039}', '\u{203a}', '\u{fb01}', '\u{fb02}',
'\u{2021}', '\u{00b7}', '\u{201a}', '\u{201e}', '\u{2030}', '\u{00c2}',
'\u{00ca}', '\u{00c1}', '\u{00cb}', '\u{00c8}', '\u{00cd}', '\u{00ce}',
'\u{00cf}', '\u{00cc}', '\u{00d3}', '\u{00d4}', '\u{f8ff}', '\u{00d2}',
'\u{00da}', '\u{00db}', '\u{00d9}', '\u{0131}', '\u{02c6}', '\u{02dc}',
'\u{00af}', '\u{02d8}', '\u{02d9}', '\u{02da}', '\u{00b8}', '\u{02dd}',
'\u{02db}', '\u{02c7}'];
#[test]
fn to_binsize_integrals()
{
assert_eq!(to_binsize(0), "empty");
assert_eq!(to_binsize(1), "1 byte");
assert_eq!(to_binsize(2), "2 bytes");
assert_eq!(to_binsize(999), "999 bytes");
assert_eq!(to_binsize(1000), "1kB");
assert_eq!(to_binsize(1000 * 7), "7kB");
assert_eq!(to_binsize(1000 * 1000), "1MB");
assert_eq!(to_binsize(1000 * 1000 * 7), "7MB");
assert_eq!(to_binsize(1000 * 1000 * 1000), "1GB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 7), "7GB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 1000), "1TB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 1000 * 7), "7TB");
}
// EOF

View File

@ -1,38 +0,0 @@
//! Transfer Mode type.
impl Default for TransferMode
{
fn default() -> Self {TransferMode::Normal}
}
c_enum! {
/// A rendering style for many things.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
pub enum TransferMode: u16 {
Normal = 0,
FadeBlack = 1,
Invisibility = 2,
Invisibility2 = 3,
Pulsate = 4,
Wobble = 5,
Wobble2 = 6,
Static = 7,
Static2 = 8,
Sky = 9,
Smear = 10,
StaticFade = 11,
StaticPulse = 12,
FoldIn = 13,
FoldOut = 14,
SlideHorz = 15,
SlideHorz2 = 16,
SlideVert = 17,
SlideVert2 = 18,
Wander = 19,
Wander2 = 20,
BigSky = 21,
None = 0xFFFF,
}
}
// EOF

@ -1 +0,0 @@
Subproject commit 2a8664e9bfed74150c6a2df28c78f2d6b3f1a674

503
source/durandal/bin.rs Normal file
View File

@ -0,0 +1,503 @@
//! Binary data conversion utilities.
use crate::durandal::{err::*, text::mac_roman_conv};
use std::{fmt, num::NonZeroU16};
#[doc(hidden)]
#[macro_export]
macro_rules! _durandal_read_impl {
// big endian
(BE $b:expr; $nam:ident u16 $n:expr) => {
_durandal_read_impl!($b; u16::from_be_bytes, $nam 2 $n);
};
(BE $b:expr; $nam:ident i16 $n:expr) => {
_durandal_read_impl!($b; i16::from_be_bytes, $nam 2 $n);
};
(BE $b:expr; $nam:ident u32 $n:expr) => {
_durandal_read_impl!($b; u32::from_be_bytes, $nam 4 $n);
};
(BE $b:expr; $nam:ident i32 $n:expr) => {
_durandal_read_impl!($b; i32::from_be_bytes, $nam 4 $n);
};
// little endian
(LE $b:expr; $nam:ident u16 $n:expr) => {
_durandal_read_impl!($b; u16::from_le_bytes, $nam 2 $n);
};
(LE $b:expr; $nam:ident i16 $n:expr) => {
_durandal_read_impl!($b; i16::from_le_bytes, $nam 2 $n);
};
(LE $b:expr; $nam:ident u32 $n:expr) => {
_durandal_read_impl!($b; u32::from_le_bytes, $nam 4 $n);
};
(LE $b:expr; $nam:ident i32 $n:expr) => {
_durandal_read_impl!($b; i32::from_le_bytes, $nam 4 $n);
};
// either endianness
($e:ident $b:expr; $nam:ident Angle $n:expr) => {
_durandal_read_impl!($e $b; $nam u16 $n);
let $nam = Angle::from_bits($nam);
};
($e:ident $b:expr; $nam:ident Fixed $n:expr) => {
_durandal_read_impl!($e $b; $nam u32 $n);
let $nam = Fixed::from_bits($nam);
};
($e:ident $b:expr; $nam:ident Unit $n:expr) => {
_durandal_read_impl!($e $b; $nam u16 $n);
let $nam = Unit::from_bits($nam);
};
($e:ident $b:expr; $nam:ident OptU16 $n:expr) => {
_durandal_read_impl!($e $b; $nam u16 $n);
let $nam = OptU16::from_repr($nam);
};
($e:ident $b:expr; $nam:ident usize u16 $n:expr) => {
_durandal_read_impl!($e $b; $nam u16 $n);
let $nam = usize::from($nam);
};
($e:ident $b:expr; $nam:ident usize u32 $n:expr) => {
_durandal_read_impl!($e $b; $nam u32 $n);
let $nam = usize_from_u32($nam);
};
// no endianness
($_:ident $b:expr; $nam:ident u8 $n:expr) => {let $nam = $b[$n];};
($_:ident $b:expr; $nam:ident slice u8 $n:expr) => {let $nam = &$b[$n];};
($_:ident $b:expr; $nam:ident i8 $n:expr) => {
let $nam = i8::from_ne_bytes([$b[$n]]);
};
($_:ident $b:expr; $nam:ident Ident $n:expr) => {
let $nam = Ident([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]);
};
($_:ident $b:expr; $nam:ident $f:ident $n:expr) => {
let $nam = $f(&$b[$n])?;
};
($_:ident $b:expr; $nam:ident no_try $f:ident $n:expr) => {
let $nam = $f(&$b[$n]);
};
// worker - creates let statement
($b:expr; $pth:path , $nam:ident 2 $n:expr) => {
let $nam = $pth([$b[$n], $b[$n + 1]]);
};
($b:expr; $pth:path , $nam:ident 4 $n:expr) => {
let $nam = $pth([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]);
};
}
/// Reads structured data from a byte slice.
///
/// # Syntax
///
/// First start by specifying the endianness, size and source using the syntax
/// `endian, size in source =>` where:
///
/// - `endian` is `BE` or `LE` for big- or little-endian respectively.
/// - `size` is an expression specifying the last index that should be used by
/// this macro in `source`.
/// - `source` is a `u8` slice to read data from.
///
/// After the initializer line, all lines have the syntax
/// `name = type[place] opts;` where:
///
/// - `name` is the binding to put the resulting data in.
/// - `type` is one of:
/// - `u8` or `i8`: one byte will be read at `place`.
/// - `u16` or `i16`: two bytes will be read at `place` with `from_*_bytes`.
/// - `u32` or `i32`: four bytes will be read at `place` with `from_*_bytes`.
/// - `Ident`: four bytes will be read at `place` into an array, disregarding
/// endianness, creating an `Ident` object.
/// - `Angle`: same as `u16`, but the result is passed to
/// `fixed::Angle::from_bits`, resulting in a `fixed::Angle` object.
/// - `Fixed`: same as `u32`, but the result is passed to
/// `fixed::Fixed::from_bits`, resulting in a `fixed::Fixed` object.
/// - `Unit`: same as `u16`, but the result is passed to
/// `fixed::Unit::from_bits`, resulting in a `fixed::Unit` object.
/// - `OptU16`: same as `u16`, but the result is passed to
/// `OptU16::from_repr`, resulting in an `OptU16` object.
/// - The name of a function, which is passed `&source[place]` as its only
/// argument. The function's result has the `?` operator applied to it.
/// - `opts` may be one of:
/// - `slice` when `type` is `u8`: `place` is a range specifying a `u8` slice
/// to be taken from `source`.
/// - `usize` when `type` is `u16` or `u32`: converts the resulting integer to
/// `usize` by `usize_to_u32` for `u32` or by `from` for `u16`.
/// - `no_try` when `type` is a function name: does not use the `?` operator
/// on the resulting function call.
/// - Nothing at all.
/// - `place` is either an integer literal which must be representable as
/// `usize`, or a range, which may only be used when `type` is a function
/// name.
///
/// # Panics
///
/// This macro will not panic unless any index expression used exceeds or
/// equals `size`.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate maraiah;
/// # use maraiah::durandal::err::*;
/// # fn main() -> ResultS<()>
/// # {
/// let buffer = &[4, 0, 2, 0, 0, 0, 6];
///
/// read_data! {
/// 7, LE in buffer =>
/// four = u16[0];
/// two = u32[2];
/// six = u8 [6];
/// }
///
/// assert_eq!(four, 4_u16);
/// assert_eq!(two, 2_u32);
/// assert_eq!(six, 6_u8);
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! read_data {
(
$sz:expr , $ty:ident in $b:expr =>
$( $nam:ident = $t:ident [ $n:expr ] $( $ex:ident )* ; )*
) => {
$crate::check_data!($sz, $b);
$($crate::_durandal_read_impl!($ty $b; $nam $($ex)* $t $n);)*
};
}
/// Checks if there is enough data in `b`.
#[macro_export]
macro_rules! check_data {
(
$sz:expr , $b:expr
) => {
if $b.len() < $sz {
return Err(err_msg("not enough data"));
}
};
}
/// Casts a `u32` to a `usize`. For future compatibility.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::usize_from_u32;
///
/// assert_eq!(usize_from_u32(777u32), 777usize);
/// ```
#[inline]
pub const fn usize_from_u32(n: u32) -> usize {n as usize}
/// Creates an `Ident` from a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::{Ident, ident};
///
/// assert_eq!(ident(b"POLY"), Ident([b'P', b'O', b'L', b'Y']));
/// ```
#[inline]
pub const fn ident(b: &[u8]) -> Ident {Ident([b[0], b[1], b[2], b[3]])}
/// Applies `u32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::u32b;
///
/// assert_eq!(u32b(&[0x00, 0x0B, 0xDE, 0x31]), 777_777u32);
/// ```
pub fn u32b(b: &[u8]) -> u32 {u32::from_be_bytes([b[0], b[1], b[2], b[3]])}
/// Applies `u16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::u16b;
///
/// assert_eq!(u16b(&[0x1E, 0x61]), 7_777u16);
/// ```
pub fn u16b(b: &[u8]) -> u16 {u16::from_be_bytes([b[0], b[1]])}
/// Applies `i32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::i32b;
///
/// assert_eq!(i32b(&[0xFF, 0x89, 0x52, 0x0F]), -7_777_777i32);
/// ```
pub fn i32b(b: &[u8]) -> i32 {i32::from_be_bytes([b[0], b[1], b[2], b[3]])}
/// Applies `i16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::i16b;
///
/// assert_eq!(i16b(&[0xE1, 0x9F]), -7_777i16);
/// ```
pub fn i16b(b: &[u8]) -> i16 {i16::from_be_bytes([b[0], b[1]])}
/// Applies a read function over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When there is no
/// data left in `b`, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::{err::*, bin::{rd_array, u16b}};
///
/// fn read_a_u16(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))}
///
/// let inp = &[0x1E, 0x61, 0x03, 0x09];
/// assert_eq!(rd_array(inp, read_a_u16).unwrap(), vec![7_777u16, 777u16]);
/// ```
pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
{
let mut v = Vec::new();
let mut p = 0;
while p < b.len() {
let (r, s) = read(&b[p..])?;
v.push(r);
p += s;
}
Ok(v)
}
/// Applies a read function a number of times over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When `n` elements
/// have been read, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_array_num<T, F>(b: &[u8], n: usize, read: F)
-> ResultS<(Vec<T>, usize)>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
{
let mut v = Vec::with_capacity(n);
let mut p = 0;
for _ in 0..n {
let (r, s) = read(&b[p..])?;
v.push(r);
p += s;
}
Ok((v, p))
}
/// Applies a read function over a slice with an offset table.
///
/// Applies `read` over each offset in `b`, of which there are `num` amount of
/// starting at `p`, resulting in a vector of its return values. Each iteration
/// reads a 32-bit big endian offset from `b`, and then passes a slice of `b`
/// to `read` starting at that offset. When all offsets have been read, the
/// function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function panics.
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_ofstable<T, F>(b: &[u8],
mut p: usize,
num: usize,
read: F)
-> ResultS<Vec<T>>
where T: Sized,
F: Fn(&[u8]) -> ResultS<T>
{
let mut v = Vec::with_capacity(num);
for _ in 0..num {
let ofs = usize_from_u32(u32b(&b[p..p + 4]));
check_data!(ofs, b);
v.push(read(&b[ofs..])?);
p += 4;
}
Ok(v)
}
impl OptU16
{
/// Creates an `OptU16` representing `None`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// assert_eq!(OptU16::none(), OptU16::from_repr(u16::max_value()));
/// ```
pub const fn none() -> Self {Self(None)}
/// Creates an `OptU16` from a `u16`.
pub fn from_repr(n: u16) -> Self
{
if n == u16::max_value() {
Self(None)
} else {
Self(NonZeroU16::new(n + 1))
}
}
/// Returns the `u16` representation.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// let u16max = u16::max_value();
///
/// assert_eq!(OptU16::from_repr(500u16).get_repr(), 500u16);
/// assert_eq!(OptU16::from_repr(u16max).get_repr(), u16max);
/// assert_eq!(OptU16::from_repr(0u16).get_repr(), 0u16);
/// ```
pub fn get_repr(self) -> u16
{
match self.0 {
None => u16::max_value(),
Some(n) => n.get() - 1,
}
}
/// Returns the `Option` representation.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// assert_eq!(OptU16::from_repr(500u16).get(), Some(500u16));
/// assert_eq!(OptU16::from_repr(u16::max_value()).get(), None);
/// assert_eq!(OptU16::from_repr(0u16).get(), Some(0u16));
/// ```
pub fn get(self) -> Option<u16>
{
match self.0 {
None => None,
Some(n) => Some(n.get() - 1),
}
}
}
impl fmt::Debug for OptU16
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self.get() {
None => write!(f, "None"),
Some(n) => write!(f, "Some({})", n),
}
}
}
impl fmt::Debug for Ident
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "\"{}\"", mac_roman_conv(&self.0))
}
}
impl PartialEq<[u8; 4]> for Ident
{
#[inline]
fn eq(&self, o: &[u8; 4]) -> bool {self.0 == *o}
}
impl<'a> PartialEq<[u8; 4]> for &'a Ident
{
#[inline]
fn eq(&self, o: &[u8; 4]) -> bool {PartialEq::eq(*self, o)}
}
impl<'a> PartialEq<&'a [u8; 4]> for Ident
{
#[inline]
fn eq(&self, o: & &'a [u8; 4]) -> bool {PartialEq::eq(self, *o)}
}
/// A four-character-code identifier.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::Ident;
///
/// assert_eq!( Ident(*b"POLY").0, *b"POLY");
/// assert_eq!( Ident(*b"POLY"), *b"POLY");
/// assert_eq!( Ident(*b"POLY"), b"POLY");
/// assert_eq!(&Ident(*b"POLY"), *b"POLY");
/// assert_eq!(&Ident(*b"POLY"), b"POLY");
/// ```
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]);
/// An object identified by a `u16` which may be `u16::max_value()` to
/// represent a nulled value.
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct OptU16(Option<NonZeroU16>);
// EOF

162
source/durandal/bit.rs Normal file
View File

@ -0,0 +1,162 @@
//! Bit streams.
use crate::durandal::err::*;
/// Reads `width` bits from `b` starting at bit `cr_bit` into an integer, most
/// significant bit first.
///
/// # Errors
///
/// The function will return an error if `width` is more than 64, or if
/// `(cr_bit + width - 1) / 8` would overflow an index into `b`.
pub fn read_bits_b(b: &[u8], cr_bit: usize, width: u8) -> ResultS<u64>
{
if width == 0 {
return Ok(0);
}
if width > 64 {
bail!("invalid number of bits");
}
let last = (cr_bit + usize::from(width) - 1) / 8;
if last >= b.len() {
bail!("not enough data");
}
let mut byte_ptr = cr_bit / 8;
let mut bits_ptr = cr_bit % 8;
let mut res = 0;
for _ in 0..width {
res <<= 1;
if b[byte_ptr] & (1 << bits_ptr) != 0 {
res |= 1;
}
bits_ptr += 1;
if bits_ptr > 7 {
bits_ptr = 0;
byte_ptr += 1;
}
}
Ok(res)
}
/// The same as `read_bits_b`, but least-significant bit first.
pub fn read_bits_l(b: &[u8], cr_bit: usize, width: u8) -> ResultS<u64>
{
if width == 0 {
Ok(0)
} else {
let res = read_bits_b(b, cr_bit, width)?;
Ok(reverse_bits(res) >> (64 - width))
}
}
fn reverse_bits(v: u64) -> u64
{
// TODO: change this to u64::reverse_bits when stabilized
let v = v >> 1 & 0x5555_5555_5555_5555 | ((v & 0x5555_5555_5555_5555) << 1);
let v = v >> 2 & 0x3333_3333_3333_3333 | ((v & 0x3333_3333_3333_3333) << 2);
let v = v >> 4 & 0x0F0F_0F0F_0F0F_0F0F | ((v & 0x0F0F_0F0F_0F0F_0F0F) << 4);
v.swap_bytes()
}
#[test]
fn bit_tests()
{
const INPUT: &[u8] = &[0b01100101, 0b10101010, 0b00010000, 0b00000000,
0b11111111, 0b11100001, 0b10101100, 0b00110011,
0b10100101, 0b11100000, 0b00000111, 0b00000001,
0b11001010, 0b10101111, 0b00101011, 0b01101010,
0b11010101, 0b10100011, 0b01010101, 0b11000001];
let mut p = 0;
let n = read_bits_b(INPUT, p, 3).unwrap();
assert_eq!(n, 0b101);
p += 3;
let n = read_bits_b(INPUT, p, 63).unwrap();
assert_eq!(n, 0b001100101010100001000000000001111111110000111001101011100110010);
p += 63;
let n = read_bits_b(INPUT, p, 4).unwrap();
assert_eq!(n, 0b1001);
p += 4;
let n = read_bits_b(INPUT, p, 7).unwrap();
assert_eq!(n, 0b0100000);
p += 7;
let n = read_bits_b(INPUT, p, 17).unwrap();
assert_eq!(n, 0b11111100000100000);
p += 17;
let n = read_bits_b(INPUT, p, 27).unwrap();
assert_eq!(n, 0b000101001111110101110101000);
p += 27;
let n = read_bits_b(INPUT, p, 33).unwrap();
assert_eq!(n, 0b101011010101011110001011010101010);
p += 33;
let n = read_bits_b(INPUT, p, 6).unwrap();
assert_eq!(n, 0b000011);
p += 6;
let e = read_bits_b(INPUT, p, 1);
assert!(if let Err(_) = e {true} else {false});
let e = read_bits_b(INPUT, p, 2);
assert!(if let Err(_) = e {true} else {false});
let mut p = 0;
let n = read_bits_l(INPUT, 0, 3).unwrap();
assert_eq!(n, 0b101);
p += 3;
let n = read_bits_l(INPUT, p, 63).unwrap();
assert_eq!(n, 0b010011001110101100111000011111111100000000000100001010101001100);
p += 63;
let n = read_bits_l(INPUT, p, 4).unwrap();
assert_eq!(n, 0b1001);
p += 4;
let n = read_bits_l(INPUT, p, 7).unwrap();
assert_eq!(n, 0b0000010);
p += 7;
let n = read_bits_l(INPUT, p, 17).unwrap();
assert_eq!(n, 0b00000100000111111);
p += 17;
let n = read_bits_l(INPUT, p, 27).unwrap();
assert_eq!(n, 0b000101011101011111100101000);
p += 27;
let n = read_bits_l(INPUT, p, 33).unwrap();
assert_eq!(n, 0b010101010110100011110101010110101);
p += 33;
let n = read_bits_l(INPUT, p, 6).unwrap();
assert_eq!(n, 0b110000);
p += 6;
let e = read_bits_l(INPUT, p, 1);
assert!(if let Err(_) = e {true} else {false});
let e = read_bits_l(INPUT, p, 2);
assert!(if let Err(_) = e {true} else {false});
}
// EOF

105
source/durandal/cenum.rs Normal file
View File

@ -0,0 +1,105 @@
#![doc(hidden)]
/// Creates an enumeration and function for converting a representation into
/// the enumeration type.
///
/// The syntax is similar to the `bitflags` macro, but each value has the
/// syntax `value => enumeration`. `enum` is used instead of `struct`.
///
/// This will generate an `enum $t` as well as a function `$t::from_repr` which
/// will return `Result<$t, ReprError>`.
///
/// # Examples
///
/// ```
/// use maraiah::{c_enum, durandal::err::ReprError};
///
/// c_enum! {
/// #[derive(Debug)]
/// enum MyEnum: u16
/// {
/// 0 => Zero,
/// 1 => One,
/// 2 => Two,
/// }
/// }
///
/// assert_eq!(MyEnum::from_repr(0), Ok(MyEnum::Zero));
/// assert_eq!(MyEnum::from_repr(1), Ok(MyEnum::One));
/// assert_eq!(MyEnum::from_repr(2), Ok(MyEnum::Two));
/// assert_eq!(MyEnum::from_repr(3), Err(ReprError::new(3)));
/// assert_eq!(MyEnum::from_repr(4), Err(ReprError::new(4)));
/// assert_eq!(MyEnum::from_repr(5), Err(ReprError::new(5)));
/// ```
#[macro_export]
macro_rules! c_enum
{
(
$(#[$outer:meta])*
$vi:vis enum $t:ident: $ti:ty
{
$($va:expr => $en:ident,)+
}
) => {
$(#[$outer])*
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
$vi enum $t
{
$($en,)+
}
#[allow(dead_code)]
impl $t
{
/// Returns, if representable, the variant of `Self` from `n`.
$vi fn from_repr(n: $ti) -> Result<Self, ReprError>
{
match n {
$($va => Ok($t::$en),)+
n => Err(ReprError::new(n))
}
}
/// Returns the representation of this type.
$vi fn to_repr(self) -> $ti
{
match self {
$($t::$en => $va,)+
}
}
}
};
}
#[cfg(test)]
mod test
{
use crate::durandal::err::ReprError;
c_enum! {
#[derive(Debug)]
enum TestEnum: u16
{
0 => Zero,
1 => One,
2 => Two,
}
}
#[test]
fn c_enum_should_be_ok()
{
assert_eq!(TestEnum::from_repr(0), Ok(TestEnum::Zero));
assert_eq!(TestEnum::from_repr(1), Ok(TestEnum::One));
assert_eq!(TestEnum::from_repr(2), Ok(TestEnum::Two));
assert_eq!(TestEnum::from_repr(3), Err(ReprError::new(3)));
assert_eq!(TestEnum::from_repr(4), Err(ReprError::new(4)));
assert_eq!(TestEnum::from_repr(5), Err(ReprError::new(5)));
}
#[test]
#[should_panic]
fn c_enum_should_error() {TestEnum::from_repr(3).unwrap();}
}
// EOF

63
source/durandal/cksum.rs Normal file
View File

@ -0,0 +1,63 @@
//! Cyclic redundancy check function.
fn crc_accum(a: u32, _: u32) -> u32
{
if a & 1 == 1 {
ISO_3309_POLYNOMIAL ^ a >> 1
} else {
a >> 1
}
}
fn crc_init() -> [u32; 256]
{
let mut t = [0; 256];
for (n, v) in t.iter_mut().enumerate() {
*v = (0..8).fold(n as u32, crc_accum);
}
t
}
/// Creates an ADLER32 of all bytes in `b`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::cksum::adler32;
///
/// assert_eq!(adler32(b"Lorem ipsum dolor sit amet"), 0x83D5_09C5);
/// ```
pub fn adler32(b: &[u8]) -> u32
{
let mut x = 1;
let mut y = 0;
for &z in b {
let z = u32::from(z);
x = (x + z) % ADLER32_MODULO;
y = (y + x) % ADLER32_MODULO;
}
(y << 16) | x
}
/// Creates a CRC-32 of all bytes in `b` with the starting sum `s`. The
/// polynomial used is the one from ISO-3309.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::cksum::crc32;
///
/// assert_eq!(crc32(b"Lorem ipsum dolor sit amet", !0), 0x5F29_D461);
/// ```
pub fn crc32(b: &[u8], s: u32) -> u32
{
let t = crc_init();
!b.iter().fold(s, |a, &o| a >> 8 ^ t[usize::from(a as u8 ^ o)])
}
const ISO_3309_POLYNOMIAL: u32 = 0xEDB8_8320;
const ADLER32_MODULO: u32 = 65521;
// EOF

99
source/durandal/err.rs Normal file
View File

@ -0,0 +1,99 @@
//! Error handling.
pub use failure::{Error, Fail};
use std::fmt;
macro_rules! ok {
($v:expr, $msg:expr) => {
match $v {
Some(v) => Ok(v),
None => Err(err_msg($msg)),
}
};
}
macro_rules! flag_ok {
($t:ident, $v:expr) => {
match $t::from_bits($v) {
Some(v) => Ok(v),
None => Err(err_msg(concat!("bad ", stringify!($t)))),
}
};
}
macro_rules! bail {
($e:expr) => {
return Err(err_msg($e));
};
}
/// Returns an `Error` with a static string.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::err::err_msg;
///
/// assert_eq!(format!("{}", err_msg("oh no not things")), "oh no not things");
/// ```
pub fn err_msg(msg: &'static str) -> Error {ErrMsg(msg).into()}
/// Returns an `Error` from a `ReprError`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::err::repr_error;
///
/// assert_eq!(format!("{}", repr_error(77)), "representation error (got 77)");
/// ```
pub fn repr_error<T: Into<i64>>(n: T) -> Error {ReprError::new(n).into()}
impl ReprError
{
/// Creates a new `ReprError`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::err::ReprError;
///
/// let err = ReprError::new(7);
///
/// assert_eq!(format!("{}", err), "representation error (got 7)");
/// ```
#[inline]
pub fn new<T: Into<i64>>(n: T) -> Self {Self(n.into())}
}
impl Fail for ReprError {}
impl Fail for ErrMsg {}
impl fmt::Display for ReprError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "representation error (got {})", self.0)
}
}
impl fmt::Display for ErrMsg
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
f.write_str(self.0)
}
}
/// A representation error for an integer.
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ReprError(i64);
#[derive(Debug)]
struct ErrMsg(&'static str);
/// A generic `failure` based `Result` type.
pub type ResultS<T> = Result<T, Error>;
// EOF

17
source/durandal/file.rs Normal file
View File

@ -0,0 +1,17 @@
//! File utilities.
use crate::durandal::err::*;
use std::fs;
/// Confirms that the path `p` is a folder.
pub fn validate_folder_path(p: &str) -> ResultS<()>
{
let at = fs::metadata(p)?;
if at.is_dir() {
Ok(())
} else {
Err(err_msg("not a directory"))
}
}
// EOF

453
source/durandal/fixed.rs Normal file
View File

@ -0,0 +1,453 @@
//! Fixed point numbers.
#![allow(clippy::use_self)]
use std::{fmt::{self, Write}, ops::*};
macro_rules! fixed_ref_unop {
(impl $imp:ident, $method:ident for $t:ty) => {
impl $imp for &$t
{
type Output = <$t as $imp>::Output;
#[inline]
fn $method(self) -> <$t as $imp>::Output {$imp::$method(*self)}
}
};
}
macro_rules! fixed_ref_binop {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<$u> for &'a $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, o)
}
}
impl<'a> $imp<&'a $u> for $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(self, *o)
}
}
impl<'a, 'b> $imp<&'a $u> for &'b $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, o: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, *o)
}
}
};
}
macro_rules! fixed_ref_op_assign {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<&'a $u> for $t
{
#[inline]
fn $method(&mut self, o: &'a $u) {$imp::$method(self, *o);}
}
};
}
macro_rules! define_fixed_types {
(
$(
$(#[$outer:meta])*
struct $t:ident ( $ti:ident, $bytes:expr ) :
$tu:ident, $tb:ident, $frac_bits:expr; $test:ident
)*
) => {$(
$(#[$outer])*
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct $t($ti);
impl $t
{
/// The number of fractional bits in this type.
#[inline]
pub const fn frac_bits() -> $tu {$frac_bits}
/// The unsigned mask for the fractional bits.
#[inline]
pub const fn frac_mask() -> $tu {(1 << $t::frac_bits()) - 1}
/// The integer mask for the fractional bits.
#[inline]
pub const fn frac_mask_i() -> $ti {(1 << $t::frac_bits()) - 1}
/// The representation of `1.0` in this type.
#[inline]
pub const fn one() -> $tu {1 << $t::frac_bits()}
/// Returns the largest value that can be represented.
#[inline]
pub const fn max_value() -> $t {$t($ti::max_value())}
/// Returns the smallest value that can be represented.
#[inline]
pub const fn min_value() -> $t {$t($ti::min_value())}
/// Returns the integer part of a number.
#[inline]
pub const fn trunc(self) -> $t {$t(self.0 & !$t::frac_mask_i())}
/// Returns the fractional part of a number.
#[inline]
pub const fn fract(self) -> $t {$t(self.0 & $t::frac_mask_i())}
/// Returns the integer part of a number as an integer.
#[inline]
pub const fn integ(self) -> $ti {self.0 >> $t::frac_bits()}
/// Returns the number of ones in the bit representation of self.
#[inline]
pub const fn count_ones(self) -> u32 {self.0.count_ones()}
/// Returns the number of zeros in the bit representation of self.
#[inline]
pub const fn count_zeros(self) -> u32 {self.0.count_zeros()}
/// Returns the number of leading zeros in the bit representation of
/// self.
#[inline]
pub const fn leading_zeros(self) -> u32 {self.0.leading_zeros()}
/// Returns the number of trailing zeros in the bit representation of
/// self.
#[inline]
pub const fn trailing_zeros(self) -> u32 {self.0.trailing_zeros()}
/// Rotates all bits left by `n`.
#[inline]
pub const fn rotate_left(self, n: u32) -> $t
{
$t(self.0.rotate_left(n))
}
/// Rotates all bits right by `n`.
#[inline]
pub const fn rotate_right(self, n: u32) -> $t
{
$t(self.0.rotate_right(n))
}
/// Reverses the byte order of the bit representation of self.
#[inline]
pub const fn swap_bytes(self) -> $t {$t(self.0.swap_bytes())}
/// Raises self to the power of `exp`.
#[inline]
pub fn pow(self, exp: u32) -> $t {$t(self.0.pow(exp))}
/// Returns the absolute value of self.
#[inline]
pub fn abs(self) -> $t {$t(self.0.abs())}
/// Returns a number representing sign of self.
#[inline]
pub fn signum(self) -> $t {$t(self.0.signum() << $t::frac_bits())}
/// Returns true if self is positive and false if the number is zero
/// or negative.
#[inline]
pub const fn is_positive(self) -> bool {self.0.is_positive()}
/// Returns true if self is negative and false if the number is zero
/// or positive.
#[inline]
pub const fn is_negative(self) -> bool {self.0.is_negative()}
/// Return the memory representation of this integer as a byte array
/// in big-endian (network) byte order.
#[inline]
pub fn to_be_bytes(self) -> [u8; $bytes] {self.0.to_be_bytes()}
/// Return the memory representation of this integer as a byte array
/// in little-endian byte order.
#[inline]
pub fn to_le_bytes(self) -> [u8; $bytes] {self.0.to_le_bytes()}
/// Create a value from its representation as a byte array in
/// big-endian byte order.
#[inline]
pub fn from_be_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_be_bytes(b))
}
/// Create a value from its representation as a byte array in
/// little-endian byte order.
#[inline]
pub fn from_le_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_le_bytes(b))
}
/// Creates a value of this type with the bit pattern `bits`.
#[inline]
pub const fn from_bits(bits: $tu) -> $t {$t(bits as $ti)}
/// Creates a value of this type with the integral portion `n`.
#[inline]
pub const fn from_int(n: $ti) -> $t {$t(n << $t::frac_bits())}
/// Returns the raw bit pattern.
#[inline]
pub const fn to_bits(self) -> $tu {self.0 as $tu}
/// Sets the raw bit pattern to `bits`.
#[inline]
pub fn set_bits(&mut self, bits: $tu) {self.0 = bits as $ti}
#[inline]
const fn mul_i(x: $ti, y: $ti) -> $ti {x * y}
#[inline]
const fn div_i(x: $ti, y: $ti) -> $ti {x / y}
#[inline]
fn div_k(x: $ti, y: $ti) -> $ti
{
($tb::from(x) * $tb::from($t::one()) / $tb::from(y)) as $ti
}
#[inline]
fn mul_k(x: $ti, y: $ti) -> $ti
{
($tb::from(x) * $tb::from(y) / $tb::from($t::one())) as $ti
}
}
#[cfg(test)]
mod $test {
use super::$t;
#[test]
fn basic_ops()
{
let one = $t::one();
let two = 2 << $t::frac_bits();
let twelve = 12 << $t::frac_bits();
assert_eq!(($t::from(1) + $t::from(1)).to_bits(), two);
assert_eq!(($t::from(2) - $t::from(1)).to_bits(), one);
assert_eq!(($t::from(6) * $t::from(2)).to_bits(), twelve);
assert_eq!(($t::from(6) * 2) .to_bits(), twelve);
}
#[test]
fn fractions()
{
let three_pt_5 = 3 << $t::frac_bits() | $t::one() / 2;
let one_pt_2 = 1 << $t::frac_bits() | $t::one() / 5;
let two_pt_4 = one_pt_2 * 2;
assert_eq!(($t::from(7) / $t::from(2)) .to_bits(), three_pt_5);
assert_eq!(($t::from(7) / 2) .to_bits(), three_pt_5);
assert_eq!(($t::from_bits(one_pt_2) * 2).to_bits(), two_pt_4);
}
#[test]
fn printing()
{
assert_eq!(format!("{}", $t::from(6)), "6.0");
assert_eq!(format!("{:2.3}", $t::from(7) / 2), " 3.500");
}
}
impl From<$ti> for $t
{
#[inline]
fn from(n: $ti) -> $t {$t::from_int(n)}
}
impl Add<$t> for $t
{
type Output = $t;
#[inline]
fn add(self, o: $t) -> $t {$t(self.0 + o.0)}
}
fixed_ref_binop! {impl Add, add for $t, $t}
impl Sub<$t> for $t
{
type Output = $t;
#[inline]
fn sub(self, o: $t) -> $t {$t(self.0 - o.0)}
}
fixed_ref_binop! {impl Sub, sub for $t, $t}
impl Mul<$t> for $t
{
type Output = $t;
#[inline]
fn mul(self, o: $t) -> $t {$t($t::mul_k(self.0, o.0))}
}
fixed_ref_binop! {impl Mul, mul for $t, $t}
impl Mul<$ti> for $t
{
type Output = $t;
#[inline]
fn mul(self, o: $ti) -> $t {$t($t::mul_i(self.0, o))}
}
fixed_ref_binop! {impl Mul, mul for $t, $ti}
impl Div<$t> for $t
{
type Output = $t;
#[inline]
fn div(self, o: $t) -> $t {$t($t::div_k(self.0, o.0))}
}
fixed_ref_binop! {impl Div, div for $t, $t}
impl Div<$ti> for $t
{
type Output = $t;
#[inline]
fn div(self, o: $ti) -> $t {$t($t::div_i(self.0, o))}
}
fixed_ref_binop! {impl Div, div for $t, $ti}
impl Neg for $t
{
type Output = $t;
#[inline]
fn neg(self) -> $t {$t(-self.0)}
}
fixed_ref_unop! {impl Neg, neg for $t}
impl Not for $t
{
type Output = $t;
#[inline]
fn not(self) -> $t {$t(!self.0)}
}
fixed_ref_unop! {impl Not, not for $t}
impl AddAssign for $t
{
#[inline]
fn add_assign(&mut self, o: $t) {self.0 += o.0}
}
fixed_ref_op_assign! {impl AddAssign, add_assign for $t, $t}
impl SubAssign for $t
{
#[inline]
fn sub_assign(&mut self, o: $t) {self.0 -= o.0}
}
fixed_ref_op_assign! {impl SubAssign, sub_assign for $t, $t}
impl MulAssign for $t
{
#[inline]
fn mul_assign(&mut self, o: $t) {self.0 = (*self * o).0}
}
fixed_ref_op_assign! {impl MulAssign, mul_assign for $t, $t}
impl DivAssign for $t
{
#[inline]
fn div_assign(&mut self, o: $t) {self.0 = (*self / o).0}
}
fixed_ref_op_assign! {impl DivAssign, div_assign for $t, $t}
impl fmt::Display for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
let prec = f.precision().unwrap_or(1);
let widt = f.width().unwrap_or(0);
write!(f, "{:widt$}.", self.0 >> $t::frac_bits(), widt = widt)?;
let mut k = self.to_bits();
for _ in 0..prec {
k &= $t::frac_mask();
k *= 10;
let d = k >> $t::frac_bits();
let d = d % 10;
f.write_char(char::from(d as u8 + b'0'))?;
}
Ok(())
}
}
impl fmt::Debug for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
fmt::Display::fmt(self, f)
}
}
)*};
}
define_fixed_types! {
/// A fixed point type representing an angle.
///
/// The format of this type is `0.9s`, but because of the implementation,
/// the real format is `7.9s`.
struct Angle(i16, 2) : u16, i32, 9; angle_tests
/// A fixed point type representing a world unit.
///
/// The format of this type is `5.10s`. This has caused eternal suffering.
struct Unit(i16, 2) : u16, i32, 10; unit_tests
/// A generic fixed point type.
///
/// The format of this type is `15.16s`.
struct Fixed(i32, 4) : u32, i64, 16; fixed_tests
/// A generic, long fixed point type.
///
/// The format of this type is `31.32s`.
struct FixedLong(i64, 8) : u64, i128, 32; fixed_long_tests
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn fixed_overflow() {Fixed::from(i16::max_value() as i32) + Fixed::from(1);}
// EOF

255
source/durandal/image.rs Normal file
View File

@ -0,0 +1,255 @@
//! Image and color representations.
/// Creates a RGB8 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image::{Color8, r5g5b5_to_rgb8};
///
/// assert_eq!(r5g5b5_to_rgb8(0x2345), Color8::new(64, 208, 40));
/// ```
pub fn r5g5b5_to_rgb8(rgb: u16) -> Color8
{
let r = (rgb >> 10) as u8 & 0x1f;
let g = (rgb >> 5) as u8 & 0x1f;
let b = rgb as u8 & 0x1f;
Color8::new(r << 3, g << 3, b << 3)
}
/// Creates a RGB16 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image::{Color16, r5g5b5_to_rgb16};
///
/// assert_eq!(r5g5b5_to_rgb16(0x2345), Color16::new(0x4000, 0xD000, 0x2800));
/// ```
pub fn r5g5b5_to_rgb16(rgb: u16) -> Color16
{
let r = rgb >> 10 & 0x1f;
let g = rgb >> 5 & 0x1f;
let b = rgb & 0x1f;
Color16::new(r << 11, g << 11, b << 11)
}
/// Creates a RGB16 color from a RGB8 format color.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image;
///
/// let inpl = image::r5g5b5_to_rgb8(0x2345);
/// let inph = image::r5g5b5_to_rgb16(0x2345);
///
/// assert_eq!(inph, image::rgb8_to_rgb16(inpl));
/// ```
pub fn rgb8_to_rgb16(cr: Color8) -> Color16
{
Color16::new(cr.r(), cr.g(), cr.b())
}
/// A generic color matrix image.
pub trait Image
{
/// The type of color this image uses for each pixel.
type Output: Color;
/// Returns the width of the image.
fn w(&self) -> usize;
/// Returns the height of the image.
fn h(&self) -> usize;
/// Returns the color of the pixel at column `x` at row `y`.
///
/// # Panics
///
/// Panics if `x` is greater than the width of the image or `y` is greater
/// than the height of the image.
fn index(&self, x: usize, y: usize) -> &Self::Output;
/// The same as `index`, but will not panic if out of bounds.
fn get(&self, x: usize, y: usize) -> Option<&Self::Output>
{
if x < self.w() && y < self.h() {
Some(self.index(x, y))
} else {
None
}
}
}
/// A generic color matrix image, which may be mutated.
pub trait ImageMut: Image
{
/// Returns the color of the pixel at column `x` at row `y`.
///
/// # Panics
///
/// Panics if `x` is greater than the width of the image or `y` is greater
/// than the height of the image.
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output;
/// The same as `index_mut`, but will not panic if out of bounds.
fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Self::Output>
{
if x < self.w() && y < self.h() {
Some(self.index_mut(x, y))
} else {
None
}
}
}
/// Any color which may be represented as RGBA16.
pub trait Color: Sized + Copy + Clone + Eq + PartialEq
{
/// Returns the red component.
fn r(&self) -> u16;
/// Returns the green component.
fn g(&self) -> u16;
/// Returns the blue component.
fn b(&self) -> u16;
/// Returns the alpha component.
fn a(&self) -> u16;
}
impl Image16
{
/// Creates a new Image16 with no canvas.
pub fn new(w: usize, h: usize) -> Self
{
Self{w, h, cr: Vec::with_capacity(w * h)}
}
/// Creates a new Image16 with an empty canvas.
pub fn new_empty(w: usize, h: usize) -> Self
{
Self{w, h, cr: vec![Color16::new(0, 0, 0); w * h]}
}
}
impl Image for Image16
{
type Output = Color16;
fn w(&self) -> usize {self.w}
fn h(&self) -> usize {self.h}
fn index(&self, x: usize, y: usize) -> &Self::Output
{
&self.cr[x + y * self.w]
}
}
impl ImageMut for Image16
{
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output
{
&mut self.cr[x + y * self.w]
}
}
impl Image8
{
/// Creates a new Image8 with no canvas.
pub fn new(w: usize, h: usize) -> Self
{
Self{w, h, cr: Vec::with_capacity(w * h)}
}
/// Creates a new Image8 with an empty canvas.
pub fn new_empty(w: usize, h: usize) -> Self
{
Self{w, h, cr: vec![Color8::new(0, 0, 0); w * h]}
}
}
impl Image for Image8
{
type Output = Color8;
fn w(&self) -> usize {self.w}
fn h(&self) -> usize {self.h}
fn index(&self, x: usize, y: usize) -> &Self::Output
{
&self.cr[x + y * self.w]
}
}
impl ImageMut for Image8
{
fn index_mut(&mut self, x: usize, y: usize) -> &mut Self::Output
{
&mut self.cr[x + y * self.w]
}
}
impl Color16
{
pub const fn new(r: u16, g: u16, b: u16) -> Self {Self(r, g, b)}
}
impl Color for Color16
{
fn r(&self) -> u16 {self.0}
fn g(&self) -> u16 {self.1}
fn b(&self) -> u16 {self.2}
fn a(&self) -> u16 {u16::max_value()}
}
impl Color8
{
pub const fn new(r: u8, g: u8, b: u8) -> Self {Self(r, g, b)}
}
impl Color for Color8
{
fn r(&self) -> u16 {u16::from(self.0) << 8}
fn g(&self) -> u16 {u16::from(self.1) << 8}
fn b(&self) -> u16 {u16::from(self.2) << 8}
fn a(&self) -> u16 {u16::max_value()}
}
/// An RGB16 color.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Color16(u16, u16, u16);
/// An RGB8 color.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Color8(u8, u8, u8);
/// An RGB16 image.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Image16
{
w: usize,
h: usize,
/// The raw color data for this image.
pub cr: Vec<Color16>,
}
/// An RGB8 image.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Image8
{
w: usize,
h: usize,
/// The raw color data for this image.
pub cr: Vec<Color8>,
}
// EOF

18
source/durandal/mod.rs Normal file
View File

@ -0,0 +1,18 @@
//! Library for utilities.
#[macro_use]
pub mod err;
#[macro_use]
pub mod cenum;
#[macro_use]
pub mod bin;
pub mod bit;
pub mod cksum;
pub mod file;
pub mod fixed;
pub mod image;
pub mod sound;
pub mod text;
// EOF

99
source/durandal/sound.rs Normal file
View File

@ -0,0 +1,99 @@
//! Sound representation.
/// Any PCM stream which may be represented as a 16-bit PCM stream.
pub trait Sound
{
/// Returns the sample rate.
fn rate(&self) -> u16;
/// Returns the number of samples.
fn len(&self) -> usize;
/// Returns the `n`th sample.
///
/// # Panics
///
/// Panics if `n` exceeds the length of this sound.
fn index(&self, n: usize) -> i16;
/// Returns the number of the first sample of the loop, or `0`.
fn lp_beg(&self) -> usize;
/// Returns the number of the last sample of the loop, or `0`.
fn lp_end(&self) -> usize;
/// Returns `true` if there are no samples in this sound.
fn is_empty(&self) -> bool {self.len() == 0}
/// The same as `index`, but will not panic if out of bounds.
fn get(&self, n: usize) -> Option<i16>
{
if n < self.len() {
Some(self.index(n))
} else {
None
}
}
}
impl Sound16
{
/// Creates a new `Sound16`.
pub fn new(rate: u16, lp_beg: usize, lp_end: usize, len: usize) -> Self
{
Self{rate, lp_beg, lp_end, data: Vec::with_capacity(len)}
}
/// Creates a new `Sound16` from an unsigned 8-bit stream.
pub fn new_from_8(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8])
-> Self
{
let mut snd = Self::new(rate, lp_beg, lp_end, b.len());
for &sample in b {
snd.data.push(Self::sample_from_8(sample));
}
snd
}
/// Creates a new `Sound16` from a signed 16-bit stream.
pub fn new_from_16(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8])
-> Self
{
let mut snd = Self::new(rate, lp_beg, lp_end, b.len() / 2);
for i in (0..b.len()).step_by(2) {
snd.data.push(i16::from_le_bytes([b[i], b[i + 1]]));
}
snd
}
/// Creates a signed 16-bit sample from an unsigned 8-bit sample.
pub fn sample_from_8(sample: u8) -> i16 {(i16::from(sample) - 0x80) << 8}
}
impl Sound for Sound16
{
fn rate(&self) -> u16 {self.rate}
fn len(&self) -> usize {self.data.len()}
fn index(&self, p: usize) -> i16 {self.data[p]}
fn lp_beg(&self) -> usize {self.lp_beg}
fn lp_end(&self) -> usize {self.lp_end}
}
/// A 16-bit PCM stream.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Sound16
{
rate: u16,
lp_beg: usize,
lp_end: usize,
/// The raw signed PCM data of this sound.
pub data: Vec<i16>,
}
// EOF

160
source/durandal/text.rs Normal file
View File

@ -0,0 +1,160 @@
//! Text conversion utilities.
/// Formats a binary size string for any given number.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::to_binsize;
///
/// assert_eq!(to_binsize(5000), "5kB".to_string());
/// ```
pub fn to_binsize(n: u64) -> String
{
const NAMES: [&str; 4] = ["kB", "MB", "GB", "TB"];
// empty size
if n == 0 {
return String::from("empty");
}
// terabytes, gigabytes, megabytes, kilobytes
for i in (1..=4).rev() {
let pow = 1000_u64.pow(i as u32);
if n >= pow {
return format!("{:1}{}", n / pow, NAMES[i - 1]);
}
}
// or, just bytes
format!("{} {}", n, if n == 1 {"byte"} else {"bytes"})
}
/// Encodes or decodes a string in the terminal encryption format.
pub fn fuck_string(s: &[u8]) -> Vec<u8>
{
let mut v = s.to_vec();
let l = s.len();
let mut p = 0;
for _ in 0..l / 4 {
p += 2;
v[p] ^= 0xfe;
v[p + 1] ^= 0xed;
p += 2;
}
for _ in 0..l % 4 {
v[p] ^= 0xfe;
p += 1;
}
v
}
/// Reads a Pascal-style byte string with bounds checking.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::pascal_str;
///
/// assert_eq!(pascal_str(b"\x0bhello world"), b"hello world"[..].into());
/// assert_eq!(pascal_str(b"\x0chello world"), None);
/// assert_eq!(pascal_str(&[]), None);
/// ```
pub fn pascal_str(b: &[u8]) -> Option<&[u8]>
{
let s = usize::from(*b.get(0)?);
b.get(1..=s)
}
/// Converts input from Mac Roman to a Unicode string.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_conv;
///
/// assert_eq!(mac_roman_conv(b"p\x8cth"), "påth");
/// assert_eq!(mac_roman_conv(b"I\xd5ve"), "Ive");
/// ```
pub fn mac_roman_conv(s: &[u8]) -> String
{
let mut v = String::with_capacity(s.len());
for &c in s.iter() {
v.push(match c {
c if c & 0x80 != 0 => TR[usize::from(c) & 0x7F],
b'\r' => '\n',
c => char::from(c),
});
}
v
}
/// Converts a C-style string from Mac Roman to Unicode.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_cstr;
///
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0ed"), "Ive awaken");
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0"), "Ive awaken");
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken"), "Ive awaken");
/// ```
#[inline]
pub fn mac_roman_cstr(s: &[u8]) -> String
{
if let Some(n) = memchr::memchr(0, s) {
mac_roman_conv(&s[..n])
} else {
mac_roman_conv(s)
}
}
const TR: [char; 128] =
['\u{00c4}', '\u{00c5}', '\u{00c7}', '\u{00c9}', '\u{00d1}', '\u{00d6}',
'\u{00dc}', '\u{00e1}', '\u{00e0}', '\u{00e2}', '\u{00e4}', '\u{00e3}',
'\u{00e5}', '\u{00e7}', '\u{00e9}', '\u{00e8}', '\u{00ea}', '\u{00eb}',
'\u{00ed}', '\u{00ec}', '\u{00ee}', '\u{00ef}', '\u{00f1}', '\u{00f3}',
'\u{00f2}', '\u{00f4}', '\u{00f6}', '\u{00f5}', '\u{00fa}', '\u{00f9}',
'\u{00fb}', '\u{00fc}', '\u{2020}', '\u{00b0}', '\u{00a2}', '\u{00a3}',
'\u{00a7}', '\u{2022}', '\u{00b6}', '\u{00df}', '\u{00ae}', '\u{00a9}',
'\u{2122}', '\u{00b4}', '\u{00a8}', '\u{2260}', '\u{00c6}', '\u{00d8}',
'\u{221e}', '\u{00b1}', '\u{2264}', '\u{2265}', '\u{00a5}', '\u{00b5}',
'\u{2202}', '\u{2211}', '\u{220f}', '\u{03c0}', '\u{222b}', '\u{00aa}',
'\u{00ba}', '\u{03a9}', '\u{00e6}', '\u{00f8}', '\u{00bf}', '\u{00a1}',
'\u{00ac}', '\u{221a}', '\u{0192}', '\u{2248}', '\u{2206}', '\u{00ab}',
'\u{00bb}', '\u{2026}', '\u{00a0}', '\u{00c0}', '\u{00c3}', '\u{00d5}',
'\u{0152}', '\u{0153}', '\u{2013}', '\u{2014}', '\u{201c}', '\u{201d}',
'\u{2018}', '\u{2019}', '\u{00f7}', '\u{25ca}', '\u{00ff}', '\u{0178}',
'\u{2044}', '\u{20ac}', '\u{2039}', '\u{203a}', '\u{fb01}', '\u{fb02}',
'\u{2021}', '\u{00b7}', '\u{201a}', '\u{201e}', '\u{2030}', '\u{00c2}',
'\u{00ca}', '\u{00c1}', '\u{00cb}', '\u{00c8}', '\u{00cd}', '\u{00ce}',
'\u{00cf}', '\u{00cc}', '\u{00d3}', '\u{00d4}', '\u{f8ff}', '\u{00d2}',
'\u{00da}', '\u{00db}', '\u{00d9}', '\u{0131}', '\u{02c6}', '\u{02dc}',
'\u{00af}', '\u{02d8}', '\u{02d9}', '\u{02da}', '\u{00b8}', '\u{02dd}',
'\u{02db}', '\u{02c7}'];
#[test]
fn to_binsize_integrals()
{
assert_eq!(to_binsize(0), "empty");
assert_eq!(to_binsize(1), "1 byte");
assert_eq!(to_binsize(2), "2 bytes");
assert_eq!(to_binsize(999), "999 bytes");
assert_eq!(to_binsize(1000), "1kB");
assert_eq!(to_binsize(1000 * 7), "7kB");
assert_eq!(to_binsize(1000 * 1000), "1MB");
assert_eq!(to_binsize(1000 * 1000 * 7), "7MB");
assert_eq!(to_binsize(1000 * 1000 * 1000), "1GB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 7), "7GB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 1000), "1TB");
assert_eq!(to_binsize(1000 * 1000 * 1000 * 1000 * 7), "7TB");
}
// EOF

Some files were not shown because too many files have changed in this diff Show More