├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── lib.rs ├── parser ├── color │ ├── indexed.rs │ ├── mod.rs │ └── rgb8.rs ├── directory.rs ├── entry │ ├── entry_type.rs │ └── mod.rs ├── header.rs ├── image.rs ├── mod.rs ├── palette.rs ├── repr │ ├── directory.rs │ ├── entry │ │ ├── entry_type.rs │ │ └── mod.rs │ ├── header.rs │ └── mod.rs ├── texture │ ├── font.rs │ ├── mip.rs │ └── mod.rs └── wad.rs ├── parser_async ├── arc_vec.rs ├── mod.rs ├── parse.rs └── wad_source.rs ├── repr ├── color.rs ├── image.rs ├── mod.rs ├── palette.rs ├── texture │ ├── font.rs │ ├── mip.rs │ ├── mod.rs │ ├── qpic.rs │ └── size.rs └── wad.rs └── test_data.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | Cargo.lock 4 | 5 | # Non-FOSS test data 6 | test_data/ 7 | src/integration_tests.rs -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ogre" 3 | version = "0.1.7" 4 | authors = ["Josh Palmer "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A rusty, dual-wielding Quake and Half-Life texture WAD parser." 8 | repository = "https://github.com/Shfty/ogre/" 9 | keywords = ["wad", "quake", "textures", "gamedev", "async"] 10 | categories = ["parsing", "parser-implementations", "rendering::data-formats", "game-development", "asynchronous"] 11 | exclude = [ 12 | ".vscode/*", 13 | ] 14 | 15 | [features] 16 | default = ["serde_support", "async"] 17 | async = ["futures", "async-std"] 18 | serde_support = ["serde", "serde-big-array"] 19 | 20 | [dependencies] 21 | nom = "7.0.0" 22 | 23 | [dependencies.serde] 24 | version = "1.0.130" 25 | features = ["derive"] 26 | optional = true 27 | 28 | [dependencies.serde-big-array] 29 | version = "0.5.1" 30 | optional = true 31 | 32 | [dependencies.futures] 33 | version = "0.3.17" 34 | optional = true 35 | 36 | [dependencies.async-std] 37 | version = "1.10.0" 38 | optional = true 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ogre   [![License]][mit] [![Latest Version]][crates.io] [![Documentation]][docs.rs] 2 | 3 | [License]: https://img.shields.io/badge/license-MIT-blue.svg 4 | [mit]: LICENSE 5 | 6 | [Latest Version]: https://img.shields.io/crates/v/ogre.svg 7 | [crates.io]: https://crates.io/crates/ogre 8 | 9 | [Documentation]: https://docs.rs/ogre/badge.svg 10 | [docs.rs]: https://docs.rs/ogre 11 | 12 | ## A rusty, dual-wielding Quake and Half-Life texture WAD parser 13 | 14 | `ogre` is a rust representation and `nom` parser for Quake and Half-Life [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files. 15 | 16 | It's written in pure Rust, and enforces the use of safe code crate-wide via `#![forbid(unsafe_code)]`. 17 | 18 | ## Rust Representation 19 | 20 | The Rust represention lives in the `repr` module, 21 | and is a set of structs representing the contents of a parsed [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) file. 22 | 23 | [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files contain certain intermediary structures - such as a header, and metadata directory - that are specific to parse-time, and thus don't show up in the final representation. 24 | For cases where you want to inspect these elements of a [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0), the `parser` module contains its own `parser::repr` submodule, as well as `nom` functions for parsing into the structures therein. 25 | 26 | ## Parsing 27 | 28 | The simplest way to parse a [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) file into AST is via the `parser::parse_wad` function: 29 | 30 | ```rust 31 | let wad = include_bytes!("../../ogre/test_data/wad2/medieval.wad"); 32 | let (_, wad) = ogre::parser::parse_wad(wad).expect("Failed to parse WAD"); 33 | assert!(wad.len() > 0); 34 | println!("{:#?}", wad); 35 | ``` 36 | 37 | This will parse a complete `Wad`, and block the calling thread until completion. 38 | Internally, it calls the rest of functions `parser` module to assemble its final output. 39 | These can also be used directly in cases where more granular parsing is desired. 40 | 41 | For better performance, a parallelized implementation is recommended. See the [`Async`](#Async) header below for more. 42 | 43 | ## Format Support 44 | 45 | `ogre` supports the Quake [`WAD2`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) and Half-Life [`WAD3`](https://yuraj.ucoz.com/half-life-formats.pdf) formats. 46 | 47 | Currently, the original Doom [`WAD`](https://doomwiki.org/wiki/WAD) format is not supported on account of its different data structure and significantly larger scope. 48 | 49 | ## Serde Support 50 | 51 | For cases where serializing and deserializing the rust representation is required, 52 | `ogre` includes `serde::Serialize` and `serde::Deserialize` derives for all types inside the `repr` and `parser::repr` modules. 53 | 54 | This functionality is gated behind the `serde_support` feature flag, which is enabled by default. 55 | 56 | ## Async 57 | 58 | `ogre` includes a parallelized implementation that uses `async_std::task` to multiplex the routines inside `parser` over some source of [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) bytes. 59 | This approach scales better than single-threading, and is generally more performant - especially for large [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files. 60 | An explanation and usage guide can be found inside the `parser_async` module. 61 | 62 | This functionality is gated behind the `async` feature flag, which is enabled by default. 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | //! # A rusty, dual-wielding Quake and Half-Life texture WAD parser 4 | //! [`ogre`](crate) is a Rust representation and [`nom`] parser for Quake and Half-Life [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files. 5 | //! 6 | //! It's written in pure Rust, and enforces the use of safe code crate-wide via `#![forbid(unsafe_code)]`. 7 | //! 8 | //! ## Rust Representation 9 | //! The Rust represention lives in the [`repr`] module, 10 | //! and is a set of structs representing the contents of a parsed [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) file. 11 | //! 12 | //! [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files contain certain intermediary structures - such as a header, and metadata directory - that are specific to parse-time, and thus don't show up in the final representation. 13 | //! For cases where you want to inspect these elements of a [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0), the [`parser`] module contains its own [`parser::repr`] submodule, as well as `nom` functions for parsing into the structures therein. 14 | //! 15 | //! ## Parsing 16 | //! The simplest way to parse a [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) file into rust representation is via the [`parser::parse_wad`] function: 17 | //! ``` 18 | //! let wad = include_bytes!("../../ogre/test_data/wad2/medieval.wad"); 19 | //! let (_, wad) = ogre::parser::parse_wad(wad).expect("Failed to parse WAD"); 20 | //! assert!(wad.len() > 0); 21 | //! println!("{:#?}", wad); 22 | //! ``` 23 | //! 24 | //! This will parse a complete [`Wad`], and block the calling thread until completion. 25 | //! Internally, it calls the rest of the functions in the [`parser`] module to assemble its final output. 26 | //! These can also be used directly in cases where more granular parsing is desired. 27 | //! 28 | //! For better performance, a parallelized implementation is recommended. See the [`Async`](#Async) header below for more. 29 | //! 30 | //! ## Format Support 31 | //! [`ogre`](crate) supports the Quake [`WAD2`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) and Half-Life [`WAD3`](https://yuraj.ucoz.com/half-life-formats.pdf) formats. 32 | //! 33 | //! Currently, the original Doom [`WAD`](https://doomwiki.org/wiki/WAD) format is not supported on account of its different data structure and significantly larger scope. 34 | //! 35 | //! ## Serde Support 36 | //! For cases where serializing and deserializing the rust representation is required, 37 | //! [`ogre`](crate) includes [`serde::Serialize`] and [`serde::Deserialize`] derives for all types inside the [`repr`] and [`parser::repr`] modules. 38 | //! 39 | //! This functionality is gated behind the `serde_support` feature flag, which is enabled by default. 40 | //! 41 | //! ## Async 42 | //! [`ogre`](crate) includes a parallelized implementation that uses [`async_std::task`] to multiplex the routines inside [`parser`] over some source of [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) bytes. 43 | //! This approach scales better than single-threading, and is generally more performant - especially for large [`WAD`](https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_7.htm#CWAD0) files. 44 | //! An explanation and usage guide can be found inside the [`parser_async`] module. 45 | //! 46 | //! This functionality is gated behind the `async` feature flag, which is enabled by default. 47 | 48 | #[cfg(doc)] 49 | use repr::Wad; 50 | 51 | pub mod parser; 52 | pub mod repr; 53 | 54 | #[cfg(feature = "async")] 55 | pub mod parser_async; 56 | 57 | #[cfg(test)] 58 | mod test_data; 59 | 60 | #[cfg(test)] 61 | mod integration_tests; 62 | -------------------------------------------------------------------------------- /src/parser/color/indexed.rs: -------------------------------------------------------------------------------- 1 | use nom::{number::complete::le_u8, IResult}; 2 | 3 | use crate::repr::ColorIndexed; 4 | use crate::impl_try_from; 5 | 6 | /// Parse a [`ColorIndexed`] from a byte slice. 7 | pub fn parse_color_indexed(i: &[u8]) -> IResult<&[u8], ColorIndexed> { 8 | let (i, o) = le_u8(i)?; 9 | Ok((i, ColorIndexed(o))) 10 | } 11 | 12 | impl_try_from!(ColorIndexed, parse_color_indexed); 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use crate::test_data::{test_color_indexed_in, test_color_indexed_out}; 17 | 18 | use super::*; 19 | use std::{convert::TryInto, error::Error}; 20 | 21 | #[test] 22 | fn test_color_indexed() -> Result<(), Box> { 23 | let color_indexed: ColorIndexed = test_color_indexed_in().try_into()?; 24 | assert_eq!(color_indexed, test_color_indexed_out()); 25 | Ok(()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/parser/color/mod.rs: -------------------------------------------------------------------------------- 1 | mod indexed; 2 | mod rgb8; 3 | 4 | pub use indexed::*; 5 | pub use rgb8::*; -------------------------------------------------------------------------------- /src/parser/color/rgb8.rs: -------------------------------------------------------------------------------- 1 | use nom::{number::complete::le_u8, IResult}; 2 | 3 | use crate::{repr::ColorRgb8, impl_try_from}; 4 | 5 | /// Parse a [`ColorRgb8`] from a byte slice. 6 | pub fn parse_color_rgb8(i: &[u8]) -> IResult<&[u8], ColorRgb8> { 7 | let (i, (r, g, b)) = nom::sequence::tuple((le_u8, le_u8, le_u8))(i)?; 8 | Ok((i, ColorRgb8(r, g, b))) 9 | } 10 | 11 | impl_try_from!(ColorRgb8, parse_color_rgb8); 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use crate::test_data::{test_color_rgb8_in, test_color_rgb8_out}; 16 | 17 | use super::*; 18 | 19 | #[test] 20 | fn test_color_rgb8() { 21 | let (_, o) = parse_color_rgb8(test_color_rgb8_in()).unwrap(); 22 | assert_eq!(o, test_color_rgb8_out()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/parser/directory.rs: -------------------------------------------------------------------------------- 1 | use nom::{multi::count, IResult}; 2 | 3 | use crate::parser::repr::Directory; 4 | use crate::parser::parse_entry; 5 | 6 | /// Given an entry count, returns a function that parses a [`Directory`] from a byte slice. 7 | pub fn parse_directory(entry_count: usize) -> impl Fn(&[u8]) -> IResult<&[u8], Directory> { 8 | move |i: &[u8]| { 9 | let (i, entries) = count(parse_entry, entry_count)(i)?; 10 | Ok((i, Directory(entries))) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/parser/entry/entry_type.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use nom::{number::complete::le_u8, IResult}; 4 | 5 | use crate::{impl_try_from, parser::repr::EntryType}; 6 | 7 | /// Parse an [`EntryType`] from a byte slice. 8 | pub fn parse_entry_type(i: &[u8]) -> IResult<&[u8], EntryType> { 9 | let (i, o) = le_u8(i)?; 10 | let entry_type: EntryType = o.try_into().expect("Invalid entry type"); 11 | Ok((i, entry_type)) 12 | } 13 | 14 | impl_try_from!(EntryType, parse_entry_type); 15 | -------------------------------------------------------------------------------- /src/parser/entry/mod.rs: -------------------------------------------------------------------------------- 1 | mod entry_type; 2 | pub use entry_type::*; 3 | 4 | use nom::{ 5 | number::complete::{le_u32, le_u8}, 6 | IResult, 7 | }; 8 | 9 | use crate::{ 10 | impl_try_from, 11 | parser::{repr::Entry, parse_byte_string}, 12 | }; 13 | 14 | /// The size of an [`Entry`] in bytes. 15 | pub const ENTRY_SIZE: usize = 32; 16 | 17 | /// Parse an `Entry` from a byte slice. 18 | pub fn parse_entry(i: &[u8]) -> IResult<&[u8], Entry> { 19 | let (i, o) = nom::sequence::tuple((le_u32, le_u32, le_u32, parse_entry_type, le_u8))(i)?; 20 | 21 | let (offset, size_directory, size_memory, entry_type, compression) = o; 22 | 23 | let (i, (name,)) = nom::sequence::tuple((parse_byte_string(16),))(&i[2..])?; 24 | 25 | Ok(( 26 | i, 27 | Entry { 28 | offset, 29 | size_directory, 30 | size_memory, 31 | entry_type, 32 | compression, 33 | name, 34 | }, 35 | )) 36 | } 37 | 38 | impl_try_from!(Entry<'a>, parse_entry); 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::test_data::{test_entry_in, test_entry_out}; 44 | 45 | #[test] 46 | fn test_entry() { 47 | let i = test_entry_in(); 48 | let (_, o) = parse_entry(&i).unwrap(); 49 | assert_eq!(o, test_entry_out()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/parser/header.rs: -------------------------------------------------------------------------------- 1 | use nom::{number::complete::le_u32, IResult}; 2 | 3 | use crate::impl_try_from; 4 | use crate::parser::{ 5 | repr::{Header, WadMagic}, 6 | parse_byte_string, 7 | }; 8 | 9 | /// The size of a [`Header`] in bytes. 10 | pub const HEADER_SIZE: usize = 12; 11 | 12 | /// Parse a [`WadMagic`] from a byte slice. 13 | pub fn parse_wad_magic(i: &[u8]) -> IResult<&[u8], WadMagic> { 14 | let (i, magic) = parse_byte_string(4)(i)?; 15 | let magic = match magic { 16 | "WAD2" => WadMagic::Wad2, 17 | "WAD3" => WadMagic::Wad3, 18 | _ => panic!("Invalid WAD magic"), 19 | }; 20 | Ok((i, magic)) 21 | } 22 | 23 | /// Parse a [`Header`] from a byte slice. 24 | pub fn parse_header(i: &[u8]) -> IResult<&[u8], Header> { 25 | let (i, o) = nom::sequence::tuple((parse_wad_magic, le_u32, le_u32))(i)?; 26 | 27 | let (magic, num_entries, dir_offset) = o; 28 | 29 | Ok(( 30 | i, 31 | Header { 32 | magic, 33 | num_entries, 34 | dir_offset, 35 | }, 36 | )) 37 | } 38 | 39 | impl_try_from!(Header, parse_header); 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use crate::test_data::{test_header_in, test_header_out}; 45 | 46 | #[test] 47 | fn test_header() { 48 | let i = test_header_in(); 49 | let (_, o) = parse_header(&i).unwrap(); 50 | assert_eq!(o, test_header_out()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/parser/image.rs: -------------------------------------------------------------------------------- 1 | use nom::{multi::count, IResult}; 2 | 3 | use crate::{repr::Image, parser::parse_color_indexed}; 4 | 5 | /// Given a texel count, returns a function that parses an [`Image`] from a byte slice. 6 | pub fn parse_image<'a>(texture_size: usize) -> impl Fn(&[u8]) -> IResult<&[u8], Image> + 'a { 7 | move |i: &[u8]| { 8 | let (i, o) = count(parse_color_indexed, texture_size)(i)?; 9 | Ok((i, Image::new(o))) 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::*; 16 | use crate::test_data::{test_image_in, test_image_out}; 17 | 18 | #[test] 19 | fn test_image() { 20 | let (_, o) = parse_image(4 * 4)(test_image_in()).unwrap(); 21 | assert_eq!(o, test_image_out()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | //! `nom` functions for parsing the WAD format. 2 | 3 | pub mod repr; 4 | 5 | mod color; 6 | mod directory; 7 | mod entry; 8 | mod header; 9 | mod image; 10 | mod palette; 11 | mod texture; 12 | mod wad; 13 | 14 | pub use color::*; 15 | pub use directory::*; 16 | pub use entry::*; 17 | pub use header::*; 18 | pub use image::*; 19 | pub use palette::*; 20 | pub use texture::*; 21 | pub use wad::*; 22 | 23 | use nom::IResult; 24 | 25 | /// Given a length, returns a function that parses a null-terminated string slice from a byte slice. 26 | pub fn parse_byte_string(length: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &str> { 27 | move |i: &[u8]| { 28 | let string_len = i 29 | .iter() 30 | .take(length) 31 | .position(|i| *i == b'\0') 32 | .unwrap_or(length); 33 | 34 | let byte_string = std::str::from_utf8(&i[..string_len]) 35 | .expect("Failed to convert name into a string slice"); 36 | 37 | Ok((&i[length..], byte_string)) 38 | } 39 | } 40 | 41 | /// Implements [`std::convert::TryFrom`] for type `$ty` using parser function `$parser`. 42 | #[macro_export] 43 | macro_rules! impl_try_from { 44 | ($ty:ty, $parser:tt) => { 45 | impl<'a> std::convert::TryFrom<&'a [u8]> for $ty { 46 | type Error = String; 47 | 48 | fn try_from(value: &'a [u8]) -> Result { 49 | match $parser(value) { 50 | Ok((_, o)) => Ok(o), 51 | Err(e) => Err(format!("{}", e)), 52 | } 53 | } 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/parser/palette.rs: -------------------------------------------------------------------------------- 1 | use nom::IResult; 2 | 3 | use crate::{repr::{ColorRgb8, Palette}, impl_try_from}; 4 | use crate::parser::parse_color_rgb8; 5 | 6 | /// Parse a [`Palette`] from a byte slice. 7 | pub fn parse_palette(i: &[u8]) -> IResult<&[u8], Palette> { 8 | let (i, o) = nom::multi::count(parse_color_rgb8, 256)(i)?; 9 | let mut color_arr = [ColorRgb8(0, 0, 0); 256]; 10 | color_arr.copy_from_slice(&o); 11 | Ok((i, Palette(color_arr))) 12 | } 13 | 14 | impl_try_from!(Palette, parse_palette); 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | use crate::test_data::*; 20 | 21 | #[test] 22 | fn test_palette() { 23 | let (_, palette) = parse_palette(test_palette_in()).unwrap(); 24 | assert_eq!(palette, test_palette_out()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/parser/repr/directory.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::parser::repr::Entry; 4 | 5 | /// The set of [`Entry`]s describing the contents of a WAD file. 6 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct Directory<'a>(#[serde(borrow)] pub Vec>); 9 | 10 | impl<'a> Deref for Directory<'a> { 11 | type Target = Vec>; 12 | 13 | fn deref(&self) -> &Self::Target { 14 | &self.0 15 | } 16 | } 17 | 18 | impl<'a> DerefMut for Directory<'a> { 19 | fn deref_mut(&mut self) -> &mut Self::Target { 20 | &mut self.0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/parser/repr/entry/entry_type.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryFrom, fmt::Debug}; 2 | 3 | /// The set of texture variants that can appear in a WAD file. 4 | #[repr(u8)] 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | pub enum EntryType { 8 | Unknown = 0x40, 9 | StatusBar = 0x42, 10 | MipTextureRgb = 0x43, 11 | MipTextureIndexed = 0x44, 12 | ConsolePicture = 0x45, 13 | Font = 0x46, 14 | } 15 | 16 | impl TryFrom for EntryType { 17 | type Error = String; 18 | 19 | fn try_from(value: u8) -> Result { 20 | match value { 21 | 0x40 => Ok(EntryType::Unknown), 22 | 0x42 => Ok(EntryType::StatusBar), 23 | 0x43 => Ok(EntryType::MipTextureRgb), 24 | 0x44 => Ok(EntryType::MipTextureIndexed), 25 | 0x45 => Ok(EntryType::ConsolePicture), 26 | 0x46 => Ok(EntryType::Font), 27 | i => Err(format!("Invalid entry type {}", i)), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/parser/repr/entry/mod.rs: -------------------------------------------------------------------------------- 1 | mod entry_type; 2 | pub use entry_type::*; 3 | 4 | #[cfg(doc)] 5 | use crate::parser::repr::Directory; 6 | 7 | #[cfg(doc)] 8 | use crate::repr::texture::Texture; 9 | 10 | /// An entry in a WAD [`Directory`] containing metadata for a single [`Texture`]. 11 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct Entry<'a> { 14 | pub offset: u32, 15 | pub size_directory: u32, 16 | pub size_memory: u32, 17 | pub entry_type: EntryType, 18 | pub compression: u8, 19 | pub name: &'a str, 20 | } 21 | -------------------------------------------------------------------------------- /src/parser/repr/header.rs: -------------------------------------------------------------------------------- 1 | /// The set of supported WAD formats. 2 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 3 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 | pub enum WadMagic { 5 | Wad2, 6 | Wad3 7 | } 8 | 9 | /// Header describing the format, entry count and dictionary location inside a WAD file. 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 12 | pub struct Header { 13 | pub magic: WadMagic, 14 | pub num_entries: u32, 15 | pub dir_offset: u32, 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/parser/repr/mod.rs: -------------------------------------------------------------------------------- 1 | //! Rust representation for parse-time WAD types. 2 | 3 | mod directory; 4 | mod entry; 5 | mod header; 6 | 7 | pub use directory::*; 8 | pub use entry::*; 9 | pub use header::*; 10 | -------------------------------------------------------------------------------- /src/parser/texture/font.rs: -------------------------------------------------------------------------------- 1 | use nom::IResult; 2 | 3 | use crate::{ 4 | repr::texture::{RowData, Texture, TextureFont}, 5 | parser::parse_image, 6 | }; 7 | 8 | use nom::{ 9 | multi::fill, 10 | number::complete::{le_u16, le_u32}, 11 | sequence::tuple, 12 | }; 13 | 14 | use crate::repr::texture::CharData; 15 | 16 | use super::parse_texture_size; 17 | 18 | /// Parse a [`RowData`] from a byte slice. 19 | pub fn parse_row_data(i: &[u8]) -> IResult<&[u8], RowData> { 20 | let (i, (count, height)) = tuple((le_u32, le_u32))(i)?; 21 | Ok((i, RowData { count, height })) 22 | } 23 | 24 | /// Parse a [`CharData`] from a byte slice. 25 | pub fn parse_char_data_single(i: &[u8]) -> IResult<&[u8], CharData> { 26 | let (i, (offset, width)) = tuple((le_u16, le_u16))(i)?; 27 | Ok((i, CharData { offset, width })) 28 | } 29 | 30 | /// Parse an array of 256 [`CharData`] structs from a byte slice. 31 | pub fn parse_char_data_array(i: &[u8]) -> IResult<&[u8], [CharData; 256]> { 32 | let mut char_data: [CharData; 256] = [Default::default(); 256]; 33 | let (i, _) = fill(parse_char_data_single, &mut char_data)(i)?; 34 | Ok((i, char_data)) 35 | } 36 | 37 | /// Parse a [`Texture::Font`] from a byte slice. 38 | pub fn parse_texture_font(i: &[u8]) -> IResult<&[u8], Texture> { 39 | let (i, size) = parse_texture_size(i)?; 40 | let (i, (row_data, char_data)) = tuple((parse_row_data, parse_char_data_array))(i)?; 41 | let (i, image) = parse_image(size.len_image() as usize)(i)?; 42 | Ok(( 43 | i, 44 | Texture::Font(TextureFont { 45 | size, 46 | row_data, 47 | char_data, 48 | image, 49 | }), 50 | )) 51 | } 52 | -------------------------------------------------------------------------------- /src/parser/texture/mip.rs: -------------------------------------------------------------------------------- 1 | use nom::{IResult, sequence::tuple}; 2 | 3 | use crate::repr::texture::{Mips, QPic, Size, Texture, TextureMipIndexed, TextureMipRgb}; 4 | 5 | use super::{parse_image, parse_palette, parse_texture_size}; 6 | 7 | /// Parse a [`Mips`] from a byte slice. 8 | pub fn parse_mips(size: Size) -> impl Fn(&[u8]) -> IResult<&[u8], Mips> { 9 | move |i: &[u8]| { 10 | let (i, (mip1, mip2, mip3)) = tuple(( 11 | parse_image(size.len_mip1() as usize), 12 | parse_image(size.len_mip2() as usize), 13 | parse_image(size.len_mip3() as usize), 14 | ))(i)?; 15 | 16 | Ok((i, Mips { mip1, mip2, mip3 })) 17 | } 18 | } 19 | 20 | /// Parse a [`Texture::Unknown`] from a byte slice. 21 | pub fn parse_texture_unknown(i: &[u8]) -> IResult<&[u8], Texture> { 22 | let (i, size) = parse_texture_size(&i[16..])?; 23 | let (i, image) = parse_image(size.len_image() as usize)(&i[16..])?; 24 | let (i, mips) = parse_mips(size)(i)?; 25 | Ok(( 26 | i, 27 | Texture::Unknown(TextureMipIndexed { 28 | qpic: QPic { size, image }, 29 | mips, 30 | }), 31 | )) 32 | } 33 | 34 | /// Parse a [`Texture::MipRgb`] from a byte slice. 35 | pub fn parse_texture_mip_rgb(i: &[u8]) -> IResult<&[u8], Texture> { 36 | let (i, size) = parse_texture_size(&i[16..])?; 37 | let (i, image) = parse_image(size.len_image() as usize)(&i[16..])?; 38 | let (i, mips) = parse_mips(size)(i)?; 39 | let (i, palette) = parse_palette(&i[2..])?; 40 | Ok(( 41 | i, 42 | Texture::MipRgb(TextureMipRgb { 43 | texture: TextureMipIndexed { 44 | qpic: QPic { size, image }, 45 | mips, 46 | }, 47 | palette, 48 | }), 49 | )) 50 | } 51 | 52 | /// Parse a [`Texture::MipIndexed`] from a byte slice. 53 | pub fn parse_texture_mip_indexed(i: &[u8]) -> IResult<&[u8], Texture> { 54 | let (i, size) = parse_texture_size(&i[16..])?; 55 | let (i, image) = parse_image(size.len_image() as usize)(&i[16..])?; 56 | let (i, mips) = parse_mips(size)(i)?; 57 | Ok(( 58 | i, 59 | Texture::MipIndexed(TextureMipIndexed { 60 | qpic: QPic { size, image }, 61 | mips, 62 | }), 63 | )) 64 | } 65 | -------------------------------------------------------------------------------- /src/parser/texture/mod.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod mip; 3 | 4 | pub use font::*; 5 | pub use mip::*; 6 | 7 | use nom::{number::complete::le_u32, sequence::tuple, IResult}; 8 | 9 | use crate::{ 10 | repr::texture::{QPic, Size, Texture}, 11 | parser::repr::EntryType, 12 | }; 13 | 14 | use super::{parse_byte_string, parse_image, parse_palette}; 15 | 16 | /// Parse a string slice of up to 16 null-terminated characters from a byte slice. 17 | pub fn parse_texture_name(i: &[u8]) -> IResult<&[u8], &str> { 18 | parse_byte_string(16)(i) 19 | } 20 | 21 | /// Parse a [`Size`] from a byte slice. 22 | pub fn parse_texture_size(i: &[u8]) -> IResult<&[u8], Size> { 23 | let (i, (width, height)) = tuple((le_u32, le_u32))(i)?; 24 | Ok((i, Size { width, height })) 25 | } 26 | 27 | /// Given an [`EntryType`], returns a function that parses a [`Texture`] from a byte slice. 28 | pub fn parse_texture(entry_type: EntryType) -> impl Fn(&[u8]) -> IResult<&[u8], Texture> { 29 | move |i: &[u8]| match entry_type { 30 | EntryType::Unknown => parse_texture_unknown(i), 31 | EntryType::StatusBar => parse_texture_status_bar(i), 32 | EntryType::MipTextureRgb => parse_texture_mip_rgb(i), 33 | EntryType::MipTextureIndexed => parse_texture_mip_indexed(i), 34 | EntryType::ConsolePicture => parse_texture_console_picture(i), 35 | EntryType::Font => parse_texture_font(i), 36 | } 37 | } 38 | 39 | /// Parse a [`Texture::StatusBar`] from a byte slice. 40 | pub fn parse_texture_status_bar(i: &[u8]) -> IResult<&[u8], Texture> { 41 | let (i, size) = parse_texture_size(i)?; 42 | let (i, image) = parse_image(size.len_image() as usize)(i)?; 43 | Ok((i, Texture::StatusBar(QPic { size, image }))) 44 | } 45 | 46 | /// Parse a [`Texture::ConsolePicture`] from a byte slice. 47 | pub fn parse_texture_console_picture(i: &[u8]) -> IResult<&[u8], Texture> { 48 | let (i, size) = parse_texture_size(&i[16..])?; 49 | let (i, image) = parse_image(size.len_image() as usize)(i)?; 50 | Ok((i, Texture::ConsolePicture(QPic { size, image }))) 51 | } 52 | -------------------------------------------------------------------------------- /src/parser/wad.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use nom::IResult; 4 | 5 | use crate::{ 6 | repr::texture::Texture, 7 | parser::{parse_directory, parse_header, parse_texture}, 8 | }; 9 | use crate::{repr::Wad, impl_try_from}; 10 | 11 | /// Parse a [`Wad`] from a byte slice. 12 | pub fn parse_wad(i: &[u8]) -> IResult<&[u8], Wad> { 13 | let header = parse_header(i)?.1; 14 | 15 | let directory = 16 | parse_directory(header.num_entries as usize)(&i[header.dir_offset as usize..])?.1; 17 | 18 | let mut textures: BTreeMap = Default::default(); 19 | 20 | for entry in directory.iter() { 21 | let (_, texture) = parse_texture(entry.entry_type)(&i[entry.offset as usize..])?; 22 | textures.insert(entry.name.to_string(), texture); 23 | } 24 | 25 | Ok((i, Wad::new(textures))) 26 | } 27 | 28 | impl_try_from!(Wad, parse_wad); 29 | -------------------------------------------------------------------------------- /src/parser_async/arc_vec.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryInto, ops::Deref}; 2 | 3 | use async_std::sync::Arc; 4 | 5 | /// [`Arc>`] wrapper. 6 | /// 7 | /// Primarily intended to bridge [`Arc>`] with the `ReadExt` and `SeekExt` traits by way of an `AsRef<[T]>` implementation 8 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 9 | pub struct ArcVec(Arc>); 10 | impl ArcVec { 11 | pub fn new(v: Vec) -> Self { 12 | ArcVec(Arc::new(v)) 13 | } 14 | } 15 | 16 | impl From> for ArcVec { 17 | fn from(v: Vec) -> Self { 18 | ArcVec::new(v) 19 | } 20 | } 21 | 22 | impl TryInto> for ArcVec { 23 | type Error = ArcVec; 24 | 25 | fn try_into(self) -> Result, Self::Error> { 26 | match Arc::try_unwrap(self.0) { 27 | Ok(v) => Ok(v), 28 | Err(e) => Err(ArcVec(e)), 29 | } 30 | } 31 | } 32 | 33 | impl Deref for ArcVec { 34 | type Target = Vec; 35 | 36 | fn deref(&self) -> &Self::Target { 37 | self.0.as_ref() 38 | } 39 | } 40 | 41 | impl AsRef<[T]> for ArcVec { 42 | fn as_ref(&self) -> &[T] { 43 | self.0.as_ref().as_slice() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/parser_async/mod.rs: -------------------------------------------------------------------------------- 1 | //! Parallelized WAD parsing implementation using [`async_std`]. 2 | //! 3 | //! [`parse_wad`] is the primary user-facing entrypoint, and will parse a complete [`Wad`]. 4 | //! 5 | //! For more granular async work, [`parse_textures`] returns a [`TryStream`] of `(String, Texture)` tuples that can be used to chain further computations, 6 | //! such as dumping to a file or conversion into some other format. 7 | //! 8 | //! These functions operate by spawning one [`async_std::task`] per texture, and thus require a way for each task to get its own thread-safe handle to the WAD data. 9 | //! 10 | //! These handles are represented by the [`WadSource`] trait, which is implemented for any type that can read and seek through a source of bytes via [`ReadExt`] and [`SeekExt`]. 11 | //! 12 | //! The [`WadSourceStream`] trait represents a stream that can generate an infinite amount of these handles given some source data, 13 | //! concrete implementations of which are provided via [`SourceStreamSlice`], [`SourceStreamVec`], and [`SourceStreamFile`]. 14 | 15 | #[cfg(doc)] 16 | use async_std::io::{ReadExt, prelude::SeekExt}; 17 | 18 | pub mod parse; 19 | pub mod wad_source; 20 | 21 | mod arc_vec; 22 | 23 | use parse::*; 24 | use wad_source::*; 25 | 26 | use futures::{StreamExt, TryFutureExt, TryStream}; 27 | 28 | use crate::repr::{texture::Texture, Wad}; 29 | 30 | pub type IoError = async_std::io::Error; 31 | 32 | /// [`TryStream`] of `(String, Texture)` pairs. 33 | pub trait TextureStream: TryStream {} 34 | impl TextureStream for T where T: TryStream {} 35 | 36 | /// Async parse a [`Wad`] from a [`WadSourceStream`]. 37 | pub async fn parse_wad(handle_stream: S) -> Result 38 | where 39 | S: 'static + WadSourceStream, 40 | W: 'static + WadSource + Send + Unpin, 41 | { 42 | parse_textures::(handle_stream) 43 | .and_then(Wad::from_stream) 44 | .await 45 | } 46 | 47 | /// Async parse a [`TextureStream`] from a [`WadSourceStream`]. 48 | pub async fn parse_textures(mut s: S) -> Result 49 | where 50 | S: 'static + WadSourceStream, 51 | W: 'static + WadSource + Send + Unpin, 52 | { 53 | let handle = s.next().await.unwrap()?; 54 | let header = parse_header(handle).await?; 55 | let stream = parse_header_textures(s, header).await?; 56 | Ok(stream) 57 | } 58 | -------------------------------------------------------------------------------- /src/parser_async/parse.rs: -------------------------------------------------------------------------------- 1 | //! Internal parsing functions. 2 | 3 | use async_std::{io::SeekFrom, task::JoinHandle}; 4 | use futures::{stream::FuturesUnordered, StreamExt}; 5 | 6 | use crate::repr::texture::Texture; 7 | use crate::parser::{repr::Header, ENTRY_SIZE, HEADER_SIZE}; 8 | 9 | use super::{IoError, WadSource, WadSourceStream}; 10 | 11 | pub type TexStream = FuturesUnordered>>; 12 | 13 | #[cfg(doc)] 14 | use super::TextureStream; 15 | 16 | /// Given a [`WadSourceStream`] and a [`Header`], spawn one [`async_std::task`] per texture and return a [`TextureStream`] of the results. 17 | pub async fn parse_header_textures( 18 | mut s: S, 19 | header: Header, 20 | ) -> Result>>, std::io::Error> 21 | where 22 | S: 'static + WadSourceStream, 23 | W: 'static + WadSource + Send + Unpin, 24 | { 25 | let num_entries = header.num_entries as usize; 26 | let dir_offset = header.dir_offset as usize; 27 | 28 | let futures = FuturesUnordered::default(); 29 | for i in 0..num_entries { 30 | let handle = s.next().await.unwrap()?; 31 | 32 | let entry_offset = i * ENTRY_SIZE; 33 | let ofs = (dir_offset + entry_offset) as u64; 34 | 35 | futures.push(async_std::task::spawn(parse_texture(handle, ofs))); 36 | } 37 | Ok(futures) 38 | } 39 | 40 | /// Read and parse a [`Header`] from a [`WadSource`] handle. 41 | pub async fn parse_header(mut wad_bytes: impl WadSource + Unpin) -> Result { 42 | let mut header_buf = [0u8; HEADER_SIZE]; 43 | wad_bytes.read_exact(&mut header_buf).await?; 44 | 45 | let header = crate::parser::parse_header(&header_buf) 46 | .expect("Invalid WAD header") 47 | .1; 48 | 49 | Ok(header) 50 | } 51 | 52 | /// Read and parse a [`Texture`] from a [`WadSource`] handle. 53 | pub async fn parse_texture( 54 | mut wad_bytes: W, 55 | entry_offset: u64, 56 | ) -> Result<(String, Texture), IoError> 57 | where 58 | W: WadSource + Unpin, 59 | { 60 | let mut entry_buf = [0u8; ENTRY_SIZE]; 61 | wad_bytes.seek(SeekFrom::Start(entry_offset)).await?; 62 | wad_bytes.read_exact(&mut entry_buf).await?; 63 | let entry = crate::parser::parse_entry(&entry_buf) 64 | .expect("Invalid entry") 65 | .1; 66 | 67 | let mut texture_buf = std::iter::repeat(0u8) 68 | .take(entry.size_directory as usize) 69 | .collect::>(); 70 | wad_bytes.seek(SeekFrom::Start(entry.offset as u64)).await?; 71 | wad_bytes.read_exact(&mut texture_buf).await?; 72 | 73 | let name = entry.name.to_string(); 74 | let texture = crate::parser::parse_texture(entry.entry_type)(&texture_buf) 75 | .unwrap_or_else(|e| panic!("Failed to parse texture: {}", e)) 76 | .1; 77 | 78 | Ok((name, texture)) 79 | } 80 | -------------------------------------------------------------------------------- /src/parser_async/wad_source.rs: -------------------------------------------------------------------------------- 1 | //! Machinery for handling WAD data sources. 2 | 3 | use std::{ 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use async_std::{fs::File, io::{prelude::SeekExt, Cursor, ReadExt}, path::PathBuf}; 9 | use futures::{future::BoxFuture, FutureExt, Stream, TryStreamExt}; 10 | 11 | use crate::repr::Wad; 12 | 13 | use super::{arc_vec::ArcVec, IoError, TextureStream}; 14 | 15 | /// A source of WAD bytes that can read and seek. 16 | pub trait WadSource: ReadExt + SeekExt {} 17 | impl WadSource for T where T: ReadExt + SeekExt {} 18 | 19 | /// Async-specific [`Wad`] extension methods. 20 | impl Wad { 21 | /// Constructs a [`Wad`] from a stream of `(String, Texture)` tuples. 22 | pub async fn from_stream(textures: impl TextureStream) -> Result { 23 | textures 24 | .try_fold(Wad::default(), |mut acc, (name, texture)| async move { 25 | acc.insert(name, texture); 26 | Ok(acc) 27 | }) 28 | .await 29 | } 30 | } 31 | 32 | #[cfg(doc)] 33 | use futures::TryStream; 34 | 35 | /// [`TryStream`] of [`WadSource`] handles 36 | pub trait WadSourceStream: 37 | futures::TryStream, Ok = W, Error = IoError> + Unpin 38 | where 39 | W: WadSource, 40 | { 41 | } 42 | 43 | impl WadSourceStream for T 44 | where 45 | T: futures::TryStream, Ok = W, Error = IoError> + Unpin, 46 | W: WadSource, 47 | { 48 | } 49 | 50 | /// [`WadSourceStream`] for generating [`WadSource`] handles from `&'a [u8]` 51 | /// 52 | /// For cases when WAD data is available as a slice, such as via [`std::include_bytes`]. 53 | pub struct SourceStreamSlice<'a>(&'a [u8]); 54 | 55 | impl<'a> From<&'a [u8]> for SourceStreamSlice<'a> { 56 | fn from(slice: &'a [u8]) -> Self { 57 | SourceStreamSlice(slice) 58 | } 59 | } 60 | 61 | impl<'a> Stream for SourceStreamSlice<'a> { 62 | type Item = Result, IoError>; 63 | 64 | fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 65 | Poll::Ready(Some(Ok(Cursor::new(self.0)))) 66 | } 67 | } 68 | 69 | /// [`WadSourceStream`] for generating [`WadSource`] handles from [`Vec`] 70 | /// 71 | /// For cases when WAD data is available as a vector, such as via [`async_std::fs::read`] 72 | pub struct SourceStreamVec(ArcVec); 73 | 74 | impl From> for SourceStreamVec { 75 | fn from(vec: Vec) -> Self { 76 | SourceStreamVec(ArcVec::new(vec)) 77 | } 78 | } 79 | 80 | impl Stream for SourceStreamVec { 81 | type Item = Result>, IoError>; 82 | 83 | fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 84 | Poll::Ready(Some(Ok(Cursor::new(self.0.clone())))) 85 | } 86 | } 87 | 88 | /// [`WadSourceStream`] for generating [`WadSource`] handles from a file path. 89 | /// 90 | /// This gives each texture parsing task its own [`File`] handle under the hood, 91 | /// which is slower than parsing a complete slice or vector of WAD data, but lighter on memory. 92 | pub struct SourceStreamFile { 93 | path: PathBuf, 94 | file_fut: BoxFuture<'static, Result>, 95 | } 96 | 97 | impl From for SourceStreamFile { 98 | fn from(path: PathBuf) -> Self { 99 | let file_fut = File::open(path.clone()).boxed(); 100 | SourceStreamFile { path, file_fut } 101 | } 102 | } 103 | 104 | impl Stream for SourceStreamFile { 105 | type Item = Result; 106 | 107 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 108 | let self_mut = self.get_mut(); 109 | match self_mut.file_fut.poll_unpin(cx) { 110 | Poll::Ready(result) => { 111 | self_mut.file_fut = File::open(self_mut.path.clone()).boxed(); 112 | Poll::Ready(Some(result)) 113 | } 114 | Poll::Pending => Poll::Pending, 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/repr/color.rs: -------------------------------------------------------------------------------- 1 | /// 8-bit indexed color. 2 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 3 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 | pub struct ColorIndexed(pub u8); 5 | 6 | /// 8-bit RGB color. 7 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 9 | pub struct ColorRgb8(pub u8, pub u8, pub u8); 10 | -------------------------------------------------------------------------------- /src/repr/image.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | use crate::repr::{ColorIndexed, ColorRgb8, Palette}; 7 | 8 | /// A list of [`ColorIndexed`] texels. 9 | #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 11 | pub struct Image(pub Vec); 12 | 13 | impl Debug for Image { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | f.debug_struct("Image") 16 | .field("length", &self.len()) 17 | .finish() 18 | } 19 | } 20 | 21 | impl Image { 22 | pub fn new(colors: Vec) -> Image { 23 | Image(colors) 24 | } 25 | 26 | pub fn texels_indexed(&self) -> impl Iterator { 27 | self.iter() 28 | } 29 | 30 | pub fn texels_rgb<'a>(&'a self, palette: &'a Palette) -> impl Iterator { 31 | self.texels_indexed() 32 | .map(move |color| &palette.0[color.0 as usize]) 33 | } 34 | } 35 | 36 | impl Deref for Image { 37 | type Target = Vec; 38 | 39 | fn deref(&self) -> &Self::Target { 40 | &self.0 41 | } 42 | } 43 | 44 | impl DerefMut for Image { 45 | fn deref_mut(&mut self) -> &mut Self::Target { 46 | &mut self.0 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/repr/mod.rs: -------------------------------------------------------------------------------- 1 | //! Rust representation of a WAD file and its contents. 2 | 3 | pub mod texture; 4 | 5 | mod color; 6 | mod image; 7 | mod palette; 8 | mod wad; 9 | 10 | pub use color::*; 11 | pub use image::*; 12 | pub use palette::*; 13 | pub use wad::*; 14 | -------------------------------------------------------------------------------- /src/repr/palette.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use super::ColorRgb8; 4 | 5 | use serde_big_array::BigArray; 6 | 7 | #[cfg(doc)] 8 | use super::ColorIndexed; 9 | 10 | /// An 8-bit palette of [`ColorRgb8`] that can be mapped from a [`ColorIndexed`]. 11 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct Palette(#[serde(with = "BigArray")] pub [ColorRgb8; 256]); 14 | 15 | impl Debug for Palette { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.debug_struct("Palette").finish() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/repr/texture/font.rs: -------------------------------------------------------------------------------- 1 | use crate::repr::{texture::Size, Image}; 2 | 3 | use serde_big_array::BigArray; 4 | 5 | /// Row data for `Font`-type textures. 6 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct RowData { 9 | pub count: u32, 10 | pub height: u32, 11 | } 12 | 13 | /// Character data for `Font`-type textures. 14 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct CharData { 17 | pub offset: u16, 18 | pub width: u16, 19 | } 20 | 21 | /// A WAD font. 22 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 23 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 24 | pub struct TextureFont { 25 | pub size: Size, 26 | pub row_data: RowData, 27 | #[serde(with = "BigArray")] 28 | pub char_data: [CharData; 256], 29 | pub image: Image, 30 | } 31 | -------------------------------------------------------------------------------- /src/repr/texture/mip.rs: -------------------------------------------------------------------------------- 1 | use crate::repr::{Image, Palette, texture::QPic}; 2 | 3 | /// A set of progressively-smaller mipmap [`Image`]s. 4 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 6 | pub struct Mips { 7 | pub mip1: Image, 8 | pub mip2: Image, 9 | pub mip3: Image, 10 | } 11 | 12 | /// A [`QPic`] combined with a set of [`Mips`]. Standard WAD2 texture format. 13 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct TextureMipIndexed { 16 | pub qpic: QPic, 17 | pub mips: Mips, 18 | } 19 | 20 | /// A [`TextureMipIndexed`] combined with a [`Palette`]. Standard WAD3 texture format. 21 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 23 | pub struct TextureMipRgb { 24 | pub texture: TextureMipIndexed, 25 | pub palette: Palette, 26 | } 27 | -------------------------------------------------------------------------------- /src/repr/texture/mod.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod mip; 3 | mod qpic; 4 | mod size; 5 | 6 | pub use font::*; 7 | pub use mip::*; 8 | pub use qpic::*; 9 | pub use size::*; 10 | 11 | use crate::repr::{Image, Palette}; 12 | 13 | /// The set of texture types that can appear in a WAD file. 14 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub enum Texture { 17 | Unknown(TextureMipIndexed), // Can allegedly turn up in tempdecal.wad 18 | StatusBar(QPic), 19 | MipRgb(TextureMipRgb), 20 | MipIndexed(TextureMipIndexed), 21 | ConsolePicture(QPic), 22 | Font(TextureFont), 23 | } 24 | 25 | impl Texture { 26 | pub fn size(&self) -> Size { 27 | match self { 28 | Texture::Unknown(texture) => texture.qpic.size, 29 | Texture::StatusBar(texture) => texture.size, 30 | Texture::MipRgb(texture) => texture.texture.qpic.size, 31 | Texture::MipIndexed(texture) => texture.qpic.size, 32 | Texture::ConsolePicture(texture) => texture.size, 33 | Texture::Font(texture) => texture.size, 34 | } 35 | } 36 | 37 | pub fn image(&self) -> &Image { 38 | match self { 39 | Texture::Unknown(texture) => &texture.qpic.image, 40 | Texture::StatusBar(texture) => &texture.image, 41 | Texture::MipRgb(texture) => &texture.texture.qpic.image, 42 | Texture::MipIndexed(texture) => &texture.qpic.image, 43 | Texture::ConsolePicture(texture) => &texture.image, 44 | Texture::Font(texture) => &texture.image, 45 | } 46 | } 47 | 48 | pub fn palette(&self) -> Option<&Palette> { 49 | match self { 50 | Texture::Unknown(_) => None, 51 | Texture::StatusBar(_) => None, 52 | Texture::MipRgb(texture) => Some(&texture.palette), 53 | Texture::MipIndexed(_) => None, 54 | Texture::ConsolePicture(_) => None, 55 | Texture::Font(_) => None, 56 | } 57 | } 58 | 59 | pub fn mips(&self) -> Option<&Mips> { 60 | match self { 61 | Texture::Unknown(texture) => Some(&texture.mips), 62 | Texture::StatusBar(_) => None, 63 | Texture::MipRgb(texture) => Some(&texture.texture.mips), 64 | Texture::MipIndexed(texture) => Some(&texture.mips), 65 | Texture::ConsolePicture(_) => None, 66 | Texture::Font(_) => None, 67 | } 68 | } 69 | 70 | pub fn font_row_count(&self) -> Option { 71 | if let Texture::Font(texture) = self { 72 | Some(texture.row_data.count) 73 | } else { 74 | None 75 | } 76 | } 77 | 78 | pub fn font_row_height(&self) -> Option { 79 | if let Texture::Font(texture) = self { 80 | Some(texture.row_data.height) 81 | } else { 82 | None 83 | } 84 | } 85 | 86 | pub fn font_char_data(&self) -> Option<&[CharData; 256]> { 87 | if let Texture::Font(texture) = self { 88 | Some(&texture.char_data) 89 | } else { 90 | None 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/repr/texture/qpic.rs: -------------------------------------------------------------------------------- 1 | use crate::repr::{texture::Size, Image}; 2 | 3 | /// An [`Image`] paired with a [`Size`]. Analogous to `qpic_t` in the Quake codebase. 4 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 6 | pub struct QPic { 7 | pub size: Size, 8 | pub image: Image, 9 | } 10 | -------------------------------------------------------------------------------- /src/repr/texture/size.rs: -------------------------------------------------------------------------------- 1 | /// 32-bit width and height. 2 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 3 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 | pub struct Size { 5 | pub width: u32, 6 | pub height: u32, 7 | } 8 | 9 | impl Size { 10 | pub fn len_image(&self) -> u32 { 11 | self.width * self.height 12 | } 13 | 14 | pub fn len_mip1(&self) -> u32 { 15 | self.width / 2 * self.height / 2 16 | } 17 | 18 | pub fn len_mip2(&self) -> u32 { 19 | self.width / 4 * self.height / 4 20 | } 21 | 22 | pub fn len_mip3(&self) -> u32 { 23 | self.width / 8 * self.height / 8 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/repr/wad.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | use super::texture::Texture; 7 | 8 | /// A set of `String` / `Texture` pairs representing the parsed contents of a WAD file. 9 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 11 | pub struct Wad(BTreeMap); 12 | 13 | impl Wad { 14 | pub fn new(textures: BTreeMap) -> Self { 15 | Wad(textures) 16 | } 17 | } 18 | 19 | impl Deref for Wad { 20 | type Target = BTreeMap; 21 | 22 | fn deref(&self) -> &Self::Target { 23 | &self.0 24 | } 25 | } 26 | 27 | impl DerefMut for Wad { 28 | fn deref_mut(&mut self) -> &mut Self::Target { 29 | &mut self.0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test_data.rs: -------------------------------------------------------------------------------- 1 | use crate::repr::{ColorIndexed, ColorRgb8, Image, Palette}; 2 | use crate::parser::repr::{Entry, EntryType, Header, WadMagic}; 3 | 4 | pub fn test_color_indexed_in() -> &'static [u8] { 5 | &[24u8] 6 | } 7 | 8 | pub fn test_color_indexed_out() -> ColorIndexed { 9 | ColorIndexed(24) 10 | } 11 | 12 | pub fn test_color_rgb8_in() -> &'static [u8] { 13 | &[24u8, 56, 92] 14 | } 15 | 16 | pub fn test_color_rgb8_out() -> ColorRgb8 { 17 | ColorRgb8(24, 56, 92) 18 | } 19 | 20 | pub fn test_palette_in() -> &'static [u8] { 21 | include_bytes!("../test_data/palette/quake.lmp") 22 | } 23 | 24 | pub fn test_palette_out() -> Palette { 25 | Palette([ 26 | ColorRgb8(0, 0, 0), 27 | ColorRgb8(15, 15, 15), 28 | ColorRgb8(31, 31, 31), 29 | ColorRgb8(47, 47, 47), 30 | ColorRgb8(63, 63, 63), 31 | ColorRgb8(75, 75, 75), 32 | ColorRgb8(91, 91, 91), 33 | ColorRgb8(107, 107, 107), 34 | ColorRgb8(123, 123, 123), 35 | ColorRgb8(139, 139, 139), 36 | ColorRgb8(155, 155, 155), 37 | ColorRgb8(171, 171, 171), 38 | ColorRgb8(187, 187, 187), 39 | ColorRgb8(203, 203, 203), 40 | ColorRgb8(219, 219, 219), 41 | ColorRgb8(235, 235, 235), 42 | ColorRgb8(15, 11, 7), 43 | ColorRgb8(23, 15, 11), 44 | ColorRgb8(31, 23, 11), 45 | ColorRgb8(39, 27, 15), 46 | ColorRgb8(47, 35, 19), 47 | ColorRgb8(55, 43, 23), 48 | ColorRgb8(63, 47, 23), 49 | ColorRgb8(75, 55, 27), 50 | ColorRgb8(83, 59, 27), 51 | ColorRgb8(91, 67, 31), 52 | ColorRgb8(99, 75, 31), 53 | ColorRgb8(107, 83, 31), 54 | ColorRgb8(115, 87, 31), 55 | ColorRgb8(123, 95, 35), 56 | ColorRgb8(131, 103, 35), 57 | ColorRgb8(143, 111, 35), 58 | ColorRgb8(11, 11, 15), 59 | ColorRgb8(19, 19, 27), 60 | ColorRgb8(27, 27, 39), 61 | ColorRgb8(39, 39, 51), 62 | ColorRgb8(47, 47, 63), 63 | ColorRgb8(55, 55, 75), 64 | ColorRgb8(63, 63, 87), 65 | ColorRgb8(71, 71, 103), 66 | ColorRgb8(79, 79, 115), 67 | ColorRgb8(91, 91, 127), 68 | ColorRgb8(99, 99, 139), 69 | ColorRgb8(107, 107, 151), 70 | ColorRgb8(115, 115, 163), 71 | ColorRgb8(123, 123, 175), 72 | ColorRgb8(131, 131, 187), 73 | ColorRgb8(139, 139, 203), 74 | ColorRgb8(0, 0, 0), 75 | ColorRgb8(7, 7, 0), 76 | ColorRgb8(11, 11, 0), 77 | ColorRgb8(19, 19, 0), 78 | ColorRgb8(27, 27, 0), 79 | ColorRgb8(35, 35, 0), 80 | ColorRgb8(43, 43, 7), 81 | ColorRgb8(47, 47, 7), 82 | ColorRgb8(55, 55, 7), 83 | ColorRgb8(63, 63, 7), 84 | ColorRgb8(71, 71, 7), 85 | ColorRgb8(75, 75, 11), 86 | ColorRgb8(83, 83, 11), 87 | ColorRgb8(91, 91, 11), 88 | ColorRgb8(99, 99, 11), 89 | ColorRgb8(107, 107, 15), 90 | ColorRgb8(7, 0, 0), 91 | ColorRgb8(15, 0, 0), 92 | ColorRgb8(23, 0, 0), 93 | ColorRgb8(31, 0, 0), 94 | ColorRgb8(39, 0, 0), 95 | ColorRgb8(47, 0, 0), 96 | ColorRgb8(55, 0, 0), 97 | ColorRgb8(63, 0, 0), 98 | ColorRgb8(71, 0, 0), 99 | ColorRgb8(79, 0, 0), 100 | ColorRgb8(87, 0, 0), 101 | ColorRgb8(95, 0, 0), 102 | ColorRgb8(103, 0, 0), 103 | ColorRgb8(111, 0, 0), 104 | ColorRgb8(119, 0, 0), 105 | ColorRgb8(127, 0, 0), 106 | ColorRgb8(19, 19, 0), 107 | ColorRgb8(27, 27, 0), 108 | ColorRgb8(35, 35, 0), 109 | ColorRgb8(47, 43, 0), 110 | ColorRgb8(55, 47, 0), 111 | ColorRgb8(67, 55, 0), 112 | ColorRgb8(75, 59, 7), 113 | ColorRgb8(87, 67, 7), 114 | ColorRgb8(95, 71, 7), 115 | ColorRgb8(107, 75, 11), 116 | ColorRgb8(119, 83, 15), 117 | ColorRgb8(131, 87, 19), 118 | ColorRgb8(139, 91, 19), 119 | ColorRgb8(151, 95, 27), 120 | ColorRgb8(163, 99, 31), 121 | ColorRgb8(175, 103, 35), 122 | ColorRgb8(35, 19, 7), 123 | ColorRgb8(47, 23, 11), 124 | ColorRgb8(59, 31, 15), 125 | ColorRgb8(75, 35, 19), 126 | ColorRgb8(87, 43, 23), 127 | ColorRgb8(99, 47, 31), 128 | ColorRgb8(115, 55, 35), 129 | ColorRgb8(127, 59, 43), 130 | ColorRgb8(143, 67, 51), 131 | ColorRgb8(159, 79, 51), 132 | ColorRgb8(175, 99, 47), 133 | ColorRgb8(191, 119, 47), 134 | ColorRgb8(207, 143, 43), 135 | ColorRgb8(223, 171, 39), 136 | ColorRgb8(239, 203, 31), 137 | ColorRgb8(255, 243, 27), 138 | ColorRgb8(11, 7, 0), 139 | ColorRgb8(27, 19, 0), 140 | ColorRgb8(43, 35, 15), 141 | ColorRgb8(55, 43, 19), 142 | ColorRgb8(71, 51, 27), 143 | ColorRgb8(83, 55, 35), 144 | ColorRgb8(99, 63, 43), 145 | ColorRgb8(111, 71, 51), 146 | ColorRgb8(127, 83, 63), 147 | ColorRgb8(139, 95, 71), 148 | ColorRgb8(155, 107, 83), 149 | ColorRgb8(167, 123, 95), 150 | ColorRgb8(183, 135, 107), 151 | ColorRgb8(195, 147, 123), 152 | ColorRgb8(211, 163, 139), 153 | ColorRgb8(227, 179, 151), 154 | ColorRgb8(171, 139, 163), 155 | ColorRgb8(159, 127, 151), 156 | ColorRgb8(147, 115, 135), 157 | ColorRgb8(139, 103, 123), 158 | ColorRgb8(127, 91, 111), 159 | ColorRgb8(119, 83, 99), 160 | ColorRgb8(107, 75, 87), 161 | ColorRgb8(95, 63, 75), 162 | ColorRgb8(87, 55, 67), 163 | ColorRgb8(75, 47, 55), 164 | ColorRgb8(67, 39, 47), 165 | ColorRgb8(55, 31, 35), 166 | ColorRgb8(43, 23, 27), 167 | ColorRgb8(35, 19, 19), 168 | ColorRgb8(23, 11, 11), 169 | ColorRgb8(15, 7, 7), 170 | ColorRgb8(187, 115, 159), 171 | ColorRgb8(175, 107, 143), 172 | ColorRgb8(163, 95, 131), 173 | ColorRgb8(151, 87, 119), 174 | ColorRgb8(139, 79, 107), 175 | ColorRgb8(127, 75, 95), 176 | ColorRgb8(115, 67, 83), 177 | ColorRgb8(107, 59, 75), 178 | ColorRgb8(95, 51, 63), 179 | ColorRgb8(83, 43, 55), 180 | ColorRgb8(71, 35, 43), 181 | ColorRgb8(59, 31, 35), 182 | ColorRgb8(47, 23, 27), 183 | ColorRgb8(35, 19, 19), 184 | ColorRgb8(23, 11, 11), 185 | ColorRgb8(15, 7, 7), 186 | ColorRgb8(219, 195, 187), 187 | ColorRgb8(203, 179, 167), 188 | ColorRgb8(191, 163, 155), 189 | ColorRgb8(175, 151, 139), 190 | ColorRgb8(163, 135, 123), 191 | ColorRgb8(151, 123, 111), 192 | ColorRgb8(135, 111, 95), 193 | ColorRgb8(123, 99, 83), 194 | ColorRgb8(107, 87, 71), 195 | ColorRgb8(95, 75, 59), 196 | ColorRgb8(83, 63, 51), 197 | ColorRgb8(67, 51, 39), 198 | ColorRgb8(55, 43, 31), 199 | ColorRgb8(39, 31, 23), 200 | ColorRgb8(27, 19, 15), 201 | ColorRgb8(15, 11, 7), 202 | ColorRgb8(111, 131, 123), 203 | ColorRgb8(103, 123, 111), 204 | ColorRgb8(95, 115, 103), 205 | ColorRgb8(87, 107, 95), 206 | ColorRgb8(79, 99, 87), 207 | ColorRgb8(71, 91, 79), 208 | ColorRgb8(63, 83, 71), 209 | ColorRgb8(55, 75, 63), 210 | ColorRgb8(47, 67, 55), 211 | ColorRgb8(43, 59, 47), 212 | ColorRgb8(35, 51, 39), 213 | ColorRgb8(31, 43, 31), 214 | ColorRgb8(23, 35, 23), 215 | ColorRgb8(15, 27, 19), 216 | ColorRgb8(11, 19, 11), 217 | ColorRgb8(7, 11, 7), 218 | ColorRgb8(255, 243, 27), 219 | ColorRgb8(239, 223, 23), 220 | ColorRgb8(219, 203, 19), 221 | ColorRgb8(203, 183, 15), 222 | ColorRgb8(187, 167, 15), 223 | ColorRgb8(171, 151, 11), 224 | ColorRgb8(155, 131, 7), 225 | ColorRgb8(139, 115, 7), 226 | ColorRgb8(123, 99, 7), 227 | ColorRgb8(107, 83, 0), 228 | ColorRgb8(91, 71, 0), 229 | ColorRgb8(75, 55, 0), 230 | ColorRgb8(59, 43, 0), 231 | ColorRgb8(43, 31, 0), 232 | ColorRgb8(27, 15, 0), 233 | ColorRgb8(11, 7, 0), 234 | ColorRgb8(0, 0, 255), 235 | ColorRgb8(11, 11, 239), 236 | ColorRgb8(19, 19, 223), 237 | ColorRgb8(27, 27, 207), 238 | ColorRgb8(35, 35, 191), 239 | ColorRgb8(43, 43, 175), 240 | ColorRgb8(47, 47, 159), 241 | ColorRgb8(47, 47, 143), 242 | ColorRgb8(47, 47, 127), 243 | ColorRgb8(47, 47, 111), 244 | ColorRgb8(47, 47, 95), 245 | ColorRgb8(43, 43, 79), 246 | ColorRgb8(35, 35, 63), 247 | ColorRgb8(27, 27, 47), 248 | ColorRgb8(19, 19, 31), 249 | ColorRgb8(11, 11, 15), 250 | ColorRgb8(43, 0, 0), 251 | ColorRgb8(59, 0, 0), 252 | ColorRgb8(75, 7, 0), 253 | ColorRgb8(95, 7, 0), 254 | ColorRgb8(111, 15, 0), 255 | ColorRgb8(127, 23, 7), 256 | ColorRgb8(147, 31, 7), 257 | ColorRgb8(163, 39, 11), 258 | ColorRgb8(183, 51, 15), 259 | ColorRgb8(195, 75, 27), 260 | ColorRgb8(207, 99, 43), 261 | ColorRgb8(219, 127, 59), 262 | ColorRgb8(227, 151, 79), 263 | ColorRgb8(231, 171, 95), 264 | ColorRgb8(239, 191, 119), 265 | ColorRgb8(247, 211, 139), 266 | ColorRgb8(167, 123, 59), 267 | ColorRgb8(183, 155, 55), 268 | ColorRgb8(199, 195, 55), 269 | ColorRgb8(231, 227, 87), 270 | ColorRgb8(127, 191, 255), 271 | ColorRgb8(171, 231, 255), 272 | ColorRgb8(215, 255, 255), 273 | ColorRgb8(103, 0, 0), 274 | ColorRgb8(139, 0, 0), 275 | ColorRgb8(179, 0, 0), 276 | ColorRgb8(215, 0, 0), 277 | ColorRgb8(255, 0, 0), 278 | ColorRgb8(255, 243, 147), 279 | ColorRgb8(255, 247, 199), 280 | ColorRgb8(255, 255, 255), 281 | ColorRgb8(159, 91, 83), 282 | ]) 283 | } 284 | 285 | pub fn test_header_in() -> Vec { 286 | [*b"WAD2", 10u32.to_le_bytes(), 24u32.to_le_bytes()].concat() 287 | } 288 | 289 | pub fn test_header_out() -> Header { 290 | Header { 291 | magic: WadMagic::Wad2, 292 | num_entries: 10, 293 | dir_offset: 24, 294 | } 295 | } 296 | 297 | pub fn test_entry_in() -> Vec { 298 | [ 299 | [ 300 | 200u32.to_le_bytes(), 301 | 16u32.to_le_bytes(), 302 | 32u32.to_le_bytes(), 303 | ] 304 | .concat(), 305 | vec![b'@', 0u8], 306 | 0u16.to_le_bytes().to_vec(), 307 | b"texture\0\0\0\0\0\0\0\0\0".to_vec(), 308 | ] 309 | .concat() 310 | } 311 | 312 | pub fn test_entry_out() -> Entry<'static> { 313 | Entry { 314 | offset: 200, 315 | size_directory: 16, 316 | size_memory: 32, 317 | entry_type: EntryType::Unknown, 318 | compression: 0, 319 | name: "texture", 320 | } 321 | } 322 | 323 | pub fn test_image_in() -> &'static [u8] { 324 | &[0, 2, 4, 6, 8, 10, 12, 16, 18, 20, 22, 24, 26, 28, 30, 32] 325 | } 326 | 327 | pub fn test_image_out() -> Image { 328 | Image( 329 | vec![0, 2, 4, 6, 8, 10, 12, 16, 18, 20, 22, 24, 26, 28, 30, 32] 330 | .into_iter() 331 | .map(ColorIndexed) 332 | .collect::>(), 333 | ) 334 | } 335 | --------------------------------------------------------------------------------