Compare commits
190 Commits
png-branch
...
master
Author | SHA1 | Date |
---|---|---|
an | 32fd50690a | |
an | d089b35278 | |
an | 1fcb3fd609 | |
an | 186020c8c2 | |
an | b5d1aaaafe | |
an | b125a6ebcf | |
an | c8c53bd804 | |
an | 98ba7309ce | |
an | 9625eb1618 | |
an | 0a0b6f9c5f | |
an | 74e11a727f | |
an | 8fb49b8929 | |
an | 01a5cdc683 | |
an | 18a8318b70 | |
Alison G. Watson | 22fcd99a42 | |
Alison G. Watson | 897bb17346 | |
an | 350bb402d2 | |
an | e3875b956f | |
an | 3ba5499bb9 | |
an | 77467ab69b | |
an | f74981d814 | |
an | e3eaeeec66 | |
an | 8354207c75 | |
an | 0e5fc4dbf2 | |
Alison G. Watson | d323a7fb00 | |
Alison G. Watson | 43ac712f34 | |
Alison G. Watson | 8f975ad44b | |
an | cbd4ef0171 | |
an | 666b9a78ec | |
Alison G. Watson | 7cbb5a1510 | |
Alison G. Watson | f328ced8f9 | |
Alison G. Watson | ed50dd2d1c | |
an | 5389062d44 | |
an | 262ab3712e | |
an | f9722647af | |
an | 91182c6015 | |
an | c01756886b | |
an | fa7d785212 | |
an | 004989ab58 | |
an | a79f814d3c | |
an | 16b3e03cfa | |
an | 790dc9f840 | |
an | e90e4df392 | |
an | f15f2f6dcc | |
an | 3e9ca71dc8 | |
an | cd854ef8ea | |
an | bb173b47e3 | |
an | 68bfd417b5 | |
an | 133b80ae38 | |
an | e58f5d8907 | |
an | 4a0257324e | |
an | 8f6b690442 | |
an | 11cfc909f8 | |
an | e3c0b0dd7d | |
an | 14c0ef8f2a | |
an | ab48d26711 | |
an | 501784e2fe | |
an | 80295108b7 | |
an | 9dcf487d18 | |
an | b16b2a7ff1 | |
an | 19cf86b8bc | |
an | 628494e8a4 | |
an | 14d9850653 | |
an | 8f1b043b93 | |
an | 54ab1f9153 | |
an | 5edec0bc88 | |
an | e6bc793861 | |
an | fcf467210e | |
an | 31c6701dd5 | |
an | ad792b99d3 | |
an | b78f6e8401 | |
an | 9b9a484351 | |
an | 2dd4762c2e | |
an | 5eb8962d2d | |
an | 430457e570 | |
an | 2095e9d58a | |
an | daebfd2da6 | |
an | bfb91792ec | |
an | be8d72df7d | |
an | 91d171c8a6 | |
an | 92aea23e04 | |
an | 17b5456aca | |
an | e64f652281 | |
an | c2339138a8 | |
an | 1ac6b2df9d | |
an | 494ed48981 | |
an | 1d8ebfb4dc | |
an | 5e09c1dae8 | |
an | 8889bcf1a4 | |
an | 3c42cd451d | |
an | 840ac231d3 | |
an | 8ff9d466e1 | |
an | 3086ff687d | |
an | 052679145e | |
an | d64a4167d8 | |
an | cb9d844404 | |
an | 82d0d378b0 | |
an | 68b4939a15 | |
an | dafa691002 | |
an | 25d5ac1314 | |
an | 475aa64138 | |
an | 661bc9082f | |
an | 02510a92f4 | |
an | 8e05c84d42 | |
an | e17f0cc081 | |
an | 629d0a70db | |
an | 24ad25257b | |
an | c9b21a19c0 | |
an | 857c70eab9 | |
an | 40a28149a1 | |
an | bb8f8d4636 | |
an | 783250a9a2 | |
an | 5fb8c710a9 | |
an | 0b075b0276 | |
an | 2d5e990cf0 | |
an | 022ef81f38 | |
an | 651efd1f32 | |
an | c5cd1aecf3 | |
an | 5d45b23c56 | |
an | 4357a2be84 | |
an | 17856bbe7e | |
an | 11f90cb112 | |
an | c984ba50a9 | |
an | 9a6c2c3a66 | |
an | 9d57058b0d | |
an | 7bc56dc171 | |
an | d9ca7a3866 | |
an | 56df373dc1 | |
an | 36307aca0a | |
an | 0acfa18abd | |
an | 9e31f6e498 | |
an | 8c9415b867 | |
an | 11ebd77e6a | |
an | 959930cede | |
an | 4447897302 | |
an | 89d0274c80 | |
an | f895dac2b8 | |
an | 3b8790b442 | |
an | 116d91ef6a | |
an | ad1c4e216b | |
an | f4b0e37542 | |
an | 6bc4ca36f4 | |
an | 760143f901 | |
an | d5d91c1134 | |
an | 67426eea3b | |
an | b3ff145f63 | |
an | c8ebe009e7 | |
an | 7a231fb351 | |
an | fb0ecffd74 | |
an | 90f5d88c20 | |
an | dc899fa058 | |
an | 36e11b95e8 | |
an | a523d6f8d4 | |
an | 023519ad0a | |
an | 0e32e7893a | |
an | 2875da07e1 | |
an | 392e20ae65 | |
an | 1a3145b5b6 | |
an | b8e624fecd | |
an | 75e354a16b | |
an | d56b3db1fe | |
an | b427f4c99d | |
an | 8693f7605a | |
an | 742f0c1c8e | |
an | 9542592027 | |
an | 3f3900d91c | |
an | da542c84e8 | |
an | 2cd5be77d3 | |
an | 46ad94bdf0 | |
an | 3b8e285d93 | |
an | 9c91d8bd01 | |
an | 685991c6ed | |
an | cbf87ff156 | |
an | 8ccfdd79ef | |
an | faf1f5ee5d | |
an | d818e579f9 | |
an | 4f99016b8b | |
an | 74fdfcaa43 | |
an | 135e9c688a | |
an | 8c2b8edded | |
an | 7e92521e2e | |
an | aa0f6cf537 | |
an | 07f08752d1 | |
an | 32855fa213 | |
an | 708f2e106c | |
an | 7e5375f32c | |
an | b830718258 | |
an | ee35332b23 | |
an | 6ee6140ba8 | |
an | 9c02fba968 |
|
@ -1,6 +1,10 @@
|
|||
/target
|
||||
/out
|
||||
*.res
|
||||
**/*.rs.bk
|
||||
*.bat
|
||||
*.user
|
||||
.DS_Store
|
||||
/out
|
||||
/target
|
||||
/tycho/resources/icons.qrc
|
||||
/tycho/resources/icons/*
|
||||
Cargo.lock
|
||||
perf.data*
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "rust-qt-binding-generator"]
|
||||
path = rust-qt-binding-generator
|
||||
url = https://git.greyserv.net/marrub/rust-qt-binding-generator
|
15
Cargo.toml
15
Cargo.toml
|
@ -1,24 +1,23 @@
|
|||
[package]
|
||||
name = "maraiah"
|
||||
version = "0.1.0"
|
||||
authors = ["Alison Sanderson <marrub@greyserv.net>"]
|
||||
authors = ["Alison Watson <marrub@greyserv.net>", "Tae Matous"]
|
||||
description = "Marathon editing tools."
|
||||
homepage = "https://greyserv.net/maraiah/"
|
||||
repository = "http://git.greyserv.net/marrub/Maraiah"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
edition = "2018"
|
||||
publish = false
|
||||
license = "CC0-1.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
serde_obj = ["serde"]
|
||||
|
||||
[workspace]
|
||||
members = ["source/leela", "source/tycho"]
|
||||
members = ["leela", "tycho"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
bitflags = "1.1"
|
||||
failure = {version = "0.1", features = ["std"]}
|
||||
serde = {version = "1.0", features = ["derive"], optional = true}
|
||||
memchr = "2.0"
|
||||
|
@ -32,4 +31,4 @@ lto = true
|
|||
|
||||
[lib]
|
||||
name = "maraiah"
|
||||
path = "source/lib.rs"
|
||||
path = "maraiah/lib.rs"
|
||||
|
|
17
LICENSE
17
LICENSE
|
@ -1,10 +1,15 @@
|
|||
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:
|
||||
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.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
|
|
2478
MarathonData.md
2478
MarathonData.md
File diff suppressed because it is too large
Load Diff
16
README.md
16
README.md
|
@ -1,6 +1,12 @@
|
|||
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.
|
||||
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)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# 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 -->
|
|
@ -0,0 +1,331 @@
|
|||
# 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 -->
|
|
@ -0,0 +1,111 @@
|
|||
# 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
|
@ -0,0 +1,447 @@
|
|||
# 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 -->
|
|
@ -0,0 +1,407 @@
|
|||
# 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 -->
|
|
@ -0,0 +1,18 @@
|
|||
# `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`.
|
|
@ -0,0 +1,31 @@
|
|||
# `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>`
|
|
@ -0,0 +1,27 @@
|
|||
# `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`.
|
|
@ -0,0 +1,14 @@
|
|||
[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"
|
|
@ -0,0 +1,161 @@
|
|||
#![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
|
|
@ -0,0 +1,626 @@
|
|||
//! 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
|
|
@ -0,0 +1,162 @@
|
|||
//! 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
|
|
@ -0,0 +1,88 @@
|
|||
#![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
|
|
@ -0,0 +1,120 @@
|
|||
#![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
|
|
@ -0,0 +1,66 @@
|
|||
//! 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
|
|
@ -0,0 +1,425 @@
|
|||
//! 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
|
|
@ -0,0 +1,22 @@
|
|||
#![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
|
|
@ -0,0 +1,167 @@
|
|||
//! 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
|
|
@ -0,0 +1,96 @@
|
|||
//! 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
|
|
@ -0,0 +1,67 @@
|
|||
//! 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
|
|
@ -0,0 +1,639 @@
|
|||
//! 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
|
|
@ -0,0 +1,260 @@
|
|||
//! 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
|
|
@ -0,0 +1,117 @@
|
|||
//! 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
|
|
@ -0,0 +1,42 @@
|
|||
//! 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
|
|
@ -0,0 +1,9 @@
|
|||
//! QuickDraw PICT `PixMap` data.
|
||||
|
||||
pub mod area;
|
||||
pub mod head;
|
||||
pub mod ind;
|
||||
pub mod r5g5b5;
|
||||
pub mod rgb8;
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,21 @@
|
|||
//! 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
|
|
@ -0,0 +1,86 @@
|
|||
//! 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
|
|
@ -0,0 +1,86 @@
|
|||
//! 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
|
|
@ -0,0 +1,44 @@
|
|||
//! 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
|
|
@ -0,0 +1,62 @@
|
|||
//! 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
|
|
@ -0,0 +1,94 @@
|
|||
//! 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
|
|
@ -0,0 +1,69 @@
|
|||
//! 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
|
|
@ -0,0 +1,41 @@
|
|||
//! 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
|
|
@ -1,3 +1,6 @@
|
|||
//! This is the base library for the Maraiah tool set. This library contains
|
||||
//! functions and structures for reading many file formats, primarily those used
|
||||
//! by Marathon, Marathon 2, and Marathon Infinity.
|
||||
#![deny(anonymous_parameters)]
|
||||
#![deny(bare_trait_objects)]
|
||||
#![deny(elided_lifetimes_in_paths)]
|
||||
|
@ -17,6 +20,8 @@
|
|||
#![deny(clippy::explicit_iter_loop)]
|
||||
#![deny(clippy::fallible_impl_from)]
|
||||
#![deny(clippy::filter_map)]
|
||||
#![deny(clippy::filter_map_next)]
|
||||
#![deny(clippy::find_map)]
|
||||
#![deny(clippy::float_arithmetic)]
|
||||
#![deny(clippy::float_cmp_const)]
|
||||
#![deny(clippy::invalid_upcast_comparisons)]
|
||||
|
@ -25,6 +30,7 @@
|
|||
#![deny(clippy::map_flatten)]
|
||||
#![deny(clippy::match_same_arms)]
|
||||
#![deny(clippy::mem_forget)]
|
||||
#![deny(clippy::missing_const_for_fn)]
|
||||
#![deny(clippy::multiple_inherent_impl)]
|
||||
#![deny(clippy::mut_mut)]
|
||||
#![deny(clippy::mutex_integer)]
|
||||
|
@ -35,6 +41,8 @@
|
|||
#![deny(clippy::panicking_unwrap)]
|
||||
#![deny(clippy::print_stdout)]
|
||||
#![deny(clippy::pub_enum_variant_names)]
|
||||
#![deny(clippy::redundant_clone)]
|
||||
#![deny(clippy::redundant_closure_for_method_calls)]
|
||||
#![deny(clippy::replace_consts)]
|
||||
#![deny(clippy::result_map_unwrap_or_else)]
|
||||
#![deny(clippy::result_unwrap_used)]
|
||||
|
@ -47,9 +55,28 @@
|
|||
#![deny(clippy::use_self)]
|
||||
#![deny(clippy::used_underscore_binding)]
|
||||
|
||||
#[macro_use]
|
||||
pub mod durandal;
|
||||
pub mod marathon;
|
||||
pub mod rozinante;
|
||||
#[macro_use] extern crate bitflags;
|
||||
|
||||
#[macro_use] mod cbitfield;
|
||||
#[macro_use] mod cenum;
|
||||
#[macro_use] mod doc;
|
||||
#[macro_use] pub mod err;
|
||||
#[macro_use] pub mod ffi;
|
||||
#[macro_use] pub mod bin;
|
||||
|
||||
pub mod bit;
|
||||
pub mod cksum;
|
||||
pub mod defl;
|
||||
pub mod file;
|
||||
pub mod fixed;
|
||||
pub mod image;
|
||||
pub mod machdr;
|
||||
pub mod map;
|
||||
pub mod meta;
|
||||
pub mod shp;
|
||||
pub mod snd;
|
||||
pub mod sound;
|
||||
pub mod text;
|
||||
pub mod xfer;
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,116 @@
|
|||
//! 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
|
|
@ -0,0 +1,39 @@
|
|||
//! 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
|
|
@ -0,0 +1,26 @@
|
|||
//! `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
|
|
@ -0,0 +1,38 @@
|
|||
//! `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
|
|
@ -0,0 +1,52 @@
|
|||
//! `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
|
|
@ -0,0 +1,103 @@
|
|||
//! `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
|
|
@ -0,0 +1,197 @@
|
|||
//! 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
|
|
@ -0,0 +1,70 @@
|
|||
//! 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
|
|
@ -0,0 +1,49 @@
|
|||
//! `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
|
|
@ -0,0 +1,46 @@
|
|||
//! `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
|
|
@ -0,0 +1,150 @@
|
|||
//! 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
|
|
@ -0,0 +1,13 @@
|
|||
//! `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
|
|
@ -0,0 +1,49 @@
|
|||
//! `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
|
|
@ -0,0 +1,398 @@
|
|||
//! `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: <fn::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
|
|
@ -0,0 +1,57 @@
|
|||
//! `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
|
|
@ -0,0 +1,71 @@
|
|||
//! `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
|
|
@ -0,0 +1,131 @@
|
|||
//! `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
|
|
@ -0,0 +1,200 @@
|
|||
//! `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
|
|
@ -0,0 +1,11 @@
|
|||
//! `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
|
|
@ -0,0 +1,29 @@
|
|||
//! `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
|
|
@ -0,0 +1,57 @@
|
|||
//! `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
|
|
@ -0,0 +1,46 @@
|
|||
//! `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
|
|
@ -0,0 +1,72 @@
|
|||
//! `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
|
|
@ -0,0 +1,54 @@
|
|||
//! `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
|
|
@ -0,0 +1,190 @@
|
|||
//! `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
|
|
@ -0,0 +1,89 @@
|
|||
//! `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
|
|
@ -0,0 +1,77 @@
|
|||
//! `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
|
|
@ -0,0 +1,84 @@
|
|||
//! `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
|
|
@ -0,0 +1,36 @@
|
|||
//! `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
|
|
@ -0,0 +1,55 @@
|
|||
//! `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
|
|
@ -0,0 +1,73 @@
|
|||
//! `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
|
|
@ -0,0 +1,28 @@
|
|||
//! `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
|
|
@ -0,0 +1,102 @@
|
|||
//! `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
|
|
@ -0,0 +1,108 @@
|
|||
//! `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
|
|
@ -0,0 +1,50 @@
|
|||
//! 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
|
|
@ -0,0 +1,59 @@
|
|||
//! 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
|
|
@ -0,0 +1,140 @@
|
|||
//! 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
|
|
@ -0,0 +1,114 @@
|
|||
//! 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
|
|
@ -0,0 +1,68 @@
|
|||
//! 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
|
|
@ -0,0 +1,70 @@
|
|||
//! 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
|
|
@ -0,0 +1,86 @@
|
|||
//! 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
|
|
@ -0,0 +1,54 @@
|
|||
//! 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
|
|
@ -0,0 +1,96 @@
|
|||
//! 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
|
|
@ -0,0 +1,47 @@
|
|||
//! 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
|
|
@ -0,0 +1,98 @@
|
|||
//! 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
|
|
@ -0,0 +1,45 @@
|
|||
//! 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
|
|
@ -0,0 +1,218 @@
|
|||
//! 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"), "I’ve");
|
||||
/// ```
|
||||
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"), "I’ve awaken");
|
||||
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0"), "I’ve awaken");
|
||||
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken"), "I’ve 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("I’ve\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
|
|
@ -0,0 +1,38 @@
|
|||
//! 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
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2a8664e9bfed74150c6a2df28c78f2d6b3f1a674
|
|
@ -1,503 +0,0 @@
|
|||
//! 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
|
|
@ -1,162 +0,0 @@
|
|||
//! 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
|
|
@ -1,96 +0,0 @@
|
|||
#![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
|
||||
{
|
||||
$($(#[$inner:meta])* $va:expr => $en:ident,)+
|
||||
}
|
||||
) => {
|
||||
$(#[$outer])*
|
||||
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||
$vi enum $t
|
||||
{
|
||||
$($en,)+
|
||||
}
|
||||
|
||||
impl $t
|
||||
{
|
||||
/// Returns, if representable, the variant of `Self` from `n`.
|
||||
$vi fn from_repr(n: $ti) -> Result<Self, ReprError>
|
||||
{
|
||||
match n {
|
||||
$($(#[$inner])* $va => Ok($t::$en),)+
|
||||
n => Err(ReprError::new(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[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
|
|
@ -1,63 +0,0 @@
|
|||
//! 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
|
|
@ -1,99 +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(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
|
|
@ -1,17 +0,0 @@
|
|||
//! 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
|
|
@ -1,453 +0,0 @@
|
|||
//! 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
|
|
@ -1,255 +0,0 @@
|
|||
//! 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
|
|
@ -1,18 +0,0 @@
|
|||
//! 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
|
|
@ -1,99 +0,0 @@
|
|||
//! 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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue