packages feed

rattletrap 3.1.2 → 14.1.4

raw patch · 296 files changed

Files

− CHANGELOG.markdown
@@ -1,7 +0,0 @@-# Change log--Rattletrap uses [Semantic Versioning][].-The change log is available through the [releases on GitHub][].--[Semantic Versioning]: http://semver.org/spec/v2.0.0.html-[releases on GitHub]: https://github.com/tfausak/rattletrap/releases
+ CHANGELOG.md view
@@ -0,0 +1,7 @@+# Change log++Rattletrap uses [Semantic Versioning][].+The change log is available through the [releases on GitHub][].++[Semantic Versioning]: http://semver.org/spec/v2.0.0.html+[releases on GitHub]: https://github.com/tfausak/rattletrap/releases
− LICENSE.markdown
@@ -1,23 +0,0 @@-# [The MIT License (MIT)][]--Copyright (c) Taylor Fausak--Permission is hereby granted, free of charge, to any person obtaining a copy of-this software and associated documentation files (the "Software"), to deal in-the Software without restriction, including without limitation the rights to-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies-of the Software, and to permit persons to whom the Software is furnished to do-so, subject to the following conditions:--The above copyright notice and this permission notice shall be included in all-copies or substantial portions of the Software.--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE-SOFTWARE.--[The MIT License (MIT)]: http://opensource.org/licenses/MIT
+ LICENSE.txt view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2024 Taylor Fausak++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
− README.markdown
@@ -1,79 +0,0 @@-# [Rattletrap][]--[![Version badge][]][version]-[![Windows build badge][]][windows build]-[![Build badge][]][build]--Rattletrap parses and generates [Rocket League][] replays.--## Install--Get Rattletrap by downloading and unpacking [the latest release][] for your-platform.--To build Rattletrap from source, install [Stack][]. Then run-`stack --resolver nightly install rattletrap`.--## Replays--Rocket League saves your replays in a folder that depends on your operating system.--- Windows: `%UserProfile%\Documents\My Games\Rocket League\TAGame\Demos`-- macOS: `$HOME/Library/Application Support/Rocket League/TAGame/Demos`-- Linux: `$HOME/.local/share/Rocket League/TAGame/Demos`--## Parse--Rattletrap can parse (decode) Rocket League replays and output them as JSON.--``` sh-> rattletrap decode http://example.com/input.replay output.json-# or-> rattletrap decode input.replay > output.json-# or-> rattletrap.decode < input.replay > output.json-```--The input argument can either be a local path or a URL.--The resulting JSON is minified, but extremely large. The output can be up to 50-times larger than the input.--## Generate--Rattletrap can also generate (encode) Rocket League replays from JSON files.--``` sh-> rattletrap encode http://example.com/input.json output.replay-# or-> rattletrap encode input.json > output.replay-# or-> rattletrap.encode < input.json > output.replay-```--The input argument can either be a local path or a URL.--If the JSON was generated by Rattletrap, the resulting replay should be-identical to the original.--## Modify--By inserting another program between parsing and generating, Rattletrap can be-used to modify replays.--``` sh-> rattletrap decode < original.replay |-  modify-replay-json |-  rattletrap encode > modified.replay-```--[Rattletrap]: https://github.com/tfausak/rattletrap-[Version badge]: https://www.stackage.org/package/rattletrap/badge/nightly?label=version-[version]: https://www.stackage.org/nightly/package/rattletrap-[Windows build badge]: https://ci.appveyor.com/api/projects/status/github/tfausak/rattletrap?branch=master&svg=true-[windows build]: https://ci.appveyor.com/project/TaylorFausak/rattletrap-[Build badge]: https://travis-ci.org/tfausak/rattletrap.svg?branch=master-[build]: https://travis-ci.org/tfausak/rattletrap-[Rocket League]: https://www.rocketleaguegame.com-[the latest release]: https://github.com/tfausak/rattletrap/releases/latest-[Stack]: https://docs.haskellstack.org/en/stable/README/
+ README.md view
@@ -0,0 +1,135 @@+# [Rattletrap](https://github.com/tfausak/rattletrap)++[![CI](https://github.com/tfausak/rattletrap/actions/workflows/ci.yml/badge.svg)](https://github.com/tfausak/rattletrap/actions/workflows/ci.yml)+[![Hackage](https://badgen.net/hackage/v/rattletrap)](https://hackage.haskell.org/package/rattletrap)++:warning: This package is not maintained anymore.++Rattletrap parses and generates [Rocket League][] replays. Parsing replays can+be used to analyze data in order to collect high-level statistics like players+and points, or low-level details like positions and cameras. Generating replays+can be used to modify replays in order to force everyone into the same car or+change the map a game was played on.++Rattletrap supports every version of Rocket League up to [2.46][], which was+released on 2024-12-03. If a replay can be played by the Rocket League client,+it can be parsed by Rattletrap. (If not, that's a bug. Please report it!)++## Install++Rattletrap is a command-line application. You should only use it if you're+comfortable running things in terminals or command prompts. Otherwise consider+using another tool like [Ball Chasing][].++The best way to get Rattletrap is by downloading [the latest release][] for+your platform.++Rattletrap is written in Haskell. If you'd like to use a program written in+a different language, consider one of the following:++- <https://github.com/jjbott/RocketLeagueReplayParser> (C#)+- <https://github.com/nickbabcock/rrrocket> (Rust)+- <https://github.com/Bakkes/CPPRP> (C++)+- <https://github.com/Drogebot/RocketRP> (C#)++## Replays++Rocket League saves your replays in a folder that depends on your operating+system.++- Windows:+  - `%UserProfile%/Documents/My Games/Rocket League/TAGame/Demos`+  - For example: `C:/Users/Taylor/Documents/My Games/Rocket League/TAGame/Demos`+- macOS:+  - `$HOME/Library/Application Support/Rocket League/TAGame/Demos`+  - For example: `/Users/taylor/Library/Application Support/Rocket League/TAGame/Demos`+- Linux:+  - `$HOME/.local/share/Rocket League/TAGame/Demos`+  - For example: `/home/taylor/.local/share/Rocket League/TAGame/Demos`++## Interface++Rattletrap is a command line application.++``` sh+$ rattletrap --help+```++```+rattletrap version 12.0.0+  -c           --compact         minify JSON output+  -f           --fast            only encode or decode the header+  -h           --help            show the help+  -i FILE|URL  --input=FILE|URL  input file or URL+  -m MODE      --mode=MODE       decode or encode+  -o FILE      --output=FILE     output file+               --schema          output the schema+               --skip-crc        skip the CRC+  -v           --version         show the version+```++By default Rattletrap will try to determine the appropriate mode (either decode+or encode) based on the file extensions of the input or output. You can+override this behavior by passing `--mode` (or `-m`) with either `decode` or+`encode`.++Input extension | Output extension | Mode+---             | ---              | ---+`.replay`       | anything         | `decode` (parse)+`.json`         | anything         | `encode` (generate)+anything        | `.replay`        | `encode` (generate)+anything        | `.json`          | `decode` (parse)+anything        | anything         | `decode` (parse)++## Parse++Rattletrap can parse (decode) Rocket League replays and output them as JSON.++``` sh+$ rattletrap --input http://example.com/input.replay --output output.json+# or+$ rattletrap -i input.replay -o output.json+# or+$ rattletrap < input.replay > output.json+```++The input argument can either be a local path or a URL.++By default the JSON is pretty-printed. To minify the JSON, pass `--compact` (or+`-c`) to Rattletrap. Even when the JSON is minified, it's extremely large. The+output can be up to 100 times larger than the input. For example, a 1.5 MB+replay turns into 31 MB of minified JSON or 159 MB of pretty-printed JSON.++## Generate++Rattletrap can also generate (encode) Rocket League replays from JSON files.++``` sh+$ rattletrap --input http://example.com/input.json --output output.replay+# or+$ rattletrap -i input.json -o output.replay+# or+$ rattletrap --mode encode < input.json > output.replay+```++The input argument can either be a local path or a URL.++If the JSON was generated by Rattletrap, the output replay will be bit-for-bit+identical to the input replay.++## Modify++By inserting another program between parsing and generating, Rattletrap can be+used to modify replays.++``` sh+$ rattletrap -i input.replay |+  modify-replay-json |+  rattletrap -o output.replay+```++[Rattletrap]: https://github.com/tfausak/rattletrap+[Rocket League]: https://www.rocketleague.com+[2.46]: https://www.rocketleague.com/en/news/patch-notes-v2-46+[Ball Chasing]: https://ballchasing.com+[the latest release]: https://github.com/tfausak/rattletrap/releases/latest
− Setup.hs
@@ -1,4 +0,0 @@-import qualified Distribution.Simple as Cabal--main :: IO ()-main = Cabal.defaultMain
− executables/Main.hs
@@ -1,5 +0,0 @@-module Main-  ( module Rattletrap-  ) where--import Rattletrap (main)
− library/Rattletrap.hs
@@ -1,87 +0,0 @@--- | Rattletrap is a tool for parsing and generating Rocket League replays. It--- is typically used as an executable; see "Rattletrap.Main" for details about--- that interface. Using Rattletrap from Haskell is as easy as importing this--- module. Together with "Data.ByteString.Lazy" and "Data.Binary.Get", you can--- decode a 'Replay' value. Then you can transform it into JSON with--- "Data.Aeson". For example:------ @--- let file = "path-to/some.replay"--- bytes <- 'Data.ByteString.Lazy.readFile' file--- let replay = 'Data.Binary.Get.runGet' 'Rattletrap.Replay.getReplay' bytes--- let json = 'Data.Aeson.encode' replay--- 'Data.ByteString.Lazy.putStr' json--- @------ There are a few unusual aspects of Rattletrap to be aware of:------ - In order to improve memory usage, everything Rattletrap does is strict.---   Don't expect any lazy values or evaluation.--- - Some names, like 'Text', collide with commonly-used modules. Use---   qualified imports to avoid collisions.--- - No instances of 'Data.Binary.Binary' are provided. Use functions like---   'getReplay' instead. This was done to improve type inference and make---   profiling easier. Also some types require information that could not be---   provided by the 'Data.Binary.Binary' interface.--- - Importing one specific module is not recommended because all JSON---   instances are defined as orphans in "Rattletrap.Json".-module Rattletrap-  ( module Rattletrap.ActorMap-  , module Rattletrap.Attribute-  , module Rattletrap.AttributeMapping-  , module Rattletrap.AttributeType-  , module Rattletrap.AttributeValue-  , module Rattletrap.Cache-  , module Rattletrap.ClassAttributeMap-  , module Rattletrap.ClassMapping-  , module Rattletrap.Content-  , module Rattletrap.Crc-  , module Rattletrap.Data-  , module Rattletrap.Frame-  , module Rattletrap.Header-  , module Rattletrap.Helper-  , module Rattletrap.Initialization-  , module Rattletrap.KeyFrame-  , module Rattletrap.Main-  , module Rattletrap.Mark-  , module Rattletrap.Message-  , module Rattletrap.Primitive-  , module Rattletrap.Property-  , module Rattletrap.PropertyValue-  , module Rattletrap.RemoteId-  , module Rattletrap.Replay-  , module Rattletrap.Replication-  , module Rattletrap.ReplicationValue-  , module Rattletrap.Utility-  , module Rattletrap.Version-  ) where--import Rattletrap.ActorMap-import Rattletrap.Attribute-import Rattletrap.AttributeMapping-import Rattletrap.AttributeType-import Rattletrap.AttributeValue-import Rattletrap.Cache-import Rattletrap.ClassAttributeMap-import Rattletrap.ClassMapping-import Rattletrap.Content-import Rattletrap.Crc-import Rattletrap.Data-import Rattletrap.Frame-import Rattletrap.Header-import Rattletrap.Helper-import Rattletrap.Initialization-import Rattletrap.Json ()-import Rattletrap.KeyFrame-import Rattletrap.Main-import Rattletrap.Mark-import Rattletrap.Message-import Rattletrap.Primitive-import Rattletrap.Property-import Rattletrap.PropertyValue-import Rattletrap.RemoteId-import Rattletrap.Replay-import Rattletrap.Replication-import Rattletrap.ReplicationValue-import Rattletrap.Utility-import Rattletrap.Version
− library/Rattletrap/ActorMap.hs
@@ -1,21 +0,0 @@-module Rattletrap.ActorMap where--import Rattletrap.Primitive--import qualified Data.Map.Strict as Map--type ActorMap = Map.Map CompressedWord Word32--{-# DEPRECATED-makeActorMap "use Data.Map.Strict.empty"- #-}--makeActorMap :: ActorMap-makeActorMap = Map.empty--{-# DEPRECATED-updateActorMap "use Data.Map.Strict.insert"- #-}--updateActorMap :: CompressedWord -> Word32 -> ActorMap -> ActorMap-updateActorMap = Map.insert
− library/Rattletrap/Attribute.hs
@@ -1,65 +0,0 @@-module Rattletrap.Attribute where--import Rattletrap.ActorMap-import Rattletrap.AttributeValue-import Rattletrap.ClassAttributeMap-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Attribute = Attribute-  { attributeId :: CompressedWord-  , attributeName :: Text-  -- ^ Read-only! Changing an attribute's name requires editing the class-  -- attribute map.-  , attributeValue :: AttributeValue-  } deriving (Eq, Ord, Show)--getAttributes-  :: (Int, Int, Int)-  -> ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> BinaryBit.BitGet [Attribute]-getAttributes version classAttributeMap actorMap actorId = do-  hasAttribute <- BinaryBit.getBool-  if not hasAttribute-    then pure []-    else do-      attribute <- getAttribute version classAttributeMap actorMap actorId-      attributes <- getAttributes version classAttributeMap actorMap actorId-      pure (attribute : attributes)--putAttributes :: [Attribute] -> BinaryBit.BitPut ()-putAttributes attributes = do-  mapM_ putAttribute attributes-  BinaryBit.putBool False--getAttribute-  :: (Int, Int, Int)-  -> ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> BinaryBit.BitGet Attribute-getAttribute version classAttributeMap actorMap actorId =-  case getAttributeMap classAttributeMap actorMap actorId of-    Nothing -> fail ("could not get attribute map for " ++ show actorId)-    Just attributeMap -> case getAttributeIdLimit attributeMap of-      Nothing -> fail ("could not get attribute ID limit for " ++ show actorId)-      Just limit -> do-        id_ <- getCompressedWord limit-        case getAttributeName classAttributeMap attributeMap id_ of-          Nothing -> fail ("could not get attribute name for " ++ show id_)-          Just name -> do-            value <- getAttributeValue-              version-              (classAttributeMapObjectMap classAttributeMap)-              name-            pure (Attribute id_ name value)--putAttribute :: Attribute -> BinaryBit.BitPut ()-putAttribute attribute = do-  BinaryBit.putBool True-  putCompressedWord (attributeId attribute)-  putAttributeValue (attributeValue attribute)
− library/Rattletrap/Attribute/AppliedDamage.hs
@@ -1,28 +0,0 @@-module Rattletrap.Attribute.AppliedDamage where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data AppliedDamageAttribute = AppliedDamageAttribute-  { appliedDamageAttributeUnknown1 :: Word8-  , appliedDamageAttributeLocation :: Vector-  , appliedDamageAttributeUnknown3 :: Int32-  , appliedDamageAttributeUnknown4 :: Int32-  } deriving (Eq, Ord, Show)--getAppliedDamageAttribute :: BinaryBit.BitGet AppliedDamageAttribute-getAppliedDamageAttribute = do-  unknown1 <- getWord8Bits-  location <- getVector-  unknown3 <- getInt32Bits-  unknown4 <- getInt32Bits-  pure (AppliedDamageAttribute unknown1 location unknown3 unknown4)--putAppliedDamageAttribute :: AppliedDamageAttribute -> BinaryBit.BitPut ()-putAppliedDamageAttribute appliedDamageAttribute = do-  putWord8Bits (appliedDamageAttributeUnknown1 appliedDamageAttribute)-  putVector (appliedDamageAttributeLocation appliedDamageAttribute)-  putInt32Bits (appliedDamageAttributeUnknown3 appliedDamageAttribute)-  putInt32Bits (appliedDamageAttributeUnknown4 appliedDamageAttribute)
− library/Rattletrap/Attribute/Boolean.hs
@@ -1,17 +0,0 @@-module Rattletrap.Attribute.Boolean where--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype BooleanAttribute = BooleanAttribute-  { booleanAttributeValue :: Bool-  } deriving (Eq, Ord, Show)--getBooleanAttribute :: BinaryBit.BitGet BooleanAttribute-getBooleanAttribute = do-  value <- BinaryBit.getBool-  pure (BooleanAttribute value)--putBooleanAttribute :: BooleanAttribute -> BinaryBit.BitPut ()-putBooleanAttribute booleanAttribute =-  BinaryBit.putBool (booleanAttributeValue booleanAttribute)
− library/Rattletrap/Attribute/Byte.hs
@@ -1,19 +0,0 @@-module Rattletrap.Attribute.Byte where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype ByteAttribute = ByteAttribute-  { byteAttributeValue :: Word8-  } deriving (Eq, Ord, Show)--getByteAttribute :: BinaryBit.BitGet ByteAttribute-getByteAttribute = do-  value <- getWord8Bits-  pure (ByteAttribute value)--putByteAttribute :: ByteAttribute -> BinaryBit.BitPut ()-putByteAttribute byteAttribute =-  putWord8Bits (byteAttributeValue byteAttribute)
− library/Rattletrap/Attribute/CamSettings.hs
@@ -1,52 +0,0 @@-module Rattletrap.Attribute.CamSettings where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data CamSettingsAttribute = CamSettingsAttribute-  { camSettingsAttributeFov :: Float32-  , camSettingsAttributeHeight :: Float32-  , camSettingsAttributeAngle :: Float32-  , camSettingsAttributeDistance :: Float32-  , camSettingsAttributeStiffness :: Float32-  , camSettingsAttributeSwivelSpeed :: Float32-  , camSettingsAttributeTransitionSpeed :: Maybe Float32-  } deriving (Eq, Ord, Show)--getCamSettingsAttribute :: (Int, Int, Int) -> BinaryBit.BitGet CamSettingsAttribute-getCamSettingsAttribute version = do-  fov <- getFloat32Bits-  height <- getFloat32Bits-  angle <- getFloat32Bits-  distance <- getFloat32Bits-  stiffness <- getFloat32Bits-  swivelSpeed <- getFloat32Bits-  transitionSpeed <- if version >= (868, 20, 0)-    then do-      x <- getFloat32Bits-      pure (Just x)-    else pure Nothing-  pure-    ( CamSettingsAttribute-      fov-      height-      angle-      distance-      stiffness-      swivelSpeed-      transitionSpeed-    )--putCamSettingsAttribute :: CamSettingsAttribute -> BinaryBit.BitPut ()-putCamSettingsAttribute camSettingsAttribute = do-  putFloat32Bits (camSettingsAttributeFov camSettingsAttribute)-  putFloat32Bits (camSettingsAttributeHeight camSettingsAttribute)-  putFloat32Bits (camSettingsAttributeAngle camSettingsAttribute)-  putFloat32Bits (camSettingsAttributeDistance camSettingsAttribute)-  putFloat32Bits (camSettingsAttributeStiffness camSettingsAttribute)-  putFloat32Bits (camSettingsAttributeSwivelSpeed camSettingsAttribute)-  case camSettingsAttributeTransitionSpeed camSettingsAttribute of-    Nothing -> pure ()-    Just transitionSpeed -> putFloat32Bits transitionSpeed
− library/Rattletrap/Attribute/ClubColors.hs
@@ -1,28 +0,0 @@-module Rattletrap.Attribute.ClubColors where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data ClubColorsAttribute = ClubColorsAttribute-  { clubColorsAttributeBlueFlag :: Bool-  , clubColorsAttributeBlueColor :: Word8-  , clubColorsAttributeOrangeFlag :: Bool-  , clubColorsAttributeOrangeColor :: Word8-  } deriving (Eq, Ord, Show)--getClubColorsAttribute :: BinaryBit.BitGet ClubColorsAttribute-getClubColorsAttribute = do-  blueFlag <- BinaryBit.getBool-  blueColor <- getWord8Bits-  orangeFlag <- BinaryBit.getBool-  orangeColor <- getWord8Bits-  pure (ClubColorsAttribute blueFlag blueColor orangeFlag orangeColor)--putClubColorsAttribute :: ClubColorsAttribute -> BinaryBit.BitPut ()-putClubColorsAttribute clubColorsAttribute = do-  BinaryBit.putBool (clubColorsAttributeBlueFlag clubColorsAttribute)-  putWord8Bits (clubColorsAttributeBlueColor clubColorsAttribute)-  BinaryBit.putBool (clubColorsAttributeOrangeFlag clubColorsAttribute)-  putWord8Bits (clubColorsAttributeOrangeColor clubColorsAttribute)
− library/Rattletrap/Attribute/DamageState.hs
@@ -1,35 +0,0 @@-module Rattletrap.Attribute.DamageState where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data DamageStateAttribute = DamageStateAttribute-  { damageStateAttributeUnknown1 :: Word8-  , damageStateAttributeUnknown2 :: Bool-  , damageStateAttributeUnknown3 :: Int32-  , damageStateAttributeUnknown4 :: Vector-  , damageStateAttributeUnknown5 :: Bool-  , damageStateAttributeUnknown6 :: Bool-  } deriving (Eq, Ord, Show)--getDamageStateAttribute :: BinaryBit.BitGet DamageStateAttribute-getDamageStateAttribute = do-  unknown1 <- getWord8Bits-  unknown2 <- BinaryBit.getBool-  unknown3 <- getInt32Bits-  unknown4 <- getVector-  unknown5 <- BinaryBit.getBool-  unknown6 <- BinaryBit.getBool-  pure-    (DamageStateAttribute unknown1 unknown2 unknown3 unknown4 unknown5 unknown6)--putDamageStateAttribute :: DamageStateAttribute -> BinaryBit.BitPut ()-putDamageStateAttribute damageStateAttribute = do-  putWord8Bits (damageStateAttributeUnknown1 damageStateAttribute)-  BinaryBit.putBool (damageStateAttributeUnknown2 damageStateAttribute)-  putInt32Bits (damageStateAttributeUnknown3 damageStateAttribute)-  putVector (damageStateAttributeUnknown4 damageStateAttribute)-  BinaryBit.putBool (damageStateAttributeUnknown5 damageStateAttribute)-  BinaryBit.putBool (damageStateAttributeUnknown6 damageStateAttribute)
− library/Rattletrap/Attribute/Demolish.hs
@@ -1,42 +0,0 @@-module Rattletrap.Attribute.Demolish where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data DemolishAttribute = DemolishAttribute-  { demolishAttributeAttackerFlag :: Bool-  , demolishAttributeAttackerActorId :: Word32-  , demolishAttributeVictimFlag :: Bool-  , demolishAttributeVictimActorId :: Word32-  , demolishAttributeAttackerVelocity :: Vector-  , demolishAttributeVictimVelocity :: Vector-  } deriving (Eq, Ord, Show)--getDemolishAttribute :: BinaryBit.BitGet DemolishAttribute-getDemolishAttribute = do-  attackerFlag <- BinaryBit.getBool-  attackerActorId <- getWord32Bits-  victimFlag <- BinaryBit.getBool-  victimActorId <- getWord32Bits-  attackerVelocity <- getVector-  victimVelocity <- getVector-  pure-    ( DemolishAttribute-      attackerFlag-      attackerActorId-      victimFlag-      victimActorId-      attackerVelocity-      victimVelocity-    )--putDemolishAttribute :: DemolishAttribute -> BinaryBit.BitPut ()-putDemolishAttribute demolishAttribute = do-  BinaryBit.putBool (demolishAttributeAttackerFlag demolishAttribute)-  putWord32Bits (demolishAttributeAttackerActorId demolishAttribute)-  BinaryBit.putBool (demolishAttributeVictimFlag demolishAttribute)-  putWord32Bits (demolishAttributeVictimActorId demolishAttribute)-  putVector (demolishAttributeAttackerVelocity demolishAttribute)-  putVector (demolishAttributeVictimVelocity demolishAttribute)
− library/Rattletrap/Attribute/Enum.hs
@@ -1,18 +0,0 @@-module Rattletrap.Attribute.Enum where--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Word as Word--newtype EnumAttribute = EnumAttribute-  { enumAttributeValue :: Word.Word16-  } deriving (Eq, Ord, Show)--getEnumAttribute :: BinaryBit.BitGet EnumAttribute-getEnumAttribute = do-  value <- BinaryBit.getWord16be 11-  pure (EnumAttribute value)--putEnumAttribute :: EnumAttribute -> BinaryBit.BitPut ()-putEnumAttribute enumAttribute =-  BinaryBit.putWord16be 11 (enumAttributeValue enumAttribute)
− library/Rattletrap/Attribute/Explosion.hs
@@ -1,24 +0,0 @@-module Rattletrap.Attribute.Explosion where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data ExplosionAttribute = ExplosionAttribute-  { explosionAttributeActorId :: Int32-  , explosionAttributeLocation :: Vector-  } deriving (Eq, Ord, Show)--getExplosionAttribute :: BinaryBit.BitGet ExplosionAttribute-getExplosionAttribute = do-  False <- BinaryBit.getBool-  actorId <- getInt32Bits-  location <- getVector-  pure (ExplosionAttribute actorId location)--putExplosionAttribute :: ExplosionAttribute -> BinaryBit.BitPut ()-putExplosionAttribute explosionAttribute = do-  BinaryBit.putBool False-  putInt32Bits (explosionAttributeActorId explosionAttribute)-  putVector (explosionAttributeLocation explosionAttribute)
− library/Rattletrap/Attribute/ExtendedExplosion.hs
@@ -1,32 +0,0 @@-module Rattletrap.Attribute.ExtendedExplosion where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data ExtendedExplosionAttribute = ExtendedExplosionAttribute-  { extendedExplosionAttributeActorId :: Int32-  , extendedExplosionAttributeLocation :: Vector-  , extendedExplosionAttributeUnknown1 :: Bool-  , extendedExplosionAttributeUnknown2 :: Int32-  } deriving (Eq, Ord, Show)--getExtendedExplosionAttribute :: BinaryBit.BitGet ExtendedExplosionAttribute-getExtendedExplosionAttribute = do-  False <- BinaryBit.getBool-  actorId <- getInt32Bits-  location <- getVector-  unknown1 <- BinaryBit.getBool-  unknown2 <- getInt32Bits-  pure (ExtendedExplosionAttribute actorId location unknown1 unknown2)--putExtendedExplosionAttribute-  :: ExtendedExplosionAttribute -> BinaryBit.BitPut ()-putExtendedExplosionAttribute extendedExplosionAttribute = do-  BinaryBit.putBool False-  putInt32Bits (extendedExplosionAttributeActorId extendedExplosionAttribute)-  putVector (extendedExplosionAttributeLocation extendedExplosionAttribute)-  BinaryBit.putBool-    (extendedExplosionAttributeUnknown1 extendedExplosionAttribute)-  putInt32Bits (extendedExplosionAttributeUnknown2 extendedExplosionAttribute)
− library/Rattletrap/Attribute/FlaggedInt.hs
@@ -1,22 +0,0 @@-module Rattletrap.Attribute.FlaggedInt where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data FlaggedIntAttribute = FlaggedIntAttribute-  { flaggedIntAttributeFlag :: Bool-  , flaggedIntAttributeInt :: Int32-  } deriving (Eq, Ord, Show)--getFlaggedIntAttribute :: BinaryBit.BitGet FlaggedIntAttribute-getFlaggedIntAttribute = do-  flag <- BinaryBit.getBool-  int <- getInt32Bits-  pure (FlaggedIntAttribute flag int)--putFlaggedIntAttribute :: FlaggedIntAttribute -> BinaryBit.BitPut ()-putFlaggedIntAttribute flaggedIntAttribute = do-  BinaryBit.putBool (flaggedIntAttributeFlag flaggedIntAttribute)-  putInt32Bits (flaggedIntAttributeInt flaggedIntAttribute)
− library/Rattletrap/Attribute/Float.hs
@@ -1,19 +0,0 @@-module Rattletrap.Attribute.Float where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype FloatAttribute = FloatAttribute-  { floatAttributeValue :: Float32-  } deriving (Eq, Ord, Show)--getFloatAttribute :: BinaryBit.BitGet FloatAttribute-getFloatAttribute = do-  value <- getFloat32Bits-  pure (FloatAttribute value)--putFloatAttribute :: FloatAttribute -> BinaryBit.BitPut ()-putFloatAttribute floatAttribute =-  putFloat32Bits (floatAttributeValue floatAttribute)
− library/Rattletrap/Attribute/GameMode.hs
@@ -1,22 +0,0 @@-module Rattletrap.Attribute.GameMode where--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Word as Word--data GameModeAttribute = GameModeAttribute-  { gameModeAttributeNumBits :: Int-  , gameModeAttributeWord :: Word.Word8-  } deriving (Eq, Ord, Show)--getGameModeAttribute :: (Int, Int, Int) -> BinaryBit.BitGet GameModeAttribute-getGameModeAttribute version = do-  let numBits = if version < (868, 12, 0) then 2 else 8-  word <- BinaryBit.getWord8 numBits-  pure (GameModeAttribute numBits word)--putGameModeAttribute :: GameModeAttribute -> BinaryBit.BitPut ()-putGameModeAttribute gameModeAttribute = do-  let numBits = gameModeAttributeNumBits gameModeAttribute-  let word = gameModeAttributeWord gameModeAttribute-  BinaryBit.putWord8 numBits word
− library/Rattletrap/Attribute/Int.hs
@@ -1,18 +0,0 @@-module Rattletrap.Attribute.Int where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype IntAttribute = IntAttribute-  { intAttributeValue :: Int32-  } deriving (Eq, Ord, Show)--getIntAttribute :: BinaryBit.BitGet IntAttribute-getIntAttribute = do-  value <- getInt32Bits-  pure (IntAttribute value)--putIntAttribute :: IntAttribute -> BinaryBit.BitPut ()-putIntAttribute intAttribute = putInt32Bits (intAttributeValue intAttribute)
− library/Rattletrap/Attribute/Loadout.hs
@@ -1,83 +0,0 @@-module Rattletrap.Attribute.Loadout where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data LoadoutAttribute = LoadoutAttribute-  { loadoutAttributeVersion :: Word8-  , loadoutAttributeBody :: Word32-  , loadoutAttributeDecal :: Word32-  , loadoutAttributeWheels :: Word32-  , loadoutAttributeRocketTrail :: Word32-  -- ^ Now known as "rocket boost".-  , loadoutAttributeAntenna :: Word32-  , loadoutAttributeTopper :: Word32-  , loadoutAttributeUnknown1 :: Word32-  , loadoutAttributeUnknown2 :: Maybe Word32-  , loadoutAttributeEngineAudio :: Maybe Word32-  , loadoutAttributeTrail :: Maybe Word32-  , loadoutAttributeGoalExplosion :: Maybe Word32-  , loadoutAttributeBanner :: Maybe Word32-  } deriving (Eq, Ord, Show)--getLoadoutAttribute :: BinaryBit.BitGet LoadoutAttribute-getLoadoutAttribute = do-  version <- getWord8Bits-  body <- getWord32Bits-  decal <- getWord32Bits-  wheels <- getWord32Bits-  rocketTrail <- getWord32Bits-  antenna <- getWord32Bits-  topper <- getWord32Bits-  unknown1 <- getWord32Bits-  unknown2 <- getOptional (version > Word8 10) getWord32Bits-  engineAudio <- getOptional (version >= Word8 16) getWord32Bits-  trail <- getOptional (version >= Word8 16) getWord32Bits-  goalExplosion <- getOptional (version >= Word8 16) getWord32Bits-  banner <- getOptional (version >= Word8 17) getWord32Bits-  pure-    ( LoadoutAttribute-      version-      body-      decal-      wheels-      rocketTrail-      antenna-      topper-      unknown1-      unknown2-      engineAudio-      trail-      goalExplosion-      banner-    )--getOptional :: Bool -> BinaryBit.BitGet a -> BinaryBit.BitGet (Maybe a)-getOptional p f = if p-  then do-    x <- f-    pure (Just x)-  else pure Nothing--putLoadoutAttribute :: LoadoutAttribute -> BinaryBit.BitPut ()-putLoadoutAttribute loadoutAttribute = do-  putWord8Bits (loadoutAttributeVersion loadoutAttribute)-  putWord32Bits (loadoutAttributeBody loadoutAttribute)-  putWord32Bits (loadoutAttributeDecal loadoutAttribute)-  putWord32Bits (loadoutAttributeWheels loadoutAttribute)-  putWord32Bits (loadoutAttributeRocketTrail loadoutAttribute)-  putWord32Bits (loadoutAttributeAntenna loadoutAttribute)-  putWord32Bits (loadoutAttributeTopper loadoutAttribute)-  putWord32Bits (loadoutAttributeUnknown1 loadoutAttribute)-  putOptional (loadoutAttributeUnknown2 loadoutAttribute) putWord32Bits-  putOptional (loadoutAttributeEngineAudio loadoutAttribute) putWord32Bits-  putOptional (loadoutAttributeTrail loadoutAttribute) putWord32Bits-  putOptional (loadoutAttributeGoalExplosion loadoutAttribute) putWord32Bits-  putOptional (loadoutAttributeBanner loadoutAttribute) putWord32Bits--putOptional :: Maybe a -> (a -> BinaryBit.BitPut ()) -> BinaryBit.BitPut ()-putOptional m f = case m of-  Just x -> f x-  Nothing -> pure ()
− library/Rattletrap/Attribute/LoadoutOnline.hs
@@ -1,30 +0,0 @@-module Rattletrap.Attribute.LoadoutOnline where--import Rattletrap.Attribute.Product-import Rattletrap.Primitive--import qualified Control.Monad as Monad-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Map as Map--newtype LoadoutOnlineAttribute = LoadoutOnlineAttribute-  { loadoutAttributeValue :: [[ProductAttribute]]-  } deriving (Eq, Ord, Show)--getLoadoutOnlineAttribute-  :: (Int, Int, Int)-  -> Map.Map Word32 Text-  -> BinaryBit.BitGet LoadoutOnlineAttribute-getLoadoutOnlineAttribute version objectMap = do-  size <- getWord8Bits-  values <- Monad.replicateM-    (fromIntegral (word8Value size))-    (getProductAttributes version objectMap)-  pure (LoadoutOnlineAttribute values)--putLoadoutOnlineAttribute :: LoadoutOnlineAttribute -> BinaryBit.BitPut ()-putLoadoutOnlineAttribute loadoutAttribute = do-  let attributes = loadoutAttributeValue loadoutAttribute-  putWord8Bits (Word8 (fromIntegral (length attributes)))-  mapM_ putProductAttributes attributes
− library/Rattletrap/Attribute/Loadouts.hs
@@ -1,22 +0,0 @@-module Rattletrap.Attribute.Loadouts where--import Rattletrap.Attribute.Loadout--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data LoadoutsAttribute = LoadoutsAttribute-  { loadoutsAttributeBlue :: LoadoutAttribute-  , loadoutsAttributeOrange :: LoadoutAttribute-  } deriving (Eq, Ord, Show)--getLoadoutsAttribute :: BinaryBit.BitGet LoadoutsAttribute-getLoadoutsAttribute = do-  blue <- getLoadoutAttribute-  orange <- getLoadoutAttribute-  pure (LoadoutsAttribute blue orange)--putLoadoutsAttribute :: LoadoutsAttribute -> BinaryBit.BitPut ()-putLoadoutsAttribute loadoutsAttribute = do-  putLoadoutAttribute (loadoutsAttributeBlue loadoutsAttribute)-  putLoadoutAttribute (loadoutsAttributeOrange loadoutsAttribute)
− library/Rattletrap/Attribute/LoadoutsOnline.hs
@@ -1,35 +0,0 @@-module Rattletrap.Attribute.LoadoutsOnline where--import Rattletrap.Attribute.LoadoutOnline-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Map as Map--data LoadoutsOnlineAttribute = LoadoutsOnlineAttribute-  { loadoutsOnlineAttributeBlue :: LoadoutOnlineAttribute-  , loadoutsOnlineAttributeOrange :: LoadoutOnlineAttribute-  , loadoutsOnlineAttributeUnknown1 :: Bool-  , loadoutsOnlineAttributeUnknown2 :: Bool-  } deriving (Eq, Ord, Show)--getLoadoutsOnlineAttribute-  :: (Int, Int, Int)-  -> Map.Map Word32 Text-  -> BinaryBit.BitGet LoadoutsOnlineAttribute-getLoadoutsOnlineAttribute version objectMap = do-  blueLoadout <- getLoadoutOnlineAttribute version objectMap-  orangeLoadout <- getLoadoutOnlineAttribute version objectMap-  unknown1 <- BinaryBit.getBool-  unknown2 <- BinaryBit.getBool-  pure (LoadoutsOnlineAttribute blueLoadout orangeLoadout unknown1 unknown2)--putLoadoutsOnlineAttribute :: LoadoutsOnlineAttribute -> BinaryBit.BitPut ()-putLoadoutsOnlineAttribute loadoutsOnlineAttribute = do-  putLoadoutOnlineAttribute-    (loadoutsOnlineAttributeBlue loadoutsOnlineAttribute)-  putLoadoutOnlineAttribute-    (loadoutsOnlineAttributeOrange loadoutsOnlineAttribute)-  BinaryBit.putBool (loadoutsOnlineAttributeUnknown1 loadoutsOnlineAttribute)-  BinaryBit.putBool (loadoutsOnlineAttributeUnknown2 loadoutsOnlineAttribute)
− library/Rattletrap/Attribute/Location.hs
@@ -1,19 +0,0 @@-module Rattletrap.Attribute.Location where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype LocationAttribute = LocationAttribute-  { locationAttributeValue :: Vector-  } deriving (Eq, Ord, Show)--getLocationAttribute :: BinaryBit.BitGet LocationAttribute-getLocationAttribute = do-  value <- getVector-  pure (LocationAttribute value)--putLocationAttribute :: LocationAttribute -> BinaryBit.BitPut ()-putLocationAttribute locationAttribute =-  putVector (locationAttributeValue locationAttribute)
− library/Rattletrap/Attribute/MusicStinger.hs
@@ -1,25 +0,0 @@-module Rattletrap.Attribute.MusicStinger where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data MusicStingerAttribute = MusicStingerAttribute-  { musicStingerAttributeFlag :: Bool-  , musicStingerAttributeCue :: Word32-  , musicStingerAttributeTrigger :: Word8-  } deriving (Eq, Ord, Show)--getMusicStingerAttribute :: BinaryBit.BitGet MusicStingerAttribute-getMusicStingerAttribute = do-  flag <- BinaryBit.getBool-  cue <- getWord32Bits-  trigger <- getWord8Bits-  pure (MusicStingerAttribute flag cue trigger)--putMusicStingerAttribute :: MusicStingerAttribute -> BinaryBit.BitPut ()-putMusicStingerAttribute musicStingerAttribute = do-  BinaryBit.putBool (musicStingerAttributeFlag musicStingerAttribute)-  putWord32Bits (musicStingerAttributeCue musicStingerAttribute)-  putWord8Bits (musicStingerAttributeTrigger musicStingerAttribute)
− library/Rattletrap/Attribute/PartyLeader.hs
@@ -1,32 +0,0 @@-module Rattletrap.Attribute.PartyLeader where--import Rattletrap.Primitive-import Rattletrap.RemoteId--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data PartyLeaderAttribute = PartyLeaderAttribute-  { partyLeaderAttributeSystemId :: Word8-  , partyLeaderAttributeId :: Maybe (RemoteId, Word8)-  } deriving (Eq, Ord, Show)--getPartyLeaderAttribute :: (Int, Int, Int) -> BinaryBit.BitGet PartyLeaderAttribute-getPartyLeaderAttribute version = do-  systemId <- getWord8Bits-  maybeRemoteAndLocalId <- if systemId == Word8 0-    then pure Nothing-    else do-      remoteId <- getRemoteId version systemId-      localId <- getWord8Bits-      pure (Just (remoteId, localId))-  pure (PartyLeaderAttribute systemId maybeRemoteAndLocalId)--putPartyLeaderAttribute :: PartyLeaderAttribute -> BinaryBit.BitPut ()-putPartyLeaderAttribute partyLeaderAttribute = do-  putWord8Bits (partyLeaderAttributeSystemId partyLeaderAttribute)-  case partyLeaderAttributeId partyLeaderAttribute of-    Nothing -> pure ()-    Just (remoteId, localId) -> do-      putRemoteId remoteId-      putWord8Bits localId
− library/Rattletrap/Attribute/Pickup.hs
@@ -1,31 +0,0 @@-module Rattletrap.Attribute.Pickup where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data PickupAttribute = PickupAttribute-  { pickupAttributeInstigatorId :: Maybe Word32-  , pickupAttributePickedUp :: Bool-  } deriving (Eq, Ord, Show)--getPickupAttribute :: BinaryBit.BitGet PickupAttribute-getPickupAttribute = do-  instigator <- BinaryBit.getBool-  maybeInstigatorId <- if instigator-    then do-      instigatorId <- getWord32Bits-      pure (Just instigatorId)-    else pure Nothing-  pickedUp <- BinaryBit.getBool-  pure (PickupAttribute maybeInstigatorId pickedUp)--putPickupAttribute :: PickupAttribute -> BinaryBit.BitPut ()-putPickupAttribute pickupAttribute = do-  case pickupAttributeInstigatorId pickupAttribute of-    Nothing -> BinaryBit.putBool False-    Just instigatorId -> do-      BinaryBit.putBool True-      putWord32Bits instigatorId-  BinaryBit.putBool (pickupAttributePickedUp pickupAttribute)
− library/Rattletrap/Attribute/PrivateMatchSettings.hs
@@ -1,50 +0,0 @@-module Rattletrap.Attribute.PrivateMatchSettings where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data PrivateMatchSettingsAttribute = PrivateMatchSettingsAttribute-  { privateMatchSettingsAttributeMutators :: Text-  , privateMatchSettingsAttributeJoinableBy :: Word32-  , privateMatchSettingsAttributeMaxPlayers :: Word32-  , privateMatchSettingsAttributeGameName :: Text-  , privateMatchSettingsAttributePassword :: Text-  , privateMatchSettingsAttributeFlag :: Bool-  } deriving (Eq, Ord, Show)--getPrivateMatchSettingsAttribute-  :: BinaryBit.BitGet PrivateMatchSettingsAttribute-getPrivateMatchSettingsAttribute = do-  mutators <- getTextBits-  joinableBy <- getWord32Bits-  maxPlayers <- getWord32Bits-  gameName <- getTextBits-  password <- getTextBits-  flag <- BinaryBit.getBool-  pure-    ( PrivateMatchSettingsAttribute-      mutators-      joinableBy-      maxPlayers-      gameName-      password-      flag-    )--putPrivateMatchSettingsAttribute-  :: PrivateMatchSettingsAttribute -> BinaryBit.BitPut ()-putPrivateMatchSettingsAttribute privateMatchSettingsAttribute = do-  putTextBits-    (privateMatchSettingsAttributeMutators privateMatchSettingsAttribute)-  putWord32Bits-    (privateMatchSettingsAttributeJoinableBy privateMatchSettingsAttribute)-  putWord32Bits-    (privateMatchSettingsAttributeMaxPlayers privateMatchSettingsAttribute)-  putTextBits-    (privateMatchSettingsAttributeGameName privateMatchSettingsAttribute)-  putTextBits-    (privateMatchSettingsAttributePassword privateMatchSettingsAttribute)-  BinaryBit.putBool-    (privateMatchSettingsAttributeFlag privateMatchSettingsAttribute)
− library/Rattletrap/Attribute/Product.hs
@@ -1,87 +0,0 @@-module Rattletrap.Attribute.Product where--import Rattletrap.Primitive--import qualified Control.Monad as Monad-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Map as Map-import qualified Data.Word as Word--data ProductAttribute = ProductAttribute-  { productAttributeUnknown :: Bool-  , productAttributeObjectId :: Word32-  , productAttributeObjectName :: Maybe Text-  -- ^ read-only-  , productAttributeValue :: Maybe (Either CompressedWord Word.Word32)-  } deriving (Eq, Ord, Show)--getProductAttributes-  :: (Int, Int, Int) -> Map.Map Word32 Text -> BinaryBit.BitGet [ProductAttribute]-getProductAttributes version objectMap = do-  size <- getWord8Bits-  Monad.replicateM-    (fromIntegral (word8Value size))-    (getProductAttribute version objectMap)--getProductAttribute-  :: (Int, Int, Int) -> Map.Map Word32 Text -> BinaryBit.BitGet ProductAttribute-getProductAttribute version objectMap = do-  flag <- BinaryBit.getBool-  objectId <- getWord32Bits-  let objectName = Map.lookup objectId objectMap-  value <- case objectName of-    Just name -> case textToString name of-      "TAGame.ProductAttribute_Painted_TA" -> if version >= (868, 18, 0)-        then do-          x <- BinaryBit.getWord32be 31-          pure (Just (Right x))-        else do-          x <- getCompressedWord 13-          pure (Just (Left x))-      "TAGame.ProductAttribute_UserColor_TA" -> do-        hasValue <- BinaryBit.getBool-        value <- if hasValue-          then do-            x <- BinaryBit.getWord32be 31-            pure (Just (Right x))-          else pure Nothing-        pure value-      _ ->-        fail-          ( "unknown object name "-          ++ show objectName-          ++ " for ID "-          ++ show objectId-          )-    Nothing -> fail ("missing object name for ID " ++ show objectId)-  pure (ProductAttribute flag objectId objectName value)--putProductAttributes :: [ProductAttribute] -> BinaryBit.BitPut ()-putProductAttributes attributes = do-  putWord8Bits (Word8 (fromIntegral (length attributes)))-  mapM_ putProductAttribute attributes--putProductAttribute :: ProductAttribute -> BinaryBit.BitPut ()-putProductAttribute attribute = do-  BinaryBit.putBool (productAttributeUnknown attribute)-  putWord32Bits (productAttributeObjectId attribute)-  case productAttributeObjectName attribute of-    Just name -> case textToString name of-      "TAGame.ProductAttribute_Painted_TA" ->-        case productAttributeValue attribute of-          Nothing -> pure ()-          Just (Left x) -> putCompressedWord x-          Just (Right x) -> BinaryBit.putWord32be 31 x-      "TAGame.ProductAttribute_UserColor_TA" ->-        case productAttributeValue attribute of-          Nothing -> BinaryBit.putBool False-          Just value -> do-            BinaryBit.putBool True-            case value of-              Left x -> putCompressedWord x-              Right x -> BinaryBit.putWord32be 31 x-      _ ->-        fail ("unknown object name for product attribute " ++ show attribute)-    Nothing ->-      fail ("missing object name for product attribute " ++ show attribute)
− library/Rattletrap/Attribute/QWord.hs
@@ -1,19 +0,0 @@-module Rattletrap.Attribute.QWord where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype QWordAttribute = QWordAttribute-  { qWordAttributeValue :: Word64-  } deriving (Eq, Ord, Show)--getQWordAttribute :: BinaryBit.BitGet QWordAttribute-getQWordAttribute = do-  value <- getWord64Bits-  pure (QWordAttribute value)--putQWordAttribute :: QWordAttribute -> BinaryBit.BitPut ()-putQWordAttribute qWordAttribute =-  putWord64Bits (qWordAttributeValue qWordAttribute)
− library/Rattletrap/Attribute/Reservation.hs
@@ -1,48 +0,0 @@-module Rattletrap.Attribute.Reservation where--import Rattletrap.Attribute.UniqueId-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Word as Word--data ReservationAttribute = ReservationAttribute-  { reservationAttributeNumber :: CompressedWord-  , reservationAttributeUniqueId :: UniqueIdAttribute-  , reservationAttributeName :: Maybe Text-  , reservationAttributeUnknown1 :: Bool-  , reservationAttributeUnknown2 :: Bool-  , reservationAttributeUnknown3 :: Maybe Word.Word8-  } deriving (Eq, Ord, Show)--getReservationAttribute :: (Int, Int, Int) -> BinaryBit.BitGet ReservationAttribute-getReservationAttribute version = do-  number <- getCompressedWord 7-  uniqueId <- getUniqueIdAttribute version-  name <- if uniqueIdAttributeSystemId uniqueId == Word8 0-    then pure Nothing-    else do-      name <- getTextBits-      pure (Just name)-  a <- BinaryBit.getBool-  b <- BinaryBit.getBool-  mc <- if version < (868, 12, 0)-    then pure Nothing-    else do-      c <- BinaryBit.getWord8 6-      pure (Just c)-  pure (ReservationAttribute number uniqueId name a b mc)--putReservationAttribute :: ReservationAttribute -> BinaryBit.BitPut ()-putReservationAttribute reservationAttribute = do-  putCompressedWord (reservationAttributeNumber reservationAttribute)-  putUniqueIdAttribute (reservationAttributeUniqueId reservationAttribute)-  case reservationAttributeName reservationAttribute of-    Nothing -> pure ()-    Just name -> putTextBits name-  BinaryBit.putBool (reservationAttributeUnknown1 reservationAttribute)-  BinaryBit.putBool (reservationAttributeUnknown2 reservationAttribute)-  case reservationAttributeUnknown3 reservationAttribute of-    Nothing -> pure ()-    Just c -> BinaryBit.putWord8 6 c
− library/Rattletrap/Attribute/RigidBodyState.hs
@@ -1,51 +0,0 @@-module Rattletrap.Attribute.RigidBodyState where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data RigidBodyStateAttribute = RigidBodyStateAttribute-  { rigidBodyStateAttributeSleeping :: Bool-  , rigidBodyStateAttributeLocation :: Vector-  , rigidBodyStateAttributeRotation :: CompressedWordVector-  , rigidBodyStateAttributeLinearVelocity :: Maybe Vector-  , rigidBodyStateAttributeAngularVelocity :: Maybe Vector-  } deriving (Eq, Ord, Show)--getRigidBodyStateAttribute :: BinaryBit.BitGet RigidBodyStateAttribute-getRigidBodyStateAttribute = do-  sleeping <- BinaryBit.getBool-  location <- getVector-  rotation <- getCompressedWordVector-  linearVelocity <- if sleeping-    then pure Nothing-    else do-      linearVelocity <- getVector-      pure (Just linearVelocity)-  angularVelocity <- if sleeping-    then pure Nothing-    else do-      angularVelocity <- getVector-      pure (Just angularVelocity)-  pure-    ( RigidBodyStateAttribute-      sleeping-      location-      rotation-      linearVelocity-      angularVelocity-    )--putRigidBodyStateAttribute :: RigidBodyStateAttribute -> BinaryBit.BitPut ()-putRigidBodyStateAttribute rigidBodyStateAttribute = do-  BinaryBit.putBool (rigidBodyStateAttributeSleeping rigidBodyStateAttribute)-  putVector (rigidBodyStateAttributeLocation rigidBodyStateAttribute)-  putCompressedWordVector-    (rigidBodyStateAttributeRotation rigidBodyStateAttribute)-  case rigidBodyStateAttributeLinearVelocity rigidBodyStateAttribute of-    Nothing -> pure ()-    Just linearVelocity -> putVector linearVelocity-  case rigidBodyStateAttributeAngularVelocity rigidBodyStateAttribute of-    Nothing -> pure ()-    Just angularVelocity -> putVector angularVelocity
− library/Rattletrap/Attribute/String.hs
@@ -1,19 +0,0 @@-module Rattletrap.Attribute.String where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype StringAttribute = StringAttribute-  { stringAttributeValue :: Text-  } deriving (Eq, Ord, Show)--getStringAttribute :: BinaryBit.BitGet StringAttribute-getStringAttribute = do-  value <- getTextBits-  pure (StringAttribute value)--putStringAttribute :: StringAttribute -> BinaryBit.BitPut ()-putStringAttribute stringAttribute =-  putTextBits (stringAttributeValue stringAttribute)
− library/Rattletrap/Attribute/TeamPaint.hs
@@ -1,38 +0,0 @@-module Rattletrap.Attribute.TeamPaint where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data TeamPaintAttribute = TeamPaintAttribute-  { teamPaintAttributeTeam :: Word8-  , teamPaintAttributePrimaryColor :: Word8-  , teamPaintAttributeAccentColor :: Word8-  , teamPaintAttributePrimaryFinish :: Word32-  , teamPaintAttributeAccentFinish :: Word32-  } deriving (Eq, Ord, Show)--getTeamPaintAttribute :: BinaryBit.BitGet TeamPaintAttribute-getTeamPaintAttribute = do-  team <- getWord8Bits-  primaryColor <- getWord8Bits-  accentColor <- getWord8Bits-  primaryFinish <- getWord32Bits-  accentFinish <- getWord32Bits-  pure-    ( TeamPaintAttribute-      team-      primaryColor-      accentColor-      primaryFinish-      accentFinish-    )--putTeamPaintAttribute :: TeamPaintAttribute -> BinaryBit.BitPut ()-putTeamPaintAttribute teamPaintAttribute = do-  putWord8Bits (teamPaintAttributeTeam teamPaintAttribute)-  putWord8Bits (teamPaintAttributePrimaryColor teamPaintAttribute)-  putWord8Bits (teamPaintAttributeAccentColor teamPaintAttribute)-  putWord32Bits (teamPaintAttributePrimaryFinish teamPaintAttribute)-  putWord32Bits (teamPaintAttributeAccentFinish teamPaintAttribute)
− library/Rattletrap/Attribute/UniqueId.hs
@@ -1,26 +0,0 @@-module Rattletrap.Attribute.UniqueId where--import Rattletrap.Primitive-import Rattletrap.RemoteId--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data UniqueIdAttribute = UniqueIdAttribute-  { uniqueIdAttributeSystemId :: Word8-  , uniqueIdAttributeRemoteId :: RemoteId-  , uniqueIdAttributeLocalId :: Word8-  } deriving (Eq, Ord, Show)--getUniqueIdAttribute :: (Int, Int, Int) -> BinaryBit.BitGet UniqueIdAttribute-getUniqueIdAttribute version = do-  systemId <- getWord8Bits-  remoteId <- getRemoteId version systemId-  localId <- getWord8Bits-  pure (UniqueIdAttribute systemId remoteId localId)--putUniqueIdAttribute :: UniqueIdAttribute -> BinaryBit.BitPut ()-putUniqueIdAttribute uniqueIdAttribute = do-  putWord8Bits (uniqueIdAttributeSystemId uniqueIdAttribute)-  putRemoteId (uniqueIdAttributeRemoteId uniqueIdAttribute)-  putWord8Bits (uniqueIdAttributeLocalId uniqueIdAttribute)
− library/Rattletrap/Attribute/WeldedInfo.hs
@@ -1,31 +0,0 @@-module Rattletrap.Attribute.WeldedInfo where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data WeldedInfoAttribute = WeldedInfoAttribute-  { weldedInfoAttributeActive :: Bool-  , weldedInfoAttributeActorId :: Int32-  , weldedInfoAttributeOffset :: Vector-  , weldedInfoAttributeMass :: Float32-  , weldedInfoAttributeRotation :: Int8Vector-  } deriving (Eq, Ord, Show)--getWeldedInfoAttribute :: BinaryBit.BitGet WeldedInfoAttribute-getWeldedInfoAttribute = do-  active <- BinaryBit.getBool-  actorId <- getInt32Bits-  offset <- getVector-  mass <- getFloat32Bits-  rotation <- getInt8Vector-  pure (WeldedInfoAttribute active actorId offset mass rotation)--putWeldedInfoAttribute :: WeldedInfoAttribute -> BinaryBit.BitPut ()-putWeldedInfoAttribute weldedInfoAttribute = do-  BinaryBit.putBool (weldedInfoAttributeActive weldedInfoAttribute)-  putInt32Bits (weldedInfoAttributeActorId weldedInfoAttribute)-  putVector (weldedInfoAttributeOffset weldedInfoAttribute)-  putFloat32Bits (weldedInfoAttributeMass weldedInfoAttribute)-  putInt8Vector (weldedInfoAttributeRotation weldedInfoAttribute)
− library/Rattletrap/AttributeMapping.hs
@@ -1,21 +0,0 @@-module Rattletrap.AttributeMapping where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data AttributeMapping = AttributeMapping-  { attributeMappingObjectId :: Word32-  , attributeMappingStreamId :: Word32-  } deriving (Eq, Ord, Show)--getAttributeMapping :: Binary.Get AttributeMapping-getAttributeMapping = do-  objectId <- getWord32-  streamId <- getWord32-  pure (AttributeMapping objectId streamId)--putAttributeMapping :: AttributeMapping -> Binary.Put-putAttributeMapping attributeMapping = do-  putWord32 (attributeMappingObjectId attributeMapping)-  putWord32 (attributeMappingStreamId attributeMapping)
− library/Rattletrap/AttributeType.hs
@@ -1,34 +0,0 @@-module Rattletrap.AttributeType where--data AttributeType-  = AppliedDamageAttributeType-  | BooleanAttributeType-  | ByteAttributeType-  | CamSettingsAttributeType-  | ClubColorsAttributeType-  | DamageStateAttributeType-  | DemolishAttributeType-  | EnumAttributeType-  | ExplosionAttributeType-  | ExtendedExplosionAttributeType-  | FlaggedIntAttributeType-  | FloatAttributeType-  | GameModeAttributeType-  | IntAttributeType-  | LoadoutAttributeType-  | LoadoutOnlineAttributeType-  | LoadoutsAttributeType-  | LoadoutsOnlineAttributeType-  | LocationAttributeType-  | MusicStingerAttributeType-  | PartyLeaderAttributeType-  | PickupAttributeType-  | PrivateMatchSettingsAttributeType-  | QWordAttributeType-  | ReservationAttributeType-  | RigidBodyStateAttributeType-  | StringAttributeType-  | TeamPaintAttributeType-  | UniqueIdAttributeType-  | WeldedInfoAttributeType-  deriving (Eq, Ord, Show)
− library/Rattletrap/AttributeValue.hs
@@ -1,242 +0,0 @@-module Rattletrap.AttributeValue-  ( module Rattletrap.AttributeValue-  , module Rattletrap.Attribute.AppliedDamage-  , module Rattletrap.Attribute.Boolean-  , module Rattletrap.Attribute.Byte-  , module Rattletrap.Attribute.CamSettings-  , module Rattletrap.Attribute.ClubColors-  , module Rattletrap.Attribute.DamageState-  , module Rattletrap.Attribute.Demolish-  , module Rattletrap.Attribute.Enum-  , module Rattletrap.Attribute.Explosion-  , module Rattletrap.Attribute.ExtendedExplosion-  , module Rattletrap.Attribute.FlaggedInt-  , module Rattletrap.Attribute.Float-  , module Rattletrap.Attribute.GameMode-  , module Rattletrap.Attribute.Int-  , module Rattletrap.Attribute.Loadout-  , module Rattletrap.Attribute.LoadoutOnline-  , module Rattletrap.Attribute.Loadouts-  , module Rattletrap.Attribute.LoadoutsOnline-  , module Rattletrap.Attribute.Location-  , module Rattletrap.Attribute.MusicStinger-  , module Rattletrap.Attribute.PartyLeader-  , module Rattletrap.Attribute.Pickup-  , module Rattletrap.Attribute.PrivateMatchSettings-  , module Rattletrap.Attribute.Product-  , module Rattletrap.Attribute.QWord-  , module Rattletrap.Attribute.Reservation-  , module Rattletrap.Attribute.RigidBodyState-  , module Rattletrap.Attribute.String-  , module Rattletrap.Attribute.TeamPaint-  , module Rattletrap.Attribute.UniqueId-  , module Rattletrap.Attribute.WeldedInfo-  ) where--import Rattletrap.Attribute.AppliedDamage-import Rattletrap.Attribute.Boolean-import Rattletrap.Attribute.Byte-import Rattletrap.Attribute.CamSettings-import Rattletrap.Attribute.ClubColors-import Rattletrap.Attribute.DamageState-import Rattletrap.Attribute.Demolish-import Rattletrap.Attribute.Enum-import Rattletrap.Attribute.Explosion-import Rattletrap.Attribute.ExtendedExplosion-import Rattletrap.Attribute.FlaggedInt-import Rattletrap.Attribute.Float-import Rattletrap.Attribute.GameMode-import Rattletrap.Attribute.Int-import Rattletrap.Attribute.Loadout-import Rattletrap.Attribute.LoadoutOnline-import Rattletrap.Attribute.Loadouts-import Rattletrap.Attribute.LoadoutsOnline-import Rattletrap.Attribute.Location-import Rattletrap.Attribute.MusicStinger-import Rattletrap.Attribute.PartyLeader-import Rattletrap.Attribute.Pickup-import Rattletrap.Attribute.PrivateMatchSettings-import Rattletrap.Attribute.Product-import Rattletrap.Attribute.QWord-import Rattletrap.Attribute.Reservation-import Rattletrap.Attribute.RigidBodyState-import Rattletrap.Attribute.String-import Rattletrap.Attribute.TeamPaint-import Rattletrap.Attribute.UniqueId-import Rattletrap.Attribute.WeldedInfo-import Rattletrap.AttributeType-import Rattletrap.Data-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Map.Strict as Map--data AttributeValue-  = AppliedDamageAttributeValue AppliedDamageAttribute-  | BooleanAttributeValue BooleanAttribute-  | ByteAttributeValue ByteAttribute-  | CamSettingsAttributeValue CamSettingsAttribute-  | ClubColorsAttributeValue ClubColorsAttribute-  | DamageStateAttributeValue DamageStateAttribute-  | DemolishAttributeValue DemolishAttribute-  | EnumAttributeValue EnumAttribute-  | ExplosionAttributeValue ExplosionAttribute-  | ExtendedExplosionAttributeValue ExtendedExplosionAttribute-  | FlaggedIntAttributeValue FlaggedIntAttribute-  | FloatAttributeValue FloatAttribute-  | GameModeAttributeValue GameModeAttribute-  | IntAttributeValue IntAttribute-  | LoadoutAttributeValue LoadoutAttribute-  | LoadoutOnlineAttributeValue LoadoutOnlineAttribute-  | LoadoutsAttributeValue LoadoutsAttribute-  | LoadoutsOnlineAttributeValue LoadoutsOnlineAttribute-  | LocationAttributeValue LocationAttribute-  | MusicStingerAttributeValue MusicStingerAttribute-  | PartyLeaderAttributeValue PartyLeaderAttribute-  | PickupAttributeValue PickupAttribute-  | PrivateMatchSettingsAttributeValue PrivateMatchSettingsAttribute-  | QWordAttributeValue QWordAttribute-  | ReservationAttributeValue ReservationAttribute-  | RigidBodyStateAttributeValue RigidBodyStateAttribute-  | StringAttributeValue StringAttribute-  | TeamPaintAttributeValue TeamPaintAttribute-  | UniqueIdAttributeValue UniqueIdAttribute-  | WeldedInfoAttributeValue WeldedInfoAttribute-  deriving (Eq, Ord, Show)--getAttributeValue-  :: (Int, Int, Int)-  -> Map.Map Word32 Text-  -> Text-  -> BinaryBit.BitGet AttributeValue-getAttributeValue version objectMap name =-  case Map.lookup name attributeTypes of-    Just constructor -> case constructor of-      AppliedDamageAttributeType -> do-        x <- getAppliedDamageAttribute-        pure (AppliedDamageAttributeValue x)-      BooleanAttributeType -> do-        x <- getBooleanAttribute-        pure (BooleanAttributeValue x)-      ByteAttributeType -> do-        x <- getByteAttribute-        pure (ByteAttributeValue x)-      CamSettingsAttributeType -> do-        x <- getCamSettingsAttribute version-        pure (CamSettingsAttributeValue x)-      ClubColorsAttributeType -> do-        x <- getClubColorsAttribute-        pure (ClubColorsAttributeValue x)-      DamageStateAttributeType -> do-        x <- getDamageStateAttribute-        pure (DamageStateAttributeValue x)-      DemolishAttributeType -> do-        x <- getDemolishAttribute-        pure (DemolishAttributeValue x)-      EnumAttributeType -> do-        x <- getEnumAttribute-        pure (EnumAttributeValue x)-      ExplosionAttributeType -> do-        x <- getExplosionAttribute-        pure (ExplosionAttributeValue x)-      ExtendedExplosionAttributeType -> do-        x <- getExtendedExplosionAttribute-        pure (ExtendedExplosionAttributeValue x)-      FlaggedIntAttributeType -> do-        x <- getFlaggedIntAttribute-        pure (FlaggedIntAttributeValue x)-      FloatAttributeType -> do-        x <- getFloatAttribute-        pure (FloatAttributeValue x)-      GameModeAttributeType -> do-        x <- getGameModeAttribute version-        pure (GameModeAttributeValue x)-      IntAttributeType -> do-        x <- getIntAttribute-        pure (IntAttributeValue x)-      LoadoutAttributeType -> do-        x <- getLoadoutAttribute-        pure (LoadoutAttributeValue x)-      LoadoutOnlineAttributeType -> do-        x <- getLoadoutOnlineAttribute version objectMap-        pure (LoadoutOnlineAttributeValue x)-      LoadoutsAttributeType -> do-        x <- getLoadoutsAttribute-        pure (LoadoutsAttributeValue x)-      LoadoutsOnlineAttributeType -> do-        x <- getLoadoutsOnlineAttribute version objectMap-        pure (LoadoutsOnlineAttributeValue x)-      LocationAttributeType -> do-        x <- getLocationAttribute-        pure (LocationAttributeValue x)-      MusicStingerAttributeType -> do-        x <- getMusicStingerAttribute-        pure (MusicStingerAttributeValue x)-      PartyLeaderAttributeType -> do-        x <- getPartyLeaderAttribute version-        pure (PartyLeaderAttributeValue x)-      PickupAttributeType -> do-        x <- getPickupAttribute-        pure (PickupAttributeValue x)-      PrivateMatchSettingsAttributeType -> do-        x <- getPrivateMatchSettingsAttribute-        pure (PrivateMatchSettingsAttributeValue x)-      QWordAttributeType -> do-        x <- getQWordAttribute-        pure (QWordAttributeValue x)-      ReservationAttributeType -> do-        x <- getReservationAttribute version-        pure (ReservationAttributeValue x)-      RigidBodyStateAttributeType -> do-        x <- getRigidBodyStateAttribute-        pure (RigidBodyStateAttributeValue x)-      StringAttributeType -> do-        x <- getStringAttribute-        pure (StringAttributeValue x)-      TeamPaintAttributeType -> do-        x <- getTeamPaintAttribute-        pure (TeamPaintAttributeValue x)-      UniqueIdAttributeType -> do-        x <- getUniqueIdAttribute version-        pure (UniqueIdAttributeValue x)-      WeldedInfoAttributeType -> do-        x <- getWeldedInfoAttribute-        pure (WeldedInfoAttributeValue x)-    Nothing -> fail ("don't know how to get attribute value " ++ show name)--attributeTypes :: Map.Map Text AttributeType-attributeTypes = Map.mapKeys stringToText (Map.fromList rawAttributeTypes)--putAttributeValue :: AttributeValue -> BinaryBit.BitPut ()-putAttributeValue value = case value of-  AppliedDamageAttributeValue x -> putAppliedDamageAttribute x-  BooleanAttributeValue x -> putBooleanAttribute x-  ByteAttributeValue x -> putByteAttribute x-  CamSettingsAttributeValue x -> putCamSettingsAttribute x-  ClubColorsAttributeValue x -> putClubColorsAttribute x-  DamageStateAttributeValue x -> putDamageStateAttribute x-  DemolishAttributeValue x -> putDemolishAttribute x-  EnumAttributeValue x -> putEnumAttribute x-  ExplosionAttributeValue x -> putExplosionAttribute x-  ExtendedExplosionAttributeValue x -> putExtendedExplosionAttribute x-  FlaggedIntAttributeValue x -> putFlaggedIntAttribute x-  FloatAttributeValue x -> putFloatAttribute x-  GameModeAttributeValue x -> putGameModeAttribute x-  IntAttributeValue x -> putIntAttribute x-  LoadoutAttributeValue x -> putLoadoutAttribute x-  LoadoutOnlineAttributeValue x -> putLoadoutOnlineAttribute x-  LoadoutsAttributeValue x -> putLoadoutsAttribute x-  LoadoutsOnlineAttributeValue x -> putLoadoutsOnlineAttribute x-  LocationAttributeValue x -> putLocationAttribute x-  MusicStingerAttributeValue x -> putMusicStingerAttribute x-  PartyLeaderAttributeValue x -> putPartyLeaderAttribute x-  PickupAttributeValue x -> putPickupAttribute x-  PrivateMatchSettingsAttributeValue x -> putPrivateMatchSettingsAttribute x-  QWordAttributeValue x -> putQWordAttribute x-  ReservationAttributeValue x -> putReservationAttribute x-  RigidBodyStateAttributeValue x -> putRigidBodyStateAttribute x-  StringAttributeValue x -> putStringAttribute x-  TeamPaintAttributeValue x -> putTeamPaintAttribute x-  UniqueIdAttributeValue x -> putUniqueIdAttribute x-  WeldedInfoAttributeValue x -> putWeldedInfoAttribute x
− library/Rattletrap/Cache.hs
@@ -1,28 +0,0 @@-module Rattletrap.Cache where--import Rattletrap.AttributeMapping-import Rattletrap.Primitive--import qualified Data.Binary as Binary--data Cache = Cache-  { cacheClassId :: Word32-  , cacheParentCacheId :: Word32-  , cacheCacheId :: Word32-  , cacheAttributeMappings :: List AttributeMapping-  } deriving (Eq, Ord, Show)--getCache :: Binary.Get Cache-getCache = do-  classId <- getWord32-  parentCacheId <- getWord32-  cacheId <- getWord32-  attributeMappings <- getList getAttributeMapping-  pure (Cache classId parentCacheId cacheId attributeMappings)--putCache :: Cache -> Binary.Put-putCache cache = do-  putWord32 (cacheClassId cache)-  putWord32 (cacheParentCacheId cache)-  putWord32 (cacheCacheId cache)-  putList putAttributeMapping (cacheAttributeMappings cache)
− library/Rattletrap/ClassAttributeMap.hs
@@ -1,300 +0,0 @@-module Rattletrap.ClassAttributeMap where--import Rattletrap.ActorMap-import Rattletrap.AttributeMapping-import Rattletrap.Cache-import Rattletrap.ClassMapping-import Rattletrap.Data-import Rattletrap.Primitive--import qualified Data.Bimap as Bimap-import qualified Data.IntMap.Strict as IntMap-import qualified Data.List as List-import qualified Data.Map.Strict as Map-import qualified Data.Maybe as Maybe-import qualified Data.Set as Set-import qualified Data.Text as Text---- | This data structure holds all the information about classes, objects, and--- attributes in the replay. The class hierarchy is not fixed; it is encoded--- in the 'Rattletrap.Content.Content'. Similarly, the attributes that belong--- to each class are not fixed either. Converting the raw data into a usable--- structure is tedious; see 'makeClassAttributeMap'.-data ClassAttributeMap = ClassAttributeMap-  { classAttributeMapObjectMap :: Map.Map Word32 Text-  -- ^ A map from object IDs to their names.-  , classAttributeMapObjectClassMap :: Map.Map Word32 Word32-  -- ^ A map from object IDs to their class IDs.-  , classAttributeMapValue :: Map.Map Word32 (Map.Map Word32 Word32)-  -- ^ A map from class IDs to a map from attribute stream IDs to attribute-  -- IDs.-  , classAttributeMapNameMap :: IntMap.IntMap Text-  } deriving (Eq, Ord, Show)---- | Makes a 'ClassAttributeMap' given the necessary fields from the--- 'Rattletrap.Content.Content'.-makeClassAttributeMap-  :: List Text-  -- ^ From 'Rattletrap.Content.contentObjects'.-  -> List ClassMapping-  -- ^ From 'Rattletrap.Content.contentClassMappings'.-  -> List Cache-  -- ^ From 'Rattletrap.Content.contentCaches'.-  -> List Text-  -- ^ From 'Rattletrap.Content.contentNames'.-  -> ClassAttributeMap-makeClassAttributeMap objects classMappings caches names =-  let-    objectMap = makeObjectMap objects-    classMap = makeClassMap classMappings-    objectClassMap = makeObjectClassMap objectMap classMap-    classCache = makeClassCache classMap caches-    attributeMap = makeAttributeMap caches-    classIds = map (\(_, classId, _, _) -> classId) classCache-    parentMap = makeParentMap classCache-    value = Map.fromList-      ( map-        ( \classId ->-          let-            ownAttributes =-              Maybe.fromMaybe Map.empty (Map.lookup classId attributeMap)-            parentsAttributes = case Map.lookup classId parentMap of-              Nothing -> []-              Just parentClassIds -> map-                ( \parentClassId -> Maybe.fromMaybe-                  Map.empty-                  (Map.lookup parentClassId attributeMap)-                )-                parentClassIds-            attributes = ownAttributes : parentsAttributes-          in (classId, Map.fromList (concatMap Map.toList attributes))-        )-        classIds-      )-    nameMap = makeNameMap names-  in ClassAttributeMap objectMap objectClassMap value nameMap--makeNameMap :: List Text -> IntMap.IntMap Text-makeNameMap names = IntMap.fromDistinctAscList (zip [0 ..] (listValue names))--getName :: IntMap.IntMap Text -> Word32 -> Maybe Text-getName nameMap nameIndex =-  IntMap.lookup (fromIntegral (word32Value nameIndex)) nameMap--makeObjectClassMap-  :: Map.Map Word32 Text -> Bimap.Bimap Word32 Text -> Map.Map Word32 Word32-makeObjectClassMap objectMap classMap = do-  let objectIds = Map.keys objectMap-  let classIds = map (getClassId objectMap classMap) objectIds-  let rawPairs = zip objectIds classIds-  let-    pairs = Maybe.mapMaybe-      ( \(objectId, maybeClassId) -> case maybeClassId of-        Nothing -> Nothing-        Just classId -> Just (objectId, classId)-      )-      rawPairs-  Map.fromList pairs--getClassId-  :: Map.Map Word32 Text -> Bimap.Bimap Word32 Text -> Word32 -> Maybe Word32-getClassId objectMap classMap objectId = do-  objectName <- getObjectName objectMap objectId-  className <- getClassName objectName-  Bimap.lookupR className classMap--makeClassCache-  :: Bimap.Bimap Word32 Text-  -> List Cache-  -> [(Maybe Text, Word32, Word32, Word32)]-makeClassCache classMap caches = map-  ( \cache ->-    let-      classId = cacheClassId cache-    in-      ( Bimap.lookup classId classMap-      , classId-      , cacheCacheId cache-      , cacheParentCacheId cache-      )-  )-  (listValue caches)--makeClassMap :: List ClassMapping -> Bimap.Bimap Word32 Text-makeClassMap classMappings = Bimap.fromList-  ( map-    ( \classMapping ->-      (classMappingStreamId classMapping, classMappingName classMapping)-    )-    (listValue classMappings)-  )--makeAttributeMap :: List Cache -> Map.Map Word32 (Map.Map Word32 Word32)-makeAttributeMap caches = Map.fromList-  ( map-    ( \cache ->-      ( cacheClassId cache-      , Map.fromList-        ( map-          ( \attributeMapping ->-            ( attributeMappingStreamId attributeMapping-            , attributeMappingObjectId attributeMapping-            )-          )-          (listValue (cacheAttributeMappings cache))-        )-      )-    )-    (listValue caches)-  )--makeShallowParentMap-  :: [(Maybe Text, Word32, Word32, Word32)] -> Map.Map Word32 Word32-makeShallowParentMap classCache = Map.fromList-  ( Maybe.mapMaybe-    ( \xs -> case xs of-      [] -> Nothing-      (maybeClassName, classId, _, parentCacheId):rest -> do-        parentClassId <- getParentClass maybeClassName parentCacheId rest-        pure (classId, parentClassId)-    )-    (List.tails (reverse classCache))-  )--makeParentMap-  :: [(Maybe Text, Word32, Word32, Word32)] -> Map.Map Word32 [Word32]-makeParentMap classCache =-  let-    shallowParentMap = makeShallowParentMap classCache-  in-    Map.mapWithKey-      (\classId _ -> getParentClasses shallowParentMap classId)-      shallowParentMap--getParentClasses :: Map.Map Word32 Word32 -> Word32 -> [Word32]-getParentClasses shallowParentMap classId =-  case Map.lookup classId shallowParentMap of-    Nothing -> []-    Just parentClassId ->-      parentClassId : getParentClasses shallowParentMap parentClassId--getParentClass-  :: Maybe Text-  -> Word32-  -> [(Maybe Text, Word32, Word32, Word32)]-  -> Maybe Word32-getParentClass maybeClassName parentCacheId xs = case maybeClassName of-  Nothing -> getParentClassById parentCacheId xs-  Just className -> getParentClassByName className parentCacheId xs--getParentClassById-  :: Word32 -> [(Maybe Text, Word32, Word32, Word32)] -> Maybe Word32-getParentClassById parentCacheId xs =-  case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of-    [] -> if parentCacheId == Word32 0-      then Nothing-      else getParentClassById (Word32 (word32Value parentCacheId - 1)) xs-    (_, parentClassId, _, _):_ -> Just parentClassId--getParentClassByName-  :: Text -> Word32 -> [(Maybe Text, Word32, Word32, Word32)] -> Maybe Word32-getParentClassByName className parentCacheId xs =-  case Map.lookup className parentClasses of-    Nothing -> getParentClassById parentCacheId xs-    Just parentClassName -> Maybe.maybe-      (getParentClassById parentCacheId xs)-      Just-      ( Maybe.listToMaybe-        ( map-          (\(_, parentClassId, _, _) -> parentClassId)-          ( filter-            (\(_, _, cacheId, _) -> cacheId <= parentCacheId)-            ( filter-              ( \(maybeClassName, _, _, _) ->-                maybeClassName == Just parentClassName-              )-              xs-            )-          )-        )-      )--parentClasses :: Map.Map Text Text-parentClasses = Map.map-  stringToText-  (Map.mapKeys stringToText (Map.fromList rawParentClasses))--makeObjectMap :: List Text -> Map.Map Word32 Text-makeObjectMap objects =-  Map.fromAscList (zip (map Word32 [0 ..]) (listValue objects))--getObjectName :: Map.Map Word32 Text -> Word32 -> Maybe Text-getObjectName objectMap objectId = Map.lookup objectId objectMap--getClassName :: Text -> Maybe Text-getClassName rawObjectName =-  Map.lookup (normalizeObjectName rawObjectName) objectClasses--normalizeObjectName :: Text -> Text-normalizeObjectName objectName =-  let-    name = textValue objectName-    crowdActor = Text.pack "TheWorld:PersistentLevel.CrowdActor_TA"-    crowdManager = Text.pack "TheWorld:PersistentLevel.CrowdManager_TA"-    boostPickup = Text.pack "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"-    mapScoreboard = Text.pack "TheWorld:PersistentLevel.InMapScoreboard_TA"-    breakout = Text.pack "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"-  in if Text.isInfixOf crowdActor name-    then Text crowdActor-    else if Text.isInfixOf crowdManager name-      then Text crowdManager-      else if Text.isInfixOf boostPickup name-        then Text boostPickup-        else if Text.isInfixOf mapScoreboard name-          then Text mapScoreboard-          else if Text.isInfixOf breakout name-            then Text breakout-            else objectName--objectClasses :: Map.Map Text Text-objectClasses = Map.map-  stringToText-  (Map.mapKeys stringToText (Map.fromList rawObjectClasses))--classHasLocation :: Text -> Bool-classHasLocation className = Set.member className classesWithLocation--classesWithLocation :: Set.Set Text-classesWithLocation = Set.fromList (map stringToText rawClassesWithLocation)--classHasRotation :: Text -> Bool-classHasRotation className = Set.member className classesWithRotation--classesWithRotation :: Set.Set Text-classesWithRotation = Set.fromList (map stringToText rawClassesWithRotation)--getAttributeIdLimit :: Map.Map Word32 Word32 -> Maybe Word-getAttributeIdLimit attributeMap = do-  ((streamId, _), _) <- Map.maxViewWithKey attributeMap-  let limit = fromIntegral (word32Value streamId)-  pure limit--getAttributeName-  :: ClassAttributeMap -> Map.Map Word32 Word32 -> CompressedWord -> Maybe Text-getAttributeName classAttributeMap attributeMap streamId = do-  let key = Word32 (fromIntegral (compressedWordValue streamId))-  attributeId <- Map.lookup key attributeMap-  let objectMap = classAttributeMapObjectMap classAttributeMap-  Map.lookup attributeId objectMap--getAttributeMap-  :: ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> Maybe (Map.Map Word32 Word32)-getAttributeMap classAttributeMap actorMap actorId = do-  objectId <- Map.lookup actorId actorMap-  let objectClassMap = classAttributeMapObjectClassMap classAttributeMap-  classId <- Map.lookup objectId objectClassMap-  let value = classAttributeMapValue classAttributeMap-  Map.lookup classId value
− library/Rattletrap/ClassMapping.hs
@@ -1,21 +0,0 @@-module Rattletrap.ClassMapping where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data ClassMapping = ClassMapping-  { classMappingName :: Text-  , classMappingStreamId :: Word32-  } deriving (Eq, Ord, Show)--getClassMapping :: Binary.Get ClassMapping-getClassMapping = do-  name <- getText-  streamId <- getWord32-  pure (ClassMapping name streamId)--putClassMapping :: ClassMapping -> Binary.Put-putClassMapping classMapping = do-  putText (classMappingName classMapping)-  putWord32 (classMappingStreamId classMapping)
− library/Rattletrap/Content.hs
@@ -1,125 +0,0 @@-module Rattletrap.Content where--import Rattletrap.Cache-import Rattletrap.ClassAttributeMap-import Rattletrap.ClassMapping-import Rattletrap.Frame-import Rattletrap.KeyFrame-import Rattletrap.Mark-import Rattletrap.Message-import Rattletrap.Primitive-import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.Map as Map---- | Contains low-level game data about a 'Rattletrap.Replay.Replay'.-data Content = Content-  { contentLevels :: List Text-  -- ^ This typically only has one element, like @stadium_oob_audio_map@.-  , contentKeyFrames :: List KeyFrame-  -- ^ A list of which frames are key frames. Although they aren't necessary-  -- for replay, key frames are frames that replicate every actor. They-  -- typically happen once every 10 seconds.-  , contentStreamSize :: Word32-  -- ^ The size of the stream in bytes. This is only really necessary because-  -- the stream has some arbitrary amount of padding at the end.-  , contentFrames :: [Frame]-  -- ^ The actual game data. This is where all the interesting information is.-  , contentMessages :: List Message-  -- ^ Debugging messages. In newer replays, this is always empty.-  , contentMarks :: List Mark-  -- ^ Tick marks shown on the scrubber when watching a replay.-  , contentPackages :: List Text-  -- ^ A list of @.upk@ files to load, like-  -- @..\\..\\TAGame\\CookedPCConsole\\Stadium_P.upk@.-  , contentObjects :: List Text-  -- ^ Objects in the stream. Used for the-  -- 'Rattletrap.ClassAttributeMap.ClassAttributeMap'.-  , contentNames :: List Text-  -- ^ It's not clear what these are used for. This list is usually not empty,-  -- but appears unused otherwise.-  , contentClassMappings :: List ClassMapping-  -- ^ A mapping between classes and their ID in the stream. Used for the-  -- 'Rattletrap.ClassAttributeMap.ClassAttributeMap'.-  , contentCaches :: List Cache-  -- ^ A list of classes along with their parent classes and attributes. Used-  -- for the 'Rattletrap.ClassAttributeMap.ClassAttributeMap'.-  } deriving (Eq, Ord, Show)--getContent-  :: (Int, Int, Int)-  -- ^ Version numbers, usually from 'Rattletrap.Header.getVersion'.-  -> Int-  -- ^ The number of frames in the stream, usually from-  -- 'Rattletrap.Header.getNumFrames'.-  -> Word-  -- ^ The maximum number of channels in the stream, usually from-  -- 'Rattletrap.Header.getMaxChannels'.-  -> Binary.Get Content-getContent version numFrames maxChannels = do-  levels <- getList getText-  keyFrames <- getList getKeyFrame-  streamSize <- getWord32-  stream <- Binary.getLazyByteString (fromIntegral (word32Value streamSize))-  messages <- getList getMessage-  marks <- getList getMark-  packages <- getList getText-  objects <- getList getText-  names <- getList getText-  classMappings <- getList getClassMapping-  caches <- getList getCache-  let-    classAttributeMap =-      makeClassAttributeMap objects classMappings caches names-  let-    frames = Binary.runGet-      ( BinaryBit.runBitGet-        ( do-          (theFrames, _) <- getFrames-            version-            numFrames-            maxChannels-            classAttributeMap-            Map.empty-          pure theFrames-        )-      )-      (reverseBytes stream)-  pure-    ( Content-      levels-      keyFrames-      streamSize-      frames-      messages-      marks-      packages-      objects-      names-      classMappings-      caches-    )--putContent :: Content -> Binary.Put-putContent content = do-  putList putText (contentLevels content)-  putList putKeyFrame (contentKeyFrames content)-  let streamSize = contentStreamSize content-  putWord32 streamSize-  let-    stream =-      Binary.runPut (BinaryBit.runBitPut (putFrames (contentFrames content)))-  Binary.putLazyByteString-    (reverseBytes (padBytes (word32Value streamSize) stream))-  putList putMessage (contentMessages content)-  putList putMark (contentMarks content)-  putList putText (contentPackages content)-  putList putText (contentObjects content)-  putList putText (contentNames content)-  putList putClassMapping (contentClassMappings content)-  putList putCache (contentCaches content)
− library/Rattletrap/Crc.hs
@@ -1,40 +0,0 @@-module Rattletrap.Crc where--import Rattletrap.Data--import qualified Data.Bits as Bits-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Vector.Unboxed as Vector-import qualified Data.Word as Word---- | Computes the CRC32 of some bytes. This is done to ensure that the bytes--- are valid before trying to parse them.------ @--- getCrc32 ('Data.ByteString.Lazy.pack' [0x00])--- @------ This CRC uses an initial value of @0xefcbf201@ and a polynomial of--- @0x04c11db7@.-getCrc32 :: ByteString.ByteString -> Word.Word32-getCrc32 bytes = do-  let update = crc32Update crc32Table-  let initial = Bits.complement crc32Initial-  let crc = ByteString.foldl update initial bytes-  Bits.complement crc--crc32Update-  :: Vector.Vector Word.Word32 -> Word.Word32 -> Word.Word8 -> Word.Word32-crc32Update table crc byte = do-  let toWord8 = fromIntegral :: (Integral a) => a -> Word.Word8-  let toInt = fromIntegral :: (Integral a) => a -> Int-  let index = toInt (Bits.xor byte (toWord8 (Bits.shiftR crc 24)))-  let left = Vector.unsafeIndex table index-  let right = Bits.shiftL crc 8-  Bits.xor left right--crc32Initial :: Word.Word32-crc32Initial = 0xefcbf201--crc32Table :: Vector.Vector Word.Word32-crc32Table = Vector.fromList rawCrc32Table
− library/Rattletrap/Data.hs
@@ -1,372 +0,0 @@-module Rattletrap.Data where--import Rattletrap.AttributeType--rawParentClasses :: [(String, String)]-rawParentClasses =-  [ ("Engine.Actor", "Core.Object")-  , ("Engine.GameReplicationInfo", "Engine.ReplicationInfo")-  , ("Engine.Info", "Engine.Actor")-  , ("Engine.Pawn", "Engine.Actor")-  , ("Engine.PlayerReplicationInfo", "Engine.ReplicationInfo")-  , ("Engine.ReplicationInfo", "Engine.Info")-  , ("Engine.TeamInfo", "Engine.ReplicationInfo")-  , ("ProjectX.GRI_X", "Engine.GameReplicationInfo")-  , ("ProjectX.Pawn_X", "Engine.Pawn")-  , ("ProjectX.PRI_X", "Engine.PlayerReplicationInfo")-  , ("TAGame.Ball_TA", "TAGame.RBActor_TA")-  , ("TAGame.CameraSettingsActor_TA", "Engine.ReplicationInfo")-  , ("TAGame.Car_Season_TA", "TAGame.PRI_TA")-  , ("TAGame.Car_TA", "TAGame.Vehicle_TA")-  , ("TAGame.CarComponent_Boost_TA", "TAGame.CarComponent_TA")-  , ("TAGame.CarComponent_Dodge_TA", "TAGame.CarComponent_TA")-  , ("TAGame.CarComponent_DoubleJump_TA", "TAGame.CarComponent_TA")-  , ("TAGame.CarComponent_FlipCar_TA", "TAGame.CarComponent_TA")-  , ("TAGame.CarComponent_Jump_TA", "TAGame.CarComponent_TA")-  , ("TAGame.CarComponent_TA", "Engine.ReplicationInfo")-  , ("TAGame.CrowdActor_TA", "Engine.ReplicationInfo")-  , ("TAGame.CrowdManager_TA", "Engine.ReplicationInfo")-  , ("TAGame.GameEvent_Season_TA", "TAGame.GameEvent_Soccar_TA")-  , ("TAGame.GameEvent_Soccar_TA", "TAGame.GameEvent_Team_TA")-  , ("TAGame.GameEvent_SoccarPrivate_TA", "TAGame.GameEvent_Soccar_TA")-  , ("TAGame.GameEvent_SoccarSplitscreen_TA", "TAGame.GameEvent_SoccarPrivate_TA")-  , ("TAGame.GameEvent_TA", "Engine.ReplicationInfo")-  , ("TAGame.GameEvent_Team_TA", "TAGame.GameEvent_TA")-  , ("TAGame.GRI_TA", "ProjectX.GRI_X")-  , ("TAGame.InMapScoreboard_TA", "Engine.Actor")-  , ("TAGame.PRI_TA", "ProjectX.PRI_X")-  , ("TAGame.RBActor_TA", "ProjectX.Pawn_X")-  , ("TAGame.SpecialPickup_BallCarSpring_TA", "TAGame.SpecialPickup_Spring_TA")-  , ("TAGame.SpecialPickup_BallFreeze_TA", "TAGame.SpecialPickup_Targeted_TA")-  , ("TAGame.SpecialPickup_BallGravity_TA", "TAGame.SpecialPickup_TA")-  , ("TAGame.SpecialPickup_BallLasso_TA", "TAGame.SpecialPickup_GrapplingHook_TA")-  , ("TAGame.SpecialPickup_BallVelcro_TA", "TAGame.SpecialPickup_TA")-  , ("TAGame.SpecialPickup_Batarang_TA", "TAGame.SpecialPickup_BallLasso_TA")-  , ("TAGame.SpecialPickup_BoostOverride_TA", "TAGame.SpecialPickup_Targeted_TA")-  , ("TAGame.SpecialPickup_GrapplingHook_TA", "TAGame.SpecialPickup_Targeted_TA")-  , ("TAGame.SpecialPickup_HitForce_TA", "TAGame.SpecialPickup_TA")-  , ("TAGame.SpecialPickup_Spring_TA", "TAGame.SpecialPickup_Targeted_TA")-  , ("TAGame.SpecialPickup_Swapper_TA", "TAGame.SpecialPickup_Targeted_TA")-  , ("TAGame.SpecialPickup_TA", "TAGame.CarComponent_TA")-  , ("TAGame.SpecialPickup_Targeted_TA", "TAGame.SpecialPickup_TA")-  , ("TAGame.SpecialPickup_Tornado_TA", "TAGame.SpecialPickup_TA")-  , ("TAGame.Team_Soccar_TA", "TAGame.Team_TA")-  , ("TAGame.Team_TA", "Engine.TeamInfo")-  , ("TAGame.Vehicle_TA", "TAGame.RBActor_TA")-  , ("TAGame.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_TA")-  , ("TAGame.VehiclePickup_TA", "Engine.ReplicationInfo")-  ]--rawClassesWithLocation :: [String]-rawClassesWithLocation =-  [ "TAGame.Ball_Breakout_TA"-  , "TAGame.Ball_TA"-  , "TAGame.CameraSettingsActor_TA"-  , "TAGame.Car_Season_TA"-  , "TAGame.Car_TA"-  , "TAGame.CarComponent_Boost_TA"-  , "TAGame.CarComponent_Dodge_TA"-  , "TAGame.CarComponent_DoubleJump_TA"-  , "TAGame.CarComponent_FlipCar_TA"-  , "TAGame.CarComponent_Jump_TA"-  , "TAGame.GameEvent_Season_TA"-  , "TAGame.GameEvent_Soccar_TA"-  , "TAGame.GameEvent_SoccarPrivate_TA"-  , "TAGame.GameEvent_SoccarSplitscreen_TA"-  , "TAGame.GRI_TA"-  , "TAGame.PRI_TA"-  , "TAGame.SpecialPickup_BallCarSpring_TA"-  , "TAGame.SpecialPickup_BallFreeze_TA"-  , "TAGame.SpecialPickup_BallGravity_TA"-  , "TAGame.SpecialPickup_BallGravity_TA"-  , "TAGame.SpecialPickup_BallLasso_TA"-  , "TAGame.SpecialPickup_BallVelcro_TA"-  , "TAGame.SpecialPickup_Batarang_TA"-  , "TAGame.SpecialPickup_BoostOverride_TA"-  , "TAGame.SpecialPickup_GrapplingHook_TA"-  , "TAGame.SpecialPickup_HitForce_TA"-  , "TAGame.SpecialPickup_Swapper_TA"-  , "TAGame.SpecialPickup_Tornado_TA"-  , "TAGame.Team_Soccar_TA"-  ]--rawClassesWithRotation :: [String]-rawClassesWithRotation =-  [ "TAGame.Ball_Breakout_TA"-  , "TAGame.Ball_TA"-  , "TAGame.Car_Season_TA"-  , "TAGame.Car_TA"-  ]--rawObjectClasses :: [(String, String)]-rawObjectClasses =-  [ ("Archetypes.Ball.Ball_BasketBall_Mutator", "TAGame.Ball_TA")-  , ("Archetypes.Ball.Ball_Basketball", "TAGame.Ball_TA")-  , ("Archetypes.Ball.Ball_Breakout", "TAGame.Ball_Breakout_TA")-  , ("Archetypes.Ball.Ball_Default", "TAGame.Ball_TA")-  , ("Archetypes.Ball.Ball_Puck", "TAGame.Ball_TA")-  , ("Archetypes.Ball.CubeBall", "TAGame.Ball_TA")-  , ("Archetypes.Car.Car_Default", "TAGame.Car_TA")-  , ("Archetypes.CarComponents.CarComponent_Boost", "TAGame.CarComponent_Boost_TA")-  , ("Archetypes.CarComponents.CarComponent_Dodge", "TAGame.CarComponent_Dodge_TA")-  , ("Archetypes.CarComponents.CarComponent_DoubleJump", "TAGame.CarComponent_DoubleJump_TA")-  , ("Archetypes.CarComponents.CarComponent_FlipCar", "TAGame.CarComponent_FlipCar_TA")-  , ("Archetypes.CarComponents.CarComponent_Jump", "TAGame.CarComponent_Jump_TA")-  , ("Archetypes.GameEvent.GameEvent_Basketball", "TAGame.GameEvent_Soccar_TA")-  , ("Archetypes.GameEvent.GameEvent_BasketballPrivate", "TAGame.GameEvent_SoccarPrivate_TA")-  , ("Archetypes.GameEvent.GameEvent_BasketballSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")-  , ("Archetypes.GameEvent.GameEvent_Breakout", "TAGame.GameEvent_Soccar_TA")-  , ("Archetypes.GameEvent.GameEvent_Hockey", "TAGame.GameEvent_Soccar_TA")-  , ("Archetypes.GameEvent.GameEvent_HockeyPrivate", "TAGame.GameEvent_SoccarPrivate_TA")-  , ("Archetypes.GameEvent.GameEvent_HockeySplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")-  , ("Archetypes.GameEvent.GameEvent_Items", "TAGame.GameEvent_Soccar_TA")-  , ("Archetypes.GameEvent.GameEvent_Season:CarArchetype", "TAGame.Car_TA")-  , ("Archetypes.GameEvent.GameEvent_Season", "TAGame.GameEvent_Season_TA")-  , ("Archetypes.GameEvent.GameEvent_Soccar", "TAGame.GameEvent_Soccar_TA")-  , ("Archetypes.GameEvent.GameEvent_SoccarPrivate", "TAGame.GameEvent_SoccarPrivate_TA")-  , ("Archetypes.GameEvent.GameEvent_SoccarSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BallFreeze", "TAGame.SpecialPickup_BallFreeze_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BallGrapplingHook", "TAGame.SpecialPickup_GrapplingHook_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BallLasso", "TAGame.SpecialPickup_BallLasso_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BallSpring", "TAGame.SpecialPickup_BallCarSpring_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BallVelcro", "TAGame.SpecialPickup_BallVelcro_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_Batarang", "TAGame.SpecialPickup_Batarang_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_BoostOverride", "TAGame.SpecialPickup_BoostOverride_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_CarSpring", "TAGame.SpecialPickup_BallCarSpring_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_GravityWell", "TAGame.SpecialPickup_BallGravity_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_StrongHit", "TAGame.SpecialPickup_HitForce_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_Swapper", "TAGame.SpecialPickup_Swapper_TA")-  , ("Archetypes.SpecialPickups.SpecialPickup_Tornado", "TAGame.SpecialPickup_Tornado_TA")-  , ("Archetypes.Teams.Team0", "TAGame.Team_Soccar_TA")-  , ("Archetypes.Teams.Team1", "TAGame.Team_Soccar_TA")-  , ("GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("GameInfo_Breakout.GameInfo.GameInfo_Breakout:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("GameInfo_Items.GameInfo.GameInfo_Items:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype", "TAGame.GRI_TA")-  , ("TAGame.Default__CameraSettingsActor_TA", "TAGame.CameraSettingsActor_TA")-  , ("TAGame.Default__PRI_TA", "TAGame.PRI_TA")-  , ("TheWorld:PersistentLevel.BreakOutActor_Platform_TA", "TAGame.BreakOutActor_Platform_TA")-  , ("TheWorld:PersistentLevel.CrowdActor_TA", "TAGame.CrowdActor_TA")-  , ("TheWorld:PersistentLevel.CrowdManager_TA", "TAGame.CrowdManager_TA")-  , ("TheWorld:PersistentLevel.InMapScoreboard_TA", "TAGame.InMapScoreboard_TA")-  , ("TheWorld:PersistentLevel.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_Boost_TA")-  ]--rawAttributeTypes :: [(String, AttributeType)]-rawAttributeTypes =-  [ ("Engine.Actor:bBlockActors", BooleanAttributeType)-  , ("Engine.Actor:bCollideActors", BooleanAttributeType)-  , ("Engine.Actor:bHidden", BooleanAttributeType)-  , ("Engine.Actor:DrawScale", FloatAttributeType)-  , ("Engine.Actor:Role", EnumAttributeType)-  , ("Engine.GameReplicationInfo:bMatchIsOver", BooleanAttributeType)-  , ("Engine.GameReplicationInfo:GameClass", FlaggedIntAttributeType)-  , ("Engine.GameReplicationInfo:ServerName", StringAttributeType)-  , ("Engine.Pawn:PlayerReplicationInfo", FlaggedIntAttributeType)-  , ("Engine.PlayerReplicationInfo:bBot", BooleanAttributeType)-  , ("Engine.PlayerReplicationInfo:bIsSpectator", BooleanAttributeType)-  , ("Engine.PlayerReplicationInfo:bReadyToPlay", BooleanAttributeType)-  , ("Engine.PlayerReplicationInfo:bWaitingPlayer", BooleanAttributeType)-  , ("Engine.PlayerReplicationInfo:Ping", ByteAttributeType)-  , ("Engine.PlayerReplicationInfo:PlayerID", IntAttributeType)-  , ("Engine.PlayerReplicationInfo:PlayerName", StringAttributeType)-  , ("Engine.PlayerReplicationInfo:RemoteUserData", StringAttributeType)-  , ("Engine.PlayerReplicationInfo:Score", IntAttributeType)-  , ("Engine.PlayerReplicationInfo:Team", FlaggedIntAttributeType)-  , ("Engine.PlayerReplicationInfo:UniqueId", UniqueIdAttributeType)-  , ("Engine.TeamInfo:Score", IntAttributeType)-  , ("ProjectX.GRI_X:bGameStarted", BooleanAttributeType)-  , ("ProjectX.GRI_X:GameServerID", QWordAttributeType)-  , ("ProjectX.GRI_X:MatchGUID", StringAttributeType)-  , ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", IntAttributeType)-  , ("ProjectX.GRI_X:ReplicatedGamePlaylist", IntAttributeType)-  , ("ProjectX.GRI_X:Reservations", ReservationAttributeType)-  , ("TAGame.Ball_Breakout_TA:AppliedDamage", AppliedDamageAttributeType)-  , ("TAGame.Ball_Breakout_TA:DamageIndex", IntAttributeType)-  , ("TAGame.Ball_Breakout_TA:LastTeamTouch", ByteAttributeType)-  , ("TAGame.Ball_TA:GameEvent", FlaggedIntAttributeType)-  , ("TAGame.Ball_TA:HitTeamNum", ByteAttributeType)-  , ("TAGame.Ball_TA:ReplicatedAddedCarBounceScale", FloatAttributeType)-  , ("TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale", FloatAttributeType)-  , ("TAGame.Ball_TA:ReplicatedBallScale", FloatAttributeType)-  , ("TAGame.Ball_TA:ReplicatedExplosionData", ExplosionAttributeType)-  , ("TAGame.Ball_TA:ReplicatedExplosionDataExtended", ExtendedExplosionAttributeType)-  , ("TAGame.Ball_TA:ReplicatedWorldBounceScale", FloatAttributeType)-  , ("TAGame.BreakOutActor_Platform_TA:DamageState", DamageStateAttributeType)-  , ("TAGame.CameraSettingsActor_TA:bUsingBehindView", BooleanAttributeType)-  , ("TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera", BooleanAttributeType)-  , ("TAGame.CameraSettingsActor_TA:CameraPitch", ByteAttributeType)-  , ("TAGame.CameraSettingsActor_TA:CameraYaw", ByteAttributeType)-  , ("TAGame.CameraSettingsActor_TA:PRI", FlaggedIntAttributeType)-  , ("TAGame.CameraSettingsActor_TA:ProfileSettings", CamSettingsAttributeType)-  , ("TAGame.Car_TA:AddedBallForceMultiplier", FloatAttributeType)-  , ("TAGame.Car_TA:AddedCarForceMultiplier", FloatAttributeType)-  , ("TAGame.Car_TA:AttachedPickup", FlaggedIntAttributeType)-  , ("TAGame.Car_TA:ClubColors", ClubColorsAttributeType)-  , ("TAGame.Car_TA:ReplicatedDemolish", DemolishAttributeType)-  , ("TAGame.Car_TA:TeamPaint", TeamPaintAttributeType)-  , ("TAGame.CarComponent_Boost_TA:bNoBoost", BooleanAttributeType)-  , ("TAGame.CarComponent_Boost_TA:BoostModifier", FloatAttributeType)-  , ("TAGame.CarComponent_Boost_TA:bUnlimitedBoost", BooleanAttributeType)-  , ("TAGame.CarComponent_Boost_TA:RechargeDelay", FloatAttributeType)-  , ("TAGame.CarComponent_Boost_TA:RechargeRate", FloatAttributeType)-  , ("TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount", ByteAttributeType)-  , ("TAGame.CarComponent_Boost_TA:UnlimitedBoostRefCount", IntAttributeType)-  , ("TAGame.CarComponent_Dodge_TA:DodgeTorque", LocationAttributeType)-  , ("TAGame.CarComponent_FlipCar_TA:bFlipRight", BooleanAttributeType)-  , ("TAGame.CarComponent_FlipCar_TA:FlipCarTime", FloatAttributeType)-  , ("TAGame.CarComponent_TA:ReplicatedActive", ByteAttributeType)-  , ("TAGame.CarComponent_TA:ReplicatedActivityTime", FloatAttributeType)-  , ("TAGame.CarComponent_TA:Vehicle", FlaggedIntAttributeType)-  , ("TAGame.CrowdActor_TA:GameEvent", FlaggedIntAttributeType)-  , ("TAGame.CrowdActor_TA:ModifiedNoise", FloatAttributeType)-  , ("TAGame.CrowdActor_TA:ReplicatedCountDownNumber", IntAttributeType)-  , ("TAGame.CrowdActor_TA:ReplicatedOneShotSound", FlaggedIntAttributeType)-  , ("TAGame.CrowdActor_TA:ReplicatedRoundCountDownNumber", IntAttributeType)-  , ("TAGame.CrowdManager_TA:GameEvent", FlaggedIntAttributeType)-  , ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", FlaggedIntAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", BooleanAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:bOverTime", BooleanAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:GameTime", IntAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", MusicStingerAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", ByteAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:RoundNum", IntAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:SecondsRemaining", IntAttributeType)-  , ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", FlaggedIntAttributeType)-  , ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", PrivateMatchSettingsAttributeType)-  , ("TAGame.GameEvent_TA:bCanVoteToForfeit", BooleanAttributeType)-  , ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", BooleanAttributeType)-  , ("TAGame.GameEvent_TA:BotSkill", IntAttributeType)-  , ("TAGame.GameEvent_TA:GameMode", GameModeAttributeType)-  , ("TAGame.GameEvent_TA:MatchTypeClass", FlaggedIntAttributeType)-  , ("TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining", IntAttributeType)-  , ("TAGame.GameEvent_TA:ReplicatedStateIndex", ByteAttributeType)-  , ("TAGame.GameEvent_TA:ReplicatedStateName", IntAttributeType)-  , ("TAGame.GameEvent_Team_TA:bForfeit", BooleanAttributeType)-  , ("TAGame.GameEvent_Team_TA:MaxTeamSize", IntAttributeType)-  , ("TAGame.GRI_TA:NewDedicatedServerIP", StringAttributeType)-  , ("TAGame.PRI_TA:bIsInSplitScreen", BooleanAttributeType)-  , ("TAGame.PRI_TA:bMatchMVP", BooleanAttributeType)-  , ("TAGame.PRI_TA:bOnlineLoadoutSet", BooleanAttributeType)-  , ("TAGame.PRI_TA:bOnlineLoadoutsSet", BooleanAttributeType)-  , ("TAGame.PRI_TA:BotProductName", IntAttributeType)-  , ("TAGame.PRI_TA:bReady", BooleanAttributeType)-  , ("TAGame.PRI_TA:bUsingBehindView", BooleanAttributeType)-  , ("TAGame.PRI_TA:bUsingItems", BooleanAttributeType)-  , ("TAGame.PRI_TA:bUsingSecondaryCamera", BooleanAttributeType)-  , ("TAGame.PRI_TA:CameraPitch", ByteAttributeType)-  , ("TAGame.PRI_TA:CameraSettings", CamSettingsAttributeType)-  , ("TAGame.PRI_TA:CameraYaw", ByteAttributeType)-  , ("TAGame.PRI_TA:ClientLoadout", LoadoutAttributeType)-  , ("TAGame.PRI_TA:ClientLoadoutOnline", LoadoutOnlineAttributeType)-  , ("TAGame.PRI_TA:ClientLoadouts", LoadoutsAttributeType)-  , ("TAGame.PRI_TA:ClientLoadoutsOnline", LoadoutsOnlineAttributeType)-  , ("TAGame.PRI_TA:MatchAssists", IntAttributeType)-  , ("TAGame.PRI_TA:MatchBreakoutDamage", IntAttributeType)-  , ("TAGame.PRI_TA:MatchGoals", IntAttributeType)-  , ("TAGame.PRI_TA:MatchSaves", IntAttributeType)-  , ("TAGame.PRI_TA:MatchScore", IntAttributeType)-  , ("TAGame.PRI_TA:MatchShots", IntAttributeType)-  , ("TAGame.PRI_TA:MaxTimeTillItem", IntAttributeType)-  , ("TAGame.PRI_TA:PartyLeader", PartyLeaderAttributeType)-  , ("TAGame.PRI_TA:PawnType", ByteAttributeType)-  , ("TAGame.PRI_TA:PersistentCamera", FlaggedIntAttributeType)-  , ("TAGame.PRI_TA:PlayerHistoryValid", BooleanAttributeType)-  , ("TAGame.PRI_TA:ReplicatedGameEvent", FlaggedIntAttributeType)-  , ("TAGame.PRI_TA:SteeringSensitivity", FloatAttributeType)-  , ("TAGame.PRI_TA:TimeTillItem", IntAttributeType)-  , ("TAGame.PRI_TA:Title", IntAttributeType)-  , ("TAGame.PRI_TA:TotalXP", IntAttributeType)-  , ("TAGame.RBActor_TA:bFrozen", BooleanAttributeType)-  , ("TAGame.RBActor_TA:bIgnoreSyncing", BooleanAttributeType)-  , ("TAGame.RBActor_TA:bReplayActor", BooleanAttributeType)-  , ("TAGame.RBActor_TA:ReplicatedRBState", RigidBodyStateAttributeType)-  , ("TAGame.RBActor_TA:WeldedInfo", WeldedInfoAttributeType)-  , ("TAGame.SpecialPickup_BallFreeze_TA:RepOrigSpeed", FloatAttributeType)-  , ("TAGame.SpecialPickup_BallVelcro_TA:AttachTime", FloatAttributeType)-  , ("TAGame.SpecialPickup_BallVelcro_TA:bBroken", BooleanAttributeType)-  , ("TAGame.SpecialPickup_BallVelcro_TA:bHit", BooleanAttributeType)-  , ("TAGame.SpecialPickup_BallVelcro_TA:BreakTime", FloatAttributeType)-  , ("TAGame.SpecialPickup_Targeted_TA:Targeted", FlaggedIntAttributeType)-  , ("TAGame.Team_Soccar_TA:GameScore", IntAttributeType)-  , ("TAGame.Team_TA:ClubColors", ClubColorsAttributeType)-  , ("TAGame.Team_TA:CustomTeamName", StringAttributeType)-  , ("TAGame.Team_TA:GameEvent", FlaggedIntAttributeType)-  , ("TAGame.Team_TA:LogoData", FlaggedIntAttributeType)-  , ("TAGame.Vehicle_TA:bDriving", BooleanAttributeType)-  , ("TAGame.Vehicle_TA:bReplicatedHandbrake", BooleanAttributeType)-  , ("TAGame.Vehicle_TA:ReplicatedSteer", ByteAttributeType)-  , ("TAGame.Vehicle_TA:ReplicatedThrottle", ByteAttributeType)-  , ("TAGame.VehiclePickup_TA:bNoPickup", BooleanAttributeType)-  , ("TAGame.VehiclePickup_TA:ReplicatedPickupData", PickupAttributeType)-  ]--rawCrc32Table :: Integral a => [a]-rawCrc32Table =-  [ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9-  , 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005-  , 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61-  , 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd-  , 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9-  , 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75-  , 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011-  , 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd-  , 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039-  , 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5-  , 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81-  , 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d-  , 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49-  , 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95-  , 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1-  , 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d-  , 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae-  , 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072-  , 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16-  , 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca-  , 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde-  , 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02-  , 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066-  , 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba-  , 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e-  , 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692-  , 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6-  , 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a-  , 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e-  , 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2-  , 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686-  , 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a-  , 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637-  , 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb-  , 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f-  , 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53-  , 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47-  , 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b-  , 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff-  , 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623-  , 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7-  , 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b-  , 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f-  , 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3-  , 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7-  , 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b-  , 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f-  , 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3-  , 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640-  , 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c-  , 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8-  , 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24-  , 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30-  , 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec-  , 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088-  , 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654-  , 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0-  , 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c-  , 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18-  , 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4-  , 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0-  , 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c-  , 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668-  , 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4-  ]
− library/Rattletrap/Frame.hs
@@ -1,67 +0,0 @@-module Rattletrap.Frame where--import Rattletrap.ActorMap-import Rattletrap.ClassAttributeMap-import Rattletrap.Primitive-import Rattletrap.Replication--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Frame = Frame-  { frameTime :: Float32-  -- ^ Time in seconds since the beginning of the match.-  , frameDelta :: Float32-  -- ^ Time in seconds since the last frame. Usually about 0.03 since there-  -- are 30 frames per second.-  , frameReplications :: [Replication]-  } deriving (Eq, Ord, Show)--getFrames-  :: (Int, Int, Int)-  -> Int-  -> Word-  -> ClassAttributeMap-  -> ActorMap-  -> BinaryBit.BitGet ([Frame], ActorMap)-getFrames version numFrames maxChannels classAttributeMap actorMap =-  if numFrames <= 0-    then pure ([], actorMap)-    else do-      (frame, newActorMap) <- getFrame-        version-        maxChannels-        classAttributeMap-        actorMap-      (frames, newerActorMap) <- getFrames-        version-        (numFrames - 1)-        maxChannels-        classAttributeMap-        newActorMap-      pure (frame : frames, newerActorMap)--putFrames :: [Frame] -> BinaryBit.BitPut ()-putFrames = mapM_ putFrame--getFrame-  :: (Int, Int, Int)-  -> Word-  -> ClassAttributeMap-  -> ActorMap-  -> BinaryBit.BitGet (Frame, ActorMap)-getFrame version maxChannels classAttributeMap actorMap = do-  time <- getFloat32Bits-  delta <- getFloat32Bits-  (replications, newActorMap) <- getReplications-    version-    maxChannels-    classAttributeMap-    actorMap-  pure (Frame time delta replications, newActorMap)--putFrame :: Frame -> BinaryBit.BitPut ()-putFrame frame = do-  putFloat32Bits (frameTime frame)-  putFloat32Bits (frameDelta frame)-  putReplications (frameReplications frame)
− library/Rattletrap/Header.hs
@@ -1,112 +0,0 @@-module Rattletrap.Header where--import Rattletrap.Primitive-import Rattletrap.Property-import Rattletrap.PropertyValue--import qualified Data.Binary as Binary-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe---- | Contains high-level metadata about a 'Rattletrap.Replay.Replay'.-data Header = Header-  { headerEngineVersion :: Word32-  -- ^ The "major" ("engine") version number.-  , headerLicenseeVersion :: Word32-  -- ^ The "minor" ("licensee") version number.-  , headerPatchVersion :: Maybe Word32-  -- ^ The "patch" ("net") version number.-  , headerLabel :: Text-  -- ^ Always @TAGame.Replay_Soccar_TA@.-  , headerProperties :: Dictionary Property-  -- ^ These properties determine how a replay will look in the list of-  -- replays in-game. One element is required for the replay to show up:-  ---  -- - MapName: This is a 'Rattletrap.PropertyValue.NameProperty' with a-  --   case-insensitive map identifier, like @Stadium_P@.-  ---  -- There are many other properties that affect how the replay looks in the-  -- list of replays.-  ---  -- - Date: A 'Rattletrap.PropertyValue.StrProperty' with the format-  --   @YYYY-mm-dd:HH-MM@. Dates are not validated, but the month must be-  --   between 1 and 12 to show up. The hour is shown modulo 12 with AM or PM.-  -- - MatchType: A 'Rattletrap.PropertyValue.NameProperty'. If this is not-  --   one of the expected values, nothing will be shown next to the replay's-  --   map. The expected values are: @Online@, @Offline@, @Private@, and-  --   @Season@.-  -- - NumFrames: This 'Rattletrap.PropertyValue.IntProperty' is used to-  --   calculate the length of the match. There are 30 frames per second,-  --   a typical 5-minute match has about 9,000 frames.-  -- - PrimaryPlayerTeam: This is an 'Rattletrap.PropertyValue.IntProperty'.-  --   It is either 0 (blue) or 1 (orange). Any other value is ignored. If-  --   this would be 0, you don't have to set it at all.-  -- - ReplayName: An optional 'Rattletrap.PropertyValue.StrProperty' with a-  --   user-supplied name for the replay.-  -- - Team0Score: The blue team's score as an-  --   'Rattletrap.PropertyValue.IntProperty'. Can be omitted if the score is-  --   0.-  -- - Team1Score: The orange team's score as an-  --   'Rattletrap.PropertyValue.IntProperty'. Can also be omitted if the-  --   score is 0.-  -- - TeamSize: An 'Rattletrap.PropertyValue.IntProperty' with the number of-  --   players per team. This value is not validated, so you can put absurd-  --   values like 99. To get an "unfair" team size like 1v4, you must set the-  --   bUnfairBots 'Rattletrap.PropertyValue.BoolProperty' to @True@.-  } deriving (Eq, Ord, Show)--getHeader :: Binary.Get Header-getHeader = do-  engineVersion <- getWord32-  licenseeVersion <- getWord32-  patchVersion <- getPatchVersion engineVersion licenseeVersion-  label <- getText-  properties <- getDictionary getProperty-  pure (Header engineVersion licenseeVersion patchVersion label properties)--getPatchVersion :: Word32 -> Word32 -> Binary.Get (Maybe Word32)-getPatchVersion major minor = if hasPatchVersion major minor-  then do-    patchVersion <- getWord32-    pure (Just patchVersion)-  else pure Nothing--hasPatchVersion :: Word32 -> Word32 -> Bool-hasPatchVersion major minor = major >= Word32 868 && minor >= Word32 18--putHeader :: Header -> Binary.Put-putHeader header = do-  putWord32 (headerEngineVersion header)-  putWord32 (headerLicenseeVersion header)-  case headerPatchVersion header of-    Nothing -> pure ()-    Just patchVersion -> putWord32 patchVersion-  putText (headerLabel header)-  putDictionary putProperty (headerProperties header)--getVersion :: Header -> (Int, Int, Int)-getVersion header =-  ( fromIntegral (word32Value (headerEngineVersion header))-  , fromIntegral (word32Value (headerLicenseeVersion header))-  , Maybe.fromMaybe 0 (fmap (\ v -> fromIntegral (word32Value v)) (headerPatchVersion header))-  )--getNumFrames :: Header -> Int-getNumFrames header =-  let-    key = textValue (stringToText "NumFrames")-    properties = dictionaryValue (headerProperties header)-  in case Map.lookup key properties of-    Just (Property _ _ (IntProperty numFrames)) ->-      fromIntegral (int32Value numFrames)-    _ -> 0--getMaxChannels :: Header -> Word-getMaxChannels header =-  let-    key = textValue (stringToText "MaxChannels")-    properties = dictionaryValue (headerProperties header)-  in case Map.lookup key properties of-    Just (Property _ _ (IntProperty numFrames)) ->-      fromIntegral (int32Value numFrames)-    _ -> 1023
− library/Rattletrap/Helper.hs
@@ -1,103 +0,0 @@--- | This module provides helper functions for converting replays to and from--- both their binary format and JSON.-module Rattletrap.Helper where--import Rattletrap.Json ()-import Rattletrap.Replay--import qualified Data.Aeson as Aeson-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified System.IO as IO---- * Lazy byte strings--- | Parses a raw replay.-decodeReplay :: ByteString.ByteString -> Either String Replay-decodeReplay contents = case Binary.runGetOrFail getReplay contents of-  Left (_, _, message) -> fail message-  Right (_, _, replay) -> pure replay---- | Encodes a replay as JSON.-encodeJson :: Replay -> ByteString.ByteString-encodeJson = Aeson.encode---- | Parses a JSON replay.-decodeJson :: ByteString.ByteString -> Either String Replay-decodeJson = Aeson.eitherDecode---- | Encodes a raw replay.-encodeReplay :: Replay -> ByteString.ByteString-encodeReplay replay = Binary.runPut (putReplay replay)---- * File paths--- | 'decodeReplay'-{-# DEPRECATED-decodeReplayFile "use Data.ByteString.Lazy.readFile"- #-}--decodeReplayFile :: FilePath -> IO (Either String Replay)-decodeReplayFile file = IO.withBinaryFile file IO.ReadMode decodeReplayHandle---- | 'encodeJson'-{-# DEPRECATED-encodeJsonFile "use Data.ByteString.Lazy.writeFile"- #-}--encodeJsonFile :: Replay -> FilePath -> IO ()-encodeJsonFile replay file =-  IO.withBinaryFile file IO.WriteMode (encodeJsonHandle replay)---- | 'decodeJson'-{-# DEPRECATED-decodeJsonFile "use Data.ByteString.Lazy.readFile"- #-}--decodeJsonFile :: FilePath -> IO (Either String Replay)-decodeJsonFile file = IO.withBinaryFile file IO.ReadMode decodeJsonHandle---- | 'encodeReplay'-{-# DEPRECATED-encodeReplayFile "use Data.ByteString.Lazy.writeFile"- #-}--encodeReplayFile :: Replay -> FilePath -> IO ()-encodeReplayFile replay file =-  IO.withBinaryFile file IO.WriteMode (encodeReplayHandle replay)---- * Handles--- | 'decodeReplay'-{-# DEPRECATED-decodeReplayHandle "use Data.ByteString.Lazy.hGetContents"- #-}--decodeReplayHandle :: IO.Handle -> IO (Either String Replay)-decodeReplayHandle handle = do-  contents <- ByteString.hGetContents handle-  pure (decodeReplay contents)---- | 'encodeJson'-{-# DEPRECATED-encodeJsonHandle "use Data.ByteString.Lazy.hPut"- #-}--encodeJsonHandle :: Replay -> IO.Handle -> IO ()-encodeJsonHandle replay handle = ByteString.hPut handle (encodeJson replay)---- | 'decodeJson'-{-# DEPRECATED-decodeJsonHandle "use Data.ByteString.Lazy.hGetContents"- #-}--decodeJsonHandle :: IO.Handle -> IO (Either String Replay)-decodeJsonHandle handle = do-  contents <- ByteString.hGetContents handle-  pure (decodeJson contents)---- | 'encodeReplay'-{-# DEPRECATED-encodeReplayHandle "use Data.ByteString.Lazy.hPut"- #-}--encodeReplayHandle :: Replay -> IO.Handle -> IO ()-encodeReplayHandle replay handle = ByteString.hPut handle (encodeReplay replay)
− library/Rattletrap/Initialization.hs
@@ -1,38 +0,0 @@-module Rattletrap.Initialization where--import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Initialization = Initialization-  { initializationLocation :: Maybe Vector-  -- ^ Not every class has an initial location. See-  -- 'Rattletrap.Data.rawClassesWithLocation'.-  , initializationRotation :: Maybe Int8Vector-  -- ^ Only classes with location can have rotation, but not every one does.-  -- See 'Rattletrap.Data.rawClassesWithRotation'.-  } deriving (Eq, Ord, Show)--getInitialization :: Bool -> Bool -> BinaryBit.BitGet Initialization-getInitialization hasLocation hasRotation = do-  location <- if hasLocation-    then do-      location <- getVector-      pure (Just location)-    else pure Nothing-  rotation <- if hasRotation-    then do-      rotation <- getInt8Vector-      pure (Just rotation)-    else pure Nothing-  pure (Initialization location rotation)--putInitialization :: Initialization -> BinaryBit.BitPut ()-putInitialization initialization = do-  case initializationLocation initialization of-    Nothing -> pure ()-    Just location -> putVector location-  case initializationRotation initialization of-    Nothing -> pure ()-    Just rotation -> putInt8Vector rotation
− library/Rattletrap/Json.hs
@@ -1,112 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Rattletrap.Json where--import Rattletrap.Attribute-import Rattletrap.AttributeMapping-import Rattletrap.AttributeValue-import Rattletrap.Cache-import Rattletrap.ClassMapping-import Rattletrap.Content-import Rattletrap.Frame-import Rattletrap.Header-import Rattletrap.Initialization-import Rattletrap.KeyFrame-import Rattletrap.Mark-import Rattletrap.Message-import Rattletrap.Primitive-import Rattletrap.Property-import Rattletrap.PropertyValue-import Rattletrap.RemoteId-import Rattletrap.Replay-import Rattletrap.Replication-import Rattletrap.ReplicationValue--import qualified Control.Monad as Monad-import qualified Data.Aeson.TH as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Language.Haskell.TH as TH--$(let toSnakeCase = Aeson.camelTo2 '_'-      dropName name = drop (length (TH.nameBase name))-      optionsFor name =-        Aeson.defaultOptions-        { Aeson.constructorTagModifier = toSnakeCase-        , Aeson.fieldLabelModifier = toSnakeCase . dropName name-        , Aeson.omitNothingFields = True-        , Aeson.sumEncoding = Aeson.ObjectWithSingleField-        , Aeson.unwrapUnaryRecords = True-        }-      deriveJSON declarations name = do-        newDeclarations <- Aeson.deriveJSON (optionsFor name) name-        pure (newDeclarations ++ declarations)-      names =-        [ ''AppliedDamageAttribute-        , ''Attribute-        , ''AttributeMapping-        , ''AttributeValue-        , ''BooleanAttribute-        , ''ByteAttribute-        , ''Cache-        , ''CamSettingsAttribute-        , ''ClassMapping-        , ''ClubColorsAttribute-        , ''CompressedWord-        , ''CompressedWordVector-        , ''Content-        , ''DamageStateAttribute-        , ''DemolishAttribute-        , ''DestroyedReplication-        , ''Dictionary-        , ''EnumAttribute-        , ''ExplosionAttribute-        , ''ExtendedExplosionAttribute-        , ''FlaggedIntAttribute-        , ''Float32-        , ''FloatAttribute-        , ''Frame-        , ''GameModeAttribute-        , ''Header-        , ''Initialization-        , ''Int32-        , ''Int8-        , ''Int8Vector-        , ''IntAttribute-        , ''KeyFrame-        , ''List-        , ''LoadoutAttribute-        , ''LoadoutOnlineAttribute-        , ''LoadoutsAttribute-        , ''LoadoutsOnlineAttribute-        , ''LocationAttribute-        , ''Mark-        , ''Message-        , ''MusicStingerAttribute-        , ''PartyLeaderAttribute-        , ''PickupAttribute-        , ''PrivateMatchSettingsAttribute-        , ''ProductAttribute-        , ''Property-        , ''PropertyValue-        , ''QWordAttribute-        , ''RemoteId-        , ''Replay-        , ''Replication-        , ''ReplicationValue-        , ''ReservationAttribute-        , ''RigidBodyStateAttribute-        , ''Section-        , ''SpawnedReplication-        , ''StringAttribute-        , ''TeamPaintAttribute-        , ''Text-        , ''UniqueIdAttribute-        , ''UpdatedReplication-        , ''Vector-        , ''WeldedInfoAttribute-        , ''Word32-        , ''Word64-        , ''Word8-        ]-  in Monad.foldM deriveJSON [] names)
− library/Rattletrap/KeyFrame.hs
@@ -1,27 +0,0 @@-module Rattletrap.KeyFrame where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data KeyFrame = KeyFrame-  { keyFrameTime :: Float32-  -- ^ When this key frame occurs, in seconds.-  , keyFrameFrame :: Word32-  -- ^ The frame number of this key frame, starting from 0.-  , keyFramePosition :: Word32-  -- ^ The bit position of this key frame in the stream.-  } deriving (Eq, Ord, Show)--getKeyFrame :: Binary.Get KeyFrame-getKeyFrame = do-  time <- getFloat32-  frame <- getWord32-  position <- getWord32-  pure (KeyFrame time frame position)--putKeyFrame :: KeyFrame -> Binary.Put-putKeyFrame keyFrame = do-  putFloat32 (keyFrameTime keyFrame)-  putWord32 (keyFrameFrame keyFrame)-  putWord32 (keyFramePosition keyFrame)
− library/Rattletrap/Main.hs
@@ -1,60 +0,0 @@-module Rattletrap.Main where--import Rattletrap.Helper-import Rattletrap.Version--import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Version as Version-import qualified Network.HTTP.Client as Client-import qualified Network.HTTP.Client.TLS as Client-import qualified System.Environment as Environment---- | Gets command-line arguments and passes them to 'mainWithArgs'.-main :: IO ()-main = do-  args <- Environment.getArgs-  mainWithArgs args---- | Runs the main command-line interface. Can be used in one of three modes:------ 1. @mainWithArgs ["version"]@: Prints out the version number and exits.---    Mostly useful for debugging.--- 2. @mainWithArgs ["decode"]@: Parses a raw replay into JSON. By default---    this reads from STDIN and writes to STDOUT. To read from a file, pass a---    second argument like @mainWithArgs ["decode", "input.replay"]@. To write---    to a file, pass a third argument like---    @mainWithArgs ["decode", "input.replay", "output.json"]@.--- 3. @mainWithArgs ["encode"]@: Generates a raw replay from JSON. The---    handling of input and output is the same as decoding.-mainWithArgs :: [String] -> IO ()-mainWithArgs args = case args of-  ["version"] -> putStrLn (Version.showVersion version)-  action:files -> do-    (getInput, putOutput) <- getIO files-    input <- getInput-    output <- case action of-      "decode" -> case decodeReplay input of-        Left message -> fail message-        Right replay -> pure (encodeJson replay)-      "encode" -> case decodeJson input of-        Left message -> fail message-        Right replay -> pure (encodeReplay replay)-      _ -> fail ("unknown action: " ++ show action)-    putOutput output-  _ -> fail ("unknown arguments: " ++ show args)--getIO-  :: [FilePath] -> IO (IO ByteString.ByteString, ByteString.ByteString -> IO ())-getIO files = case files of-  [] -> pure (ByteString.getContents, ByteString.putStr)-  [i] -> pure (readUrlOrFile i, ByteString.putStr)-  [i, o] -> pure (readUrlOrFile i, ByteString.writeFile o)-  _ -> fail ("unknown files: " ++ show files)--readUrlOrFile :: FilePath -> IO ByteString.ByteString-readUrlOrFile i = case Client.parseUrlThrow i of-  Just request -> do-    manager <- Client.newTlsManager-    response <- Client.httpLbs request manager-    pure (Client.responseBody response)-  Nothing -> ByteString.readFile i
− library/Rattletrap/Mark.hs
@@ -1,23 +0,0 @@-module Rattletrap.Mark where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data Mark = Mark-  { markValue :: Text-  -- ^ Which type of mark this is, like @Team0Goal@.-  , markFrame :: Word32-  -- ^ Which frame this mark belongs to, starting from 0.-  } deriving (Eq, Ord, Show)--getMark :: Binary.Get Mark-getMark = do-  value <- getText-  frame <- getWord32-  pure (Mark value frame)--putMark :: Mark -> Binary.Put-putMark mark = do-  putText (markValue mark)-  putWord32 (markFrame mark)
− library/Rattletrap/Message.hs
@@ -1,27 +0,0 @@-module Rattletrap.Message where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data Message = Message-  { messageFrame :: Word32-  -- ^ Which frame this message belongs to, starting from 0.-  , messageName :: Text-  -- ^ The primary player's name.-  , messageValue :: Text-  -- ^ The content of the message.-  } deriving (Eq, Ord, Show)--getMessage :: Binary.Get Message-getMessage = do-  frame <- getWord32-  name <- getText-  value <- getText-  pure (Message frame name value)--putMessage :: Message -> Binary.Put-putMessage message = do-  putWord32 (messageFrame message)-  putText (messageName message)-  putText (messageValue message)
− library/Rattletrap/Primitive.hs
@@ -1,31 +0,0 @@-module Rattletrap.Primitive-  ( module Rattletrap.Primitive.CompressedWord-  , module Rattletrap.Primitive.CompressedWordVector-  , module Rattletrap.Primitive.Dictionary-  , module Rattletrap.Primitive.Float32-  , module Rattletrap.Primitive.Int8-  , module Rattletrap.Primitive.Int8Vector-  , module Rattletrap.Primitive.Int32-  , module Rattletrap.Primitive.List-  , module Rattletrap.Primitive.Section-  , module Rattletrap.Primitive.Text-  , module Rattletrap.Primitive.Vector-  , module Rattletrap.Primitive.Word8-  , module Rattletrap.Primitive.Word32-  , module Rattletrap.Primitive.Word64-  ) where--import Rattletrap.Primitive.CompressedWord-import Rattletrap.Primitive.CompressedWordVector-import Rattletrap.Primitive.Dictionary-import Rattletrap.Primitive.Float32-import Rattletrap.Primitive.Int32-import Rattletrap.Primitive.Int8-import Rattletrap.Primitive.Int8Vector-import Rattletrap.Primitive.List-import Rattletrap.Primitive.Section-import Rattletrap.Primitive.Text-import Rattletrap.Primitive.Vector-import Rattletrap.Primitive.Word32-import Rattletrap.Primitive.Word64-import Rattletrap.Primitive.Word8
− library/Rattletrap/Primitive/CompressedWord.hs
@@ -1,49 +0,0 @@-module Rattletrap.Primitive.CompressedWord where--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Bits as Bits--data CompressedWord = CompressedWord-  { compressedWordLimit :: Word-  , compressedWordValue :: Word-  } deriving (Eq, Ord, Show)--getCompressedWord :: Word -> BinaryBit.BitGet CompressedWord-getCompressedWord limit = do-  value <- getCompressedWordStep limit (getMaxBits limit) 0 0-  pure (CompressedWord limit value)--putCompressedWord :: CompressedWord -> BinaryBit.BitPut ()-putCompressedWord compressedWord = do-  let limit = compressedWordLimit compressedWord-  let value = compressedWordValue compressedWord-  let maxBits = getMaxBits limit-  let-    go position soFar = if position < maxBits-      then do-        let x = Bits.shiftL 1 position-        if maxBits > 1 && position == maxBits - 1 && soFar + x > limit-          then pure ()-          else do-            let bit = Bits.testBit value position-            BinaryBit.putBool bit-            let delta = if bit then x else 0-            go (position + 1) (soFar + delta)-      else pure ()-  go 0 0--getMaxBits :: (Integral a, Integral b) => a -> b-getMaxBits x = do-  let n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))-  if x < 1024 && x == 2 ^ n then n + 1 else n--getCompressedWordStep :: Word -> Word -> Word -> Word -> BinaryBit.BitGet Word-getCompressedWordStep limit maxBits position value = do-  let x = Bits.shiftL 1 (fromIntegral position)-  if position < maxBits && value + x <= limit-    then do-      bit <- BinaryBit.getBool-      let newValue = if bit then value + x else value-      getCompressedWordStep limit maxBits (position + 1) newValue-    else pure value
− library/Rattletrap/Primitive/CompressedWordVector.hs
@@ -1,26 +0,0 @@-module Rattletrap.Primitive.CompressedWordVector where--import Rattletrap.Primitive.CompressedWord--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data CompressedWordVector = CompressedWordVector-  { compressedWordVectorX :: CompressedWord-  , compressedWordVectorY :: CompressedWord-  , compressedWordVectorZ :: CompressedWord-  } deriving (Eq, Ord, Show)--getCompressedWordVector :: BinaryBit.BitGet CompressedWordVector-getCompressedWordVector = do-  let limit = 65536-  x <- getCompressedWord limit-  y <- getCompressedWord limit-  z <- getCompressedWord limit-  pure (CompressedWordVector x y z)--putCompressedWordVector :: CompressedWordVector -> BinaryBit.BitPut ()-putCompressedWordVector compressedWordVector = do-  putCompressedWord (compressedWordVectorX compressedWordVector)-  putCompressedWord (compressedWordVectorY compressedWordVector)-  putCompressedWord (compressedWordVectorZ compressedWordVector)
− library/Rattletrap/Primitive/Dictionary.hs
@@ -1,60 +0,0 @@-module Rattletrap.Primitive.Dictionary where--import Rattletrap.Primitive.Text--import qualified Data.Binary as Binary-import qualified Data.Map as Map-import qualified Data.Text as Text--data Dictionary a = Dictionary-  { dictionaryKeys :: [Text]-  -- ^ Objects in JSON aren't ordered, so the order of the keys must be stored-  -- separately.-  , dictionaryLastKey :: Text-  -- ^ The last key is usually @None@ but sometimes contains extra null bytes.-  , dictionaryValue :: Map.Map Text.Text a-  -- ^ Be sure to update 'dictionaryKeys' if you add, change, or remove a key-  -- in this map.-  } deriving (Eq, Ord, Show)--getDictionary :: Binary.Get a -> Binary.Get (Dictionary a)-getDictionary getValue = do-  (elements, lastKey) <- getElements getValue-  let keys = map fst elements-  let value = Map.mapKeys textValue (Map.fromList elements)-  pure (Dictionary keys lastKey value)--getElements :: Binary.Get a -> Binary.Get ([(Text, a)], Text)-getElements getValue = do-  (key, maybeValue) <- getElement getValue-  case maybeValue of-    Nothing -> pure ([], key)-    Just value -> do-      let element = (key, value)-      (elements, lastKey) <- getElements getValue-      pure (element : elements, lastKey)--getElement :: Binary.Get a -> Binary.Get (Text, Maybe a)-getElement getValue = do-  key <- getText-  if isNoneKey key-    then pure (key, Nothing)-    else do-      value <- getValue-      pure (key, Just value)--isNoneKey :: Text -> Bool-isNoneKey text = filter (/= '\x00') (textToString text) == "None"--putDictionary :: (a -> Binary.Put) -> Dictionary a -> Binary.Put-putDictionary putValue dictionary = do-  let elements = dictionaryValue dictionary-  mapM_-    ( \key -> do-      putText key-      case Map.lookup (textValue key) elements of-        Nothing -> fail ("could not find key " ++ textToString key)-        Just value -> putValue value-    )-    (dictionaryKeys dictionary)-  putText (dictionaryLastKey dictionary)
− library/Rattletrap/Primitive/Float32.hs
@@ -1,33 +0,0 @@-module Rattletrap.Primitive.Float32 where--import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.IEEE754 as IEEE754-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString--newtype Float32 = Float32-  { float32Value :: Float-  } deriving (Eq, Ord, Show)--getFloat32 :: Binary.Get Float32-getFloat32 = do-  float32 <- IEEE754.getFloat32le-  pure (Float32 float32)--putFloat32 :: Float32 -> Binary.Put-putFloat32 float32 = IEEE754.putFloat32le (float32Value float32)--getFloat32Bits :: BinaryBit.BitGet Float32-getFloat32Bits = do-  bytes <- BinaryBit.getLazyByteString 4-  pure (Binary.runGet getFloat32 (reverseBytes bytes))--putFloat32Bits :: Float32 -> BinaryBit.BitPut ()-putFloat32Bits float32 = do-  let bytes = Binary.runPut (putFloat32 float32)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Primitive/Int32.hs
@@ -1,33 +0,0 @@-module Rattletrap.Primitive.Int32 where--import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Int as Int--newtype Int32 = Int32-  { int32Value :: Int.Int32-  } deriving (Eq, Ord, Show)--getInt32 :: Binary.Get Int32-getInt32 = do-  int32 <- Binary.getInt32le-  pure (Int32 int32)--putInt32 :: Int32 -> Binary.Put-putInt32 int32 = Binary.putInt32le (int32Value int32)--getInt32Bits :: BinaryBit.BitGet Int32-getInt32Bits = do-  bytes <- BinaryBit.getLazyByteString 4-  pure (Binary.runGet getInt32 (reverseBytes bytes))--putInt32Bits :: Int32 -> BinaryBit.BitPut ()-putInt32Bits int32 = do-  let bytes = Binary.runPut (putInt32 int32)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Primitive/Int8.hs
@@ -1,32 +0,0 @@-module Rattletrap.Primitive.Int8 where--import Rattletrap.Utility--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Int as Int--newtype Int8 = Int8-  { int8Value :: Int.Int8-  } deriving (Eq, Ord, Show)--getInt8 :: Binary.Get Int8-getInt8 = do-  int8 <- Binary.getInt8-  pure (Int8 int8)--putInt8 :: Int8 -> Binary.Put-putInt8 int8 = Binary.putInt8 (int8Value int8)--getInt8Bits :: BinaryBit.BitGet Int8-getInt8Bits = do-  bytes <- BinaryBit.getLazyByteString 1-  pure (Binary.runGet getInt8 (reverseBytes bytes))--putInt8Bits :: Int8 -> BinaryBit.BitPut ()-putInt8Bits int8 = do-  let bytes = Binary.runPut (putInt8 int8)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Primitive/Int8Vector.hs
@@ -1,41 +0,0 @@-module Rattletrap.Primitive.Int8Vector where--import Rattletrap.Primitive.Int8--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Int8Vector = Int8Vector-  { int8VectorX :: Maybe Int8-  , int8VectorY :: Maybe Int8-  , int8VectorZ :: Maybe Int8-  } deriving (Eq, Ord, Show)--getInt8Vector :: BinaryBit.BitGet Int8Vector-getInt8Vector = do-  x <- getInt8VectorField-  y <- getInt8VectorField-  z <- getInt8VectorField-  pure (Int8Vector x y z)--putInt8Vector :: Int8Vector -> BinaryBit.BitPut ()-putInt8Vector int8Vector = do-  putInt8VectorField (int8VectorX int8Vector)-  putInt8VectorField (int8VectorY int8Vector)-  putInt8VectorField (int8VectorZ int8Vector)--getInt8VectorField :: BinaryBit.BitGet (Maybe Int8)-getInt8VectorField = do-  hasField <- BinaryBit.getBool-  if hasField-    then do-      field <- getInt8Bits-      pure (Just field)-    else pure Nothing--putInt8VectorField :: Maybe Int8 -> BinaryBit.BitPut ()-putInt8VectorField maybeField = case maybeField of-  Nothing -> BinaryBit.putBool False-  Just field -> do-    BinaryBit.putBool True-    putInt8Bits field
− library/Rattletrap/Primitive/List.hs
@@ -1,22 +0,0 @@-module Rattletrap.Primitive.List where--import Rattletrap.Primitive.Word32--import qualified Control.Monad as Monad-import qualified Data.Binary as Binary--newtype List a = List-  { listValue :: [a]-  } deriving (Eq, Ord, Show)--getList :: Binary.Get a -> Binary.Get (List a)-getList getElement = do-  size <- getWord32-  elements <- Monad.replicateM (fromIntegral (word32Value size)) getElement-  pure (List elements)--putList :: (a -> Binary.Put) -> List a -> Binary.Put-putList putElement list = do-  let elements = listValue list-  putWord32 (Word32 (fromIntegral (length elements)))-  mapM_ putElement elements
− library/Rattletrap/Primitive/Section.hs
@@ -1,54 +0,0 @@-module Rattletrap.Primitive.Section where--import Rattletrap.Crc-import Rattletrap.Primitive.Word32--import qualified Control.Monad as Monad-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString---- | A section is a large piece of a 'Rattletrap.Replay.Replay'. It has a--- 32-bit size (in bytes), a 32-bit CRC (see "Rattletrap.Crc"), and then a--- bunch of data (the body). This interface is provided so that you don't have--- to think about the size and CRC.-newtype Section a = Section-  { sectionBody :: a-  -- ^ The actual content in the section.-  } deriving (Eq, Ord, Show)---- | Given a way to get the 'sectionBody', gets a section. This will fail if--- the section's CRC does not match.------ @--- let header = 'Data.Binary.Get.runGet' ('getSection' 'Rattletrap.Header.getHeader') bytes--- @-getSection :: Binary.Get a -> Binary.Get (Section a)-getSection getBody = do-  size <- getWord32-  crc <- getWord32-  rawBody <- Binary.getLazyByteString (fromIntegral (word32Value size))-  let actualCrc = Word32 (getCrc32 rawBody)-  Monad.when (actualCrc /= crc) (fail (crcMessage actualCrc crc))-  let body = Binary.runGet getBody rawBody-  pure (Section body)---- | Given a way to put the 'sectionBody', puts a section. This will also put--- the size and CRC.------ @--- let bytes = 'Data.Binary.Put.runPut' ('putSection' 'Rattletrap.Content.putContent' content)--- @-putSection :: (a -> Binary.Put) -> Section a -> Binary.Put-putSection putBody section = do-  let rawBody = Binary.runPut (putBody (sectionBody section))-  let size = ByteString.length rawBody-  let crc = getCrc32 rawBody-  putWord32 (Word32 (fromIntegral size))-  putWord32 (Word32 crc)-  Binary.putLazyByteString rawBody--crcMessage :: Word32 -> Word32 -> String-crcMessage actual expected = unwords-  ["actual CRC", show actual, "does not match expected CRC", show expected]
− library/Rattletrap/Primitive/Text.hs
@@ -1,94 +0,0 @@-module Rattletrap.Primitive.Text where--import Rattletrap.Primitive.Int32-import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Char as Char-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Encoding--newtype Text = Text-  { textValue :: Text.Text-  } deriving (Eq, Ord, Show)--getText :: Binary.Get Text-getText = do-  rawSize <- getInt32-  let decode = getTextDecoder rawSize-  let size = normalizeTextSize rawSize-  bytes <- Binary.getLazyByteString size-  let text = dropNull (decode bytes)-  pure (Text text)--putText :: Text -> Binary.Put-putText text = do-  let size = getTextSize text-  let encode = getTextEncoder size-  putInt32 size-  Binary.putLazyByteString (encode (addNull (textValue text)))--getTextBits :: BinaryBit.BitGet Text-getTextBits = do-  rawSize <- getInt32Bits-  let decode = getTextDecoder rawSize-  let size = normalizeTextSize rawSize-  bytes <- BinaryBit.getLazyByteString size-  let text = dropNull (decode (reverseBytes bytes))-  pure (Text text)--putTextBits :: Text -> BinaryBit.BitPut ()-putTextBits text = do-  let size = getTextSize text-  let encode = getTextEncoder size-  putInt32Bits size-  BinaryBit.putByteString-    (ByteString.toStrict (reverseBytes (encode (addNull (textValue text)))))--stringToText :: String -> Text-stringToText string = Text (Text.pack string)--textToString :: Text -> String-textToString text = Text.unpack (textValue text)--getTextSize :: Text -> Int32-getTextSize text =-  let-    value = textValue text-    scale = if Text.all Char.isLatin1 value then 1 else -1-    rawSize =-      if Text.null value then 0 else fromIntegral (Text.length value) + 1-    size = if value == Text.pack "\x00\x00\x00None"-      then 0x05000000-      else scale * rawSize-  in-    Int32 size--normalizeTextSize :: Integral a => Int32 -> a-normalizeTextSize size = case int32Value size of-  0x05000000 -> 8-  x -> if x < 0 then (-2 * fromIntegral x) else fromIntegral x--getTextDecoder :: Int32 -> ByteString.ByteString -> Text.Text-getTextDecoder size bytes =-  let-    decode =-      if size < Int32 0 then Encoding.decodeUtf16LE else Encoding.decodeLatin1-  in-    decode (ByteString.toStrict bytes)--getTextEncoder :: Int32 -> Text.Text -> ByteString.ByteString-getTextEncoder size text = if size < Int32 0-  then ByteString.fromStrict (Encoding.encodeUtf16LE text)-  else encodeLatin1 text--dropNull :: Text.Text -> Text.Text-dropNull = Text.dropWhileEnd (== '\x00')--addNull :: Text.Text -> Text.Text-addNull text = if Text.null text then text else Text.snoc text '\x00'
− library/Rattletrap/Primitive/Vector.hs
@@ -1,41 +0,0 @@-module Rattletrap.Primitive.Vector where--import Rattletrap.Primitive.CompressedWord--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Vector = Vector-  { vectorBias :: Word-  , vectorX :: Int-  , vectorY :: Int-  , vectorZ :: Int-  } deriving (Eq, Ord, Show)--getVector :: BinaryBit.BitGet Vector-getVector = do-  bitSize <- getCompressedWord 19-  let limit = 2 ^ (compressedWordValue bitSize + 2)-  dx <- getCompressedWord limit-  dy <- getCompressedWord limit-  dz <- getCompressedWord limit-  let fromCompressedWord x = fromIntegral (compressedWordValue x)-  let bias = 2 ^ (fromCompressedWord bitSize + 1 :: Word)-  let x = fromCompressedWord dx - fromIntegral bias-  let y = fromCompressedWord dy - fromIntegral bias-  let z = fromCompressedWord dz - fromIntegral bias-  pure (Vector bias x y z)--putVector :: Vector -> BinaryBit.BitPut ()-putVector vector = do-  let-    bitSize =-      round (logBase (2 :: Float) (fromIntegral (vectorBias vector))) - 1-  putCompressedWord (CompressedWord 19 bitSize)-  let dx = fromIntegral (vectorX vector + fromIntegral (vectorBias vector))-  let dy = fromIntegral (vectorY vector + fromIntegral (vectorBias vector))-  let dz = fromIntegral (vectorZ vector + fromIntegral (vectorBias vector))-  let limit = 2 ^ (bitSize + 2)-  putCompressedWord (CompressedWord limit dx)-  putCompressedWord (CompressedWord limit dy)-  putCompressedWord (CompressedWord limit dz)
− library/Rattletrap/Primitive/Word32.hs
@@ -1,33 +0,0 @@-module Rattletrap.Primitive.Word32 where--import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Word as Word--newtype Word32 = Word32-  { word32Value :: Word.Word32-  } deriving (Eq, Ord, Show)--getWord32 :: Binary.Get Word32-getWord32 = do-  word32 <- Binary.getWord32le-  pure (Word32 word32)--putWord32 :: Word32 -> Binary.Put-putWord32 word32 = Binary.putWord32le (word32Value word32)--getWord32Bits :: BinaryBit.BitGet Word32-getWord32Bits = do-  bytes <- BinaryBit.getLazyByteString 4-  pure (Binary.runGet getWord32 (reverseBytes bytes))--putWord32Bits :: Word32 -> BinaryBit.BitPut ()-putWord32Bits word32 = do-  let bytes = Binary.runPut (putWord32 word32)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Primitive/Word64.hs
@@ -1,33 +0,0 @@-module Rattletrap.Primitive.Word64 where--import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Word as Word--newtype Word64 = Word64-  { word64Value :: Word.Word64-  } deriving (Eq, Ord, Show)--getWord64 :: Binary.Get Word64-getWord64 = do-  word64 <- Binary.getWord64le-  pure (Word64 word64)--putWord64 :: Word64 -> Binary.Put-putWord64 word64 = Binary.putWord64le (word64Value word64)--getWord64Bits :: BinaryBit.BitGet Word64-getWord64Bits = do-  bytes <- BinaryBit.getLazyByteString 8-  pure (Binary.runGet getWord64 (reverseBytes bytes))--putWord64Bits :: Word64 -> BinaryBit.BitPut ()-putWord64Bits word64 = do-  let bytes = Binary.runPut (putWord64 word64)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Primitive/Word8.hs
@@ -1,33 +0,0 @@-module Rattletrap.Primitive.Word8 where--import Rattletrap.Utility--import qualified Data.Binary as Binary-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Word as Word--newtype Word8 = Word8-  { word8Value :: Word.Word8-  } deriving (Eq, Ord, Show)--getWord8 :: Binary.Get Word8-getWord8 = do-  word8 <- Binary.getWord8-  pure (Word8 word8)--putWord8 :: Word8 -> Binary.Put-putWord8 word8 = Binary.putWord8 (word8Value word8)--getWord8Bits :: BinaryBit.BitGet Word8-getWord8Bits = do-  bytes <- BinaryBit.getLazyByteString 1-  pure (Binary.runGet getWord8 (reverseBytes bytes))--putWord8Bits :: Word8 -> BinaryBit.BitPut ()-putWord8Bits word8 = do-  let bytes = Binary.runPut (putWord8 word8)-  BinaryBit.putByteString (ByteString.toStrict (reverseBytes bytes))
− library/Rattletrap/Property.hs
@@ -1,26 +0,0 @@-module Rattletrap.Property where--import Rattletrap.Primitive-import Rattletrap.PropertyValue--import qualified Data.Binary as Binary--data Property = Property-  { propertyKind :: Text-  , propertySize :: Word64-  -- ^ Not used.-  , propertyValue :: PropertyValue Property-  } deriving (Eq, Ord, Show)--getProperty :: Binary.Get Property-getProperty = do-  kind <- getText-  size <- getWord64-  value <- getPropertyValue getProperty kind-  pure (Property kind size value)--putProperty :: Property -> Binary.Put-putProperty property = do-  putText (propertyKind property)-  putWord64 (propertySize property)-  putPropertyValue putProperty (propertyValue property)
− library/Rattletrap/PropertyValue.hs
@@ -1,69 +0,0 @@-module Rattletrap.PropertyValue where--import Rattletrap.Primitive--import qualified Data.Binary as Binary--data PropertyValue a-  = ArrayProperty (List (Dictionary a))-  -- ^ Yes, a list of dictionaries. No, it doesn't make sense. These usually-  -- only have one element.-  | BoolProperty Word8-  | ByteProperty Text-                 (Maybe Text)-  -- ^ This is a strange name for essentially a key-value pair.-  | FloatProperty Float32-  | IntProperty Int32-  | NameProperty Text-  -- ^ It's unclear how exactly this is different than a 'StrProperty'.-  | QWordProperty Word64-  | StrProperty Text-  deriving (Eq, Ord, Show)--getPropertyValue :: Binary.Get a -> Text -> Binary.Get (PropertyValue a)-getPropertyValue getProperty kind = case textToString kind of-  "ArrayProperty" -> do-    list <- getList (getDictionary getProperty)-    pure (ArrayProperty list)-  "BoolProperty" -> do-    word8 <- getWord8-    pure (BoolProperty word8)-  "ByteProperty" -> do-    k <- getText-    v <- if textToString k == "OnlinePlatform_Steam"-      then pure Nothing-      else do-        v <- getText-        pure (Just v)-    pure (ByteProperty k v)-  "FloatProperty" -> do-    float32 <- getFloat32-    pure (FloatProperty float32)-  "IntProperty" -> do-    int32 <- getInt32-    pure (IntProperty int32)-  "NameProperty" -> do-    text <- getText-    pure (NameProperty text)-  "QWordProperty" -> do-    word64 <- getWord64-    pure (QWordProperty word64)-  "StrProperty" -> do-    text <- getText-    pure (StrProperty text)-  _ -> fail ("don't know how to read property value " ++ show kind)--putPropertyValue :: (a -> Binary.Put) -> PropertyValue a -> Binary.Put-putPropertyValue putProperty value = case value of-  ArrayProperty list -> putList (putDictionary putProperty) list-  BoolProperty word8 -> putWord8 word8-  ByteProperty k mv -> do-    putText k-    case mv of-      Nothing -> pure ()-      Just v -> putText v-  FloatProperty float32 -> putFloat32 float32-  IntProperty int32 -> putInt32 int32-  NameProperty text -> putText text-  QWordProperty word64 -> putWord64 word64-  StrProperty text -> putText text
− library/Rattletrap/RemoteId.hs
@@ -1,53 +0,0 @@-module Rattletrap.RemoteId where--import Rattletrap.Primitive-import Rattletrap.Utility--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Encoding-import qualified Data.Word as Word--data RemoteId-  = PlayStationId Text.Text-                  [Word.Word8]-  | SplitscreenId Word.Word32-  | SteamId Word64-  | XboxId Word64-  deriving (Eq, Ord, Show)--getRemoteId :: (Int, Int, Int) -> Word8 -> BinaryBit.BitGet RemoteId-getRemoteId (_, _, patchVersion) systemId = case word8Value systemId of-  0 -> do-    word24 <- BinaryBit.getWord32be 24-    pure (SplitscreenId word24)-  1 -> do-    word64 <- getWord64Bits-    pure (SteamId word64)-  2 -> do-    rawName <- BinaryBit.getLazyByteString 16-    let-      name = Text.dropWhileEnd-        (== '\x00')-        (Encoding.decodeLatin1 (ByteString.toStrict (reverseBytes rawName)))-      numBytes = if patchVersion >= 1 then 24 else 16-    bytes <- BinaryBit.getLazyByteString numBytes-    pure (PlayStationId name (ByteString.unpack bytes))-  4 -> do-    word64 <- getWord64Bits-    pure (XboxId word64)-  _ -> fail ("unknown system id " ++ show systemId)--putRemoteId :: RemoteId -> BinaryBit.BitPut ()-putRemoteId remoteId = case remoteId of-  PlayStationId name bytes -> do-    let-      rawName = ByteString.toStrict-        (reverseBytes (padBytes (16 :: Int) (encodeLatin1 name)))-    BinaryBit.putByteString rawName-    BinaryBit.putByteString (ByteString.toStrict (ByteString.pack bytes))-  SplitscreenId word24 -> BinaryBit.putWord32be 24 word24-  SteamId word64 -> putWord64Bits word64-  XboxId word64 -> putWord64Bits word64
− library/Rattletrap/Replay.hs
@@ -1,39 +0,0 @@-module Rattletrap.Replay where--import Rattletrap.Content-import Rattletrap.Header-import Rattletrap.Primitive.Section--import qualified Data.Binary as Binary---- | A Rocket League replay.-data Replay = Replay-  { replayHeader :: Section Header-  -- ^ This has most of the high-level metadata.-  , replayContent :: Section Content-  -- ^ This has most of the low-level game data.-  } deriving (Eq, Ord, Show)---- | Parses a raw replay. Use this with 'Data.Binary.Get.runGet'.------ @--- let replay = 'Data.Binary.Get.runGet' 'getReplay' bytes--- @-getReplay :: Binary.Get Replay-getReplay = do-  header <- getSection getHeader-  let version = getVersion (sectionBody header)-  let numFrames = getNumFrames (sectionBody header)-  let maxChannels = getMaxChannels (sectionBody header)-  content <- getSection (getContent version numFrames maxChannels)-  pure (Replay header content)---- | Generates a raw replay. Use this with 'Data.Binary.Put.runPut'.------ @--- let bytes = 'Data.Binary.Put.runPut' ('putReplay' replay)--- @-putReplay :: Replay -> Binary.Put-putReplay replay = do-  putSection putHeader (replayHeader replay)-  putSection putContent (replayContent replay)
− library/Rattletrap/Replication.hs
@@ -1,66 +0,0 @@-module Rattletrap.Replication where--import Rattletrap.ActorMap-import Rattletrap.ClassAttributeMap-import Rattletrap.Primitive-import Rattletrap.ReplicationValue--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data Replication = Replication-  { replicationActorId :: CompressedWord-  , replicationValue :: ReplicationValue-  } deriving (Eq, Ord, Show)--getReplications-  :: (Int, Int, Int)-  -> Word-  -> ClassAttributeMap-  -> ActorMap-  -> BinaryBit.BitGet ([Replication], ActorMap)-getReplications version maxChannels classAttributeMap actorMap = do-  maybeReplication <- getReplication-    version-    maxChannels-    classAttributeMap-    actorMap-  case maybeReplication of-    Nothing -> pure ([], actorMap)-    Just (replication, newActorMap) -> do-      (replications, newerActorMap) <- getReplications-        version-        maxChannels-        classAttributeMap-        newActorMap-      pure (replication : replications, newerActorMap)--putReplications :: [Replication] -> BinaryBit.BitPut ()-putReplications replications = do-  mapM_ putReplication replications-  BinaryBit.putBool False--getReplication-  :: (Int, Int, Int)-  -> Word-  -> ClassAttributeMap-  -> ActorMap-  -> BinaryBit.BitGet (Maybe (Replication, ActorMap))-getReplication version maxChannels classAttributeMap actorMap = do-  hasReplication <- BinaryBit.getBool-  if not hasReplication-    then pure Nothing-    else do-      actorId <- getCompressedWord maxChannels-      (value, newActorMap) <- getReplicationValue-        version-        classAttributeMap-        actorMap-        actorId-      pure (Just (Replication actorId value, newActorMap))--putReplication :: Replication -> BinaryBit.BitPut ()-putReplication replication = do-  BinaryBit.putBool True-  putCompressedWord (replicationActorId replication)-  putReplicationValue (replicationValue replication)
− library/Rattletrap/Replication/Destroyed.hs
@@ -1,17 +0,0 @@-module Rattletrap.Replication.Destroyed where--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit---- | Destroyed replications don't actually contain any extra information. All--- you need to know is the actor's ID, which is given by the--- 'Rattletrap.Replication.Replication'.-data DestroyedReplication = DestroyedReplication-  {-  } deriving (Eq, Ord, Show)--getDestroyedReplication :: BinaryBit.BitGet DestroyedReplication-getDestroyedReplication = pure DestroyedReplication--putDestroyedReplication :: DestroyedReplication -> BinaryBit.BitPut ()-putDestroyedReplication _ = pure ()
− library/Rattletrap/Replication/Spawned.hs
@@ -1,88 +0,0 @@-module Rattletrap.Replication.Spawned where--import Rattletrap.ActorMap-import Rattletrap.ClassAttributeMap-import Rattletrap.Initialization-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Map as Map--data SpawnedReplication = SpawnedReplication-  { spawnedReplicationFlag :: Bool-  -- ^ Unclear what this is.-  , spawnedReplicationNameIndex :: Maybe Word32-  , spawnedReplicationName :: Maybe Text-  -- ^ Read-only! Changing a replication's name requires editing the-  -- 'spawnedReplicationNameIndex' and maybe the class attribute map.-  , spawnedReplicationObjectId :: Word32-  , spawnedReplicationObjectName :: Text-  -- ^ Read-only! Changing a replication's object requires editing the class-  -- attribute map.-  , spawnedReplicationClassName :: Text-  -- ^ Read-only! Changing a replication's class requires editing the class-  -- attribute map.-  , spawnedReplicationInitialization :: Initialization-  } deriving (Eq, Ord, Show)--getSpawnedReplication-  :: (Int, Int, Int)-  -> ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> BinaryBit.BitGet (SpawnedReplication, ActorMap)-getSpawnedReplication version classAttributeMap actorMap actorId = do-  flag <- BinaryBit.getBool-  nameIndex <- if version < (868, 14, 0)-    then pure Nothing-    else do-      nameIndex <- getWord32Bits-      pure (Just nameIndex)-  name <- lookupName classAttributeMap nameIndex-  objectId <- getWord32Bits-  let newActorMap = Map.insert actorId objectId actorMap-  objectName <- lookupObjectName classAttributeMap objectId-  className <- lookupClassName objectName-  let hasLocation = classHasLocation className-  let hasRotation = classHasRotation className-  initialization <- getInitialization hasLocation hasRotation-  pure-    ( SpawnedReplication-      flag-      nameIndex-      name-      objectId-      objectName-      className-      initialization-    , newActorMap-    )--putSpawnedReplication :: SpawnedReplication -> BinaryBit.BitPut ()-putSpawnedReplication spawnedReplication = do-  BinaryBit.putBool (spawnedReplicationFlag spawnedReplication)-  case spawnedReplicationNameIndex spawnedReplication of-    Nothing -> pure ()-    Just nameIndex -> putWord32Bits nameIndex-  putWord32Bits (spawnedReplicationObjectId spawnedReplication)-  putInitialization (spawnedReplicationInitialization spawnedReplication)--lookupName :: Monad m => ClassAttributeMap -> Maybe Word32 -> m (Maybe Text)-lookupName classAttributeMap maybeNameIndex = case maybeNameIndex of-  Nothing -> pure Nothing-  Just nameIndex ->-    case getName (classAttributeMapNameMap classAttributeMap) nameIndex of-      Nothing -> fail ("could not get name for index " ++ show nameIndex)-      Just name -> pure (Just name)--lookupObjectName :: Monad m => ClassAttributeMap -> Word32 -> m Text-lookupObjectName classAttributeMap objectId =-  case getObjectName (classAttributeMapObjectMap classAttributeMap) objectId of-    Nothing -> fail ("could not get object name for id " ++ show objectId)-    Just objectName -> pure objectName--lookupClassName :: Monad m => Text -> m Text-lookupClassName objectName = case getClassName objectName of-  Nothing -> fail ("could not get class name for object " ++ show objectName)-  Just className -> pure className
− library/Rattletrap/Replication/Updated.hs
@@ -1,27 +0,0 @@-module Rattletrap.Replication.Updated where--import Rattletrap.ActorMap-import Rattletrap.Attribute-import Rattletrap.ClassAttributeMap-import Rattletrap.Primitive--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--newtype UpdatedReplication = UpdatedReplication-  { updatedReplicationAttributes :: [Attribute]-  } deriving (Eq, Ord, Show)--getUpdatedReplication-  :: (Int, Int, Int)-  -> ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> BinaryBit.BitGet UpdatedReplication-getUpdatedReplication version classAttributeMap actorMap actorId = do-  attributes <- getAttributes version classAttributeMap actorMap actorId-  pure (UpdatedReplication attributes)--putUpdatedReplication :: UpdatedReplication -> BinaryBit.BitPut ()-putUpdatedReplication updatedReplication =-  putAttributes (updatedReplicationAttributes updatedReplication)
− library/Rattletrap/ReplicationValue.hs
@@ -1,65 +0,0 @@-module Rattletrap.ReplicationValue-  ( module Rattletrap.ReplicationValue-  , module Rattletrap.Replication.Destroyed-  , module Rattletrap.Replication.Spawned-  , module Rattletrap.Replication.Updated-  ) where--import Rattletrap.ActorMap-import Rattletrap.ClassAttributeMap-import Rattletrap.Primitive-import Rattletrap.Replication.Destroyed-import Rattletrap.Replication.Spawned-import Rattletrap.Replication.Updated--import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit--data ReplicationValue-  = SpawnedReplicationValue SpawnedReplication-  -- ^ Creates a new actor.-  | UpdatedReplicationValue UpdatedReplication-  -- ^ Updates an existing actor.-  | DestroyedReplicationValue DestroyedReplication-  -- ^ Destroys an existing actor.-  deriving (Eq, Ord, Show)--getReplicationValue-  :: (Int, Int, Int)-  -> ClassAttributeMap-  -> ActorMap-  -> CompressedWord-  -> BinaryBit.BitGet (ReplicationValue, ActorMap)-getReplicationValue version classAttributeMap actorMap actorId = do-  isOpen <- BinaryBit.getBool-  if isOpen-    then do-      isNew <- BinaryBit.getBool-      if isNew-        then do-          (x, newActorMap) <- getSpawnedReplication-            version-            classAttributeMap-            actorMap-            actorId-          pure (SpawnedReplicationValue x, newActorMap)-        else do-          x <- getUpdatedReplication version classAttributeMap actorMap actorId-          pure (UpdatedReplicationValue x, actorMap)-    else do-      x <- getDestroyedReplication-      pure (DestroyedReplicationValue x, actorMap)--putReplicationValue :: ReplicationValue -> BinaryBit.BitPut ()-putReplicationValue value = case value of-  SpawnedReplicationValue x -> do-    BinaryBit.putBool True-    BinaryBit.putBool True-    putSpawnedReplication x-  UpdatedReplicationValue x -> do-    BinaryBit.putBool True-    BinaryBit.putBool False-    putUpdatedReplication x-  DestroyedReplicationValue x -> do-    BinaryBit.putBool False-    putDestroyedReplication x
− library/Rattletrap/Utility.hs
@@ -1,30 +0,0 @@-module Rattletrap.Utility where--import qualified Data.Bits as Bits-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.ByteString.Lazy.Char8 as ByteString8-import qualified Data.Text as Text-import qualified Data.Word as Word--encodeLatin1 :: Text.Text -> ByteString.ByteString-encodeLatin1 text = ByteString8.pack (Text.unpack text)--padBytes :: Integral a => a -> ByteString.ByteString -> ByteString.ByteString-padBytes size bytes = ByteString.concat-  [ bytes-  , ByteString.replicate (fromIntegral size - ByteString.length bytes) 0x00-  ]--reverseByte :: Word.Word8 -> Word.Word8-reverseByte byte =-  Bits.shiftR (byte Bits..&. Bits.bit 7) 7-    + Bits.shiftR (byte Bits..&. Bits.bit 6) 5-    + Bits.shiftR (byte Bits..&. Bits.bit 5) 3-    + Bits.shiftR (byte Bits..&. Bits.bit 4) 1-    + Bits.shiftL (byte Bits..&. Bits.bit 3) 1-    + Bits.shiftL (byte Bits..&. Bits.bit 2) 3-    + Bits.shiftL (byte Bits..&. Bits.bit 1) 5-    + Bits.shiftL (byte Bits..&. Bits.bit 0) 7--reverseBytes :: ByteString.ByteString -> ByteString.ByteString-reverseBytes = ByteString.map reverseByte
− library/Rattletrap/Version.hs
@@ -1,7 +0,0 @@-module Rattletrap.Version where--import qualified Data.Version as Version-import qualified Paths_rattletrap as This--version :: Version.Version-version = This.version
− package.yaml
@@ -1,63 +0,0 @@-name: rattletrap-version: 3.1.2--category: Game-description: Rattletrap parses and generates Rocket League replays.-extra-source-files:-  - CHANGELOG.markdown-  - replays/*.replay-  - package.yaml-  - README.markdown-  - stack.yaml-github: tfausak/rattletrap-license-file: LICENSE.markdown-license: MIT-maintainer: Taylor Fausak-synopsis: Parse and generate Rocket League replays.--dependencies:-  - aeson >= 1.2.3 && < 1.3-  - base >= 4.10.1 && < 4.11-  - bimap >= 0.3.3 && < 0.4-  - binary >= 0.8.5 && < 0.9-  - binary-bits >= 0.5 && < 0.6-  - bytestring >= 0.10.8 && < 0.11-  - containers >= 0.5.10 && < 0.6-  - data-binary-ieee754 >= 0.4.4 && < 0.5-  - http-client >= 0.5.7 && < 0.6-  - http-client-tls >= 0.3.5 && < 0.4-  - template-haskell >= 2.12.0 && < 2.13-  - text >= 1.2.2 && < 1.3-  - vector >= 0.12.0 && < 0.13-ghc-options: -Wall--library:-  default-extensions:-    - Strict-  other-modules: Paths_rattletrap-  source-dirs: library--executables:-  rattletrap:-    dependencies:-      - rattletrap-    ghc-options:-      - -rtsopts-      - -threaded-      - -with-rtsopts=-N-    main: Main.hs-    source-dirs: executables--tests:-  test:-    dependencies:-      - filepath >= 1.4.1 && < 1.5-      - hspec >= 2.4.4 && < 2.5-      - rattletrap-      - temporary >= 1.2.1 && < 1.3-    ghc-options:-      - -rtsopts-      - -threaded-      - -with-rtsopts=-N-    main: Main.hs-    source-dirs: tests
rattletrap.cabal view
@@ -1,244 +1,233 @@--- This file has been generated from package.yaml by hpack version 0.20.0.------ see: https://github.com/sol/hpack------ hash: ffc648678c84d8942a93a5168812a9a02db2b7471f0f42a0576f8409235cc6cd+cabal-version: 2.2+name: rattletrap+version: 14.1.4+synopsis: Parse and generate Rocket League replays.+description:+  Warning: This package is not maintained anymore.+  .+  Rattletrap parses and generates Rocket League replays. -name:           rattletrap-version:        3.1.2-synopsis:       Parse and generate Rocket League replays.-description:    Rattletrap parses and generates Rocket League replays.-category:       Game-homepage:       https://github.com/tfausak/rattletrap#readme-bug-reports:    https://github.com/tfausak/rattletrap/issues-maintainer:     Taylor Fausak-license:        MIT-license-file:   LICENSE.markdown-build-type:     Simple-cabal-version:  >= 1.10+build-type: Simple+category: Game+extra-doc-files:+  CHANGELOG.md+  README.md -extra-source-files:-    CHANGELOG.markdown-    package.yaml-    README.markdown-    replays/00080014003600090000036E0F65CCEB.replay-    replays/07E925B1423653D44CB8B4B2524792C1.replay-    replays/0AD21FEE4A512906BEB6C98136AAF49A.replay-    replays/1205D96C4D819800927791820096CD49.replay-    replays/160CA83E41083BFD8E6315B4BFCA0561.replay-    replays/16D580EF479483E015207C901776F9FB.replay-    replays/18D6738D415B70B5BE4C299588D3C141.replay-    replays/1A126AC24CAA0DB0E98835BD960B8AF8.replay-    replays/1AE415514DFC65DCBF8B8391AD35488D.replay-    replays/1BC2D01444ACE577D01E988EADD4DFD0.replay-    replays/1D1DE97D4941C86E43FE0093563DB621.replay-    replays/1EF90FCC4F719F606A5327B3CDD782A4.replay-    replays/1F3798E540B0C37A946561ABBB3037F9.replay-    replays/211466D04B983F5A33CC2FA1D5928672.replay-    replays/22660E3649FC7971E5653692473D4318.replay-    replays/22BACD794ABE7B92E50E9CBDBD9C59CE.replay-    replays/27B6A7B64553F0F685874584F96BAB1B.replay-    replays/29F582C34A65EB34D358A784CBE3C189.replay-    replays/2CFE577044E651D3FA9DBF83ECA8BCC3.replay-    replays/338173964F9F71EBDD31058A1936CBB4.replay-    replays/372DBFCA4BDB340E4357B6BD43032802.replay-    replays/387F059C47C09E253C875CA990EFD9F2.replay-    replays/3EA147DD485B8DD24810689A7A989E44.replay-    replays/4126861E477F4A03DE2A4080374D7908.replay-    replays/42F0D8DA4FC89AE7B80FCAB7F637A8EA.replay-    replays/504ED825482186E771FAA9B642CE5CE4.replay-    replays/520E1BFF468CF6C3C48D1EA85D9C7909.replay-    replays/52AA67F94090C19D33C5009E54D31FE4.replay-    replays/540DA764423C8FB24EB9D486D982F16F.replay-    replays/551CA4D44FF2B86015DE44A6B5790D4C.replay-    replays/6320E51C49066A7C210A2993C2201D5F.replay-    replays/6688EEE34BFEB3EC3A9E3283098CC712.replay-    replays/6B0D1614417085A7AAD82EAA30D8DABE.replay-    replays/6D1B06D844A5BB91B81FD4B5B28F08BA.replay-    replays/6F7CFCD24638F8A6567AB3A8B9958A90.replay-    replays/7109EB9846D303E54B7ACBA792036213.replay-    replays/7BF6073F4614CE0A438994B9A260DA6A.replay-    replays/89CBA30E46FA5385BDD35DA4285D4D2E.replay-    replays/8AE551FF406D7B82ED853B8C7BFF8CDA.replay-    replays/9704208245D7DD851F2FB2BC7DFD9AC3.replay-    replays/98E58A904D713F2DE202358E8573265D.replay-    replays/A128B3AB45D5A18E3EF9CF93C9576BCE.replay-    replays/A52F804845573D8DA65E97BF59026A43.replay-    replays/A558B1B44124D6E021640884E8EEC2A7.replay-    replays/A6711CE74272B2E663DCC9A200A218E3.replay-    replays/A7F001A1417A19BFA8C90990D8F7C2FF.replay-    replays/AFB1F46A49737E36928E1EABC6F5B7AD.replay-    replays/B9F9B87D4A9D0A3D25D4EC91C0401DE2.replay-    replays/C14F7E0E4D9B5E6BE9AD5D8ED56B174C.replay-    replays/C8372B1345B1803DEF039F815DBD802D.replay-    replays/CC4CA70D4F7A67EBAD0ED9B9923106F7.replay-    replays/D0449F5F4AA775B86FFA7DA2B5A3204E.replay-    replays/D428F81646A98C25902CE988AE5C14C8.replay-    replays/D7FB197A451D69075A0C99A2F49A4053.replay-    replays/DCB3A6B94A9DBE46FDE5EAA9B012F6C8.replay-    replays/DE56B1B146687EA2E62E31A37A08C8EB.replay-    replays/EAE311E84BA35B590A6FDBA6DD4F2FEB.replay-    replays/EAE8DADA4BB2DC5422792C9B4A67392D.replay-    replays/F299F176491554B11E34AB91CA76B2CE.replay-    replays/F7B9E14545C7467B89A00895980FCD73.replay-    replays/F811C1D24888015E23B598AD8628C742.replay-    replays/FDC79DA84DD463D4BCCE6B892829AC88.replay-    stack.yaml+license-file: LICENSE.txt+license: MIT+maintainer: Taylor Fausak  source-repository head   type: git   location: https://github.com/tfausak/rattletrap +flag pedantic+  default: False+  manual: True++common library+  build-depends: base ^>=4.19.0.0 || ^>=4.20.0.0 || ^>=4.21.0.0+  default-extensions: NamedFieldPuns+  default-language: Haskell2010+  ghc-options:+    -Weverything+    -Wno-all-missed-specialisations+    -Wno-implicit-prelude+    -Wno-missed-specialisations+    -Wno-missing-deriving-strategies+    -Wno-missing-export-lists+    -Wno-missing-exported-signatures+    -Wno-missing-import-lists+    -Wno-missing-kind-signatures+    -Wno-missing-role-annotations+    -Wno-missing-safe-haskell-mode+    -Wno-prepositive-qualified-module+    -Wno-safe+    -Wno-unsafe++  if flag(pedantic)+    ghc-options: -Werror++common executable+  import: library+  build-depends: rattletrap+  ghc-options:+    -rtsopts+    -threaded+ library-  hs-source-dirs:-      library-  default-extensions: Strict-  ghc-options: -Wall+  import: library+  autogen-modules: Paths_rattletrap   build-depends:-      aeson >=1.2.3 && <1.3-    , base >=4.10.1 && <4.11-    , bimap >=0.3.3 && <0.4-    , binary >=0.8.5 && <0.9-    , binary-bits >=0.5 && <0.6-    , bytestring >=0.10.8 && <0.11-    , containers >=0.5.10 && <0.6-    , data-binary-ieee754 >=0.4.4 && <0.5-    , http-client >=0.5.7 && <0.6-    , http-client-tls >=0.3.5 && <0.4-    , template-haskell >=2.12.0 && <2.13-    , text >=1.2.2 && <1.3-    , vector >=0.12.0 && <0.13+    aeson ^>=2.1.2.1 || ^>=2.2.2.0,+    aeson-pretty ^>=0.8.10,+    array ^>=0.5.5.0,+    bytestring ^>=0.11.4.0 || ^>=0.12.0.2,+    containers ^>=0.6.7 || ^>=0.7,+    filepath ^>=1.4.100.1 || ^>=1.5.2.0,+    http-client ^>=0.7.17,+    http-client-tls ^>=0.3.6.3,+    text ^>=2.0.2 || ^>=2.1,++  exposed-modules: Paths_rattletrap+  -- cabal-gild: discover src/lib   exposed-modules:-      Rattletrap-      Rattletrap.ActorMap-      Rattletrap.Attribute-      Rattletrap.Attribute.AppliedDamage-      Rattletrap.Attribute.Boolean-      Rattletrap.Attribute.Byte-      Rattletrap.Attribute.CamSettings-      Rattletrap.Attribute.ClubColors-      Rattletrap.Attribute.DamageState-      Rattletrap.Attribute.Demolish-      Rattletrap.Attribute.Enum-      Rattletrap.Attribute.Explosion-      Rattletrap.Attribute.ExtendedExplosion-      Rattletrap.Attribute.FlaggedInt-      Rattletrap.Attribute.Float-      Rattletrap.Attribute.GameMode-      Rattletrap.Attribute.Int-      Rattletrap.Attribute.Loadout-      Rattletrap.Attribute.LoadoutOnline-      Rattletrap.Attribute.Loadouts-      Rattletrap.Attribute.LoadoutsOnline-      Rattletrap.Attribute.Location-      Rattletrap.Attribute.MusicStinger-      Rattletrap.Attribute.PartyLeader-      Rattletrap.Attribute.Pickup-      Rattletrap.Attribute.PrivateMatchSettings-      Rattletrap.Attribute.Product-      Rattletrap.Attribute.QWord-      Rattletrap.Attribute.Reservation-      Rattletrap.Attribute.RigidBodyState-      Rattletrap.Attribute.String-      Rattletrap.Attribute.TeamPaint-      Rattletrap.Attribute.UniqueId-      Rattletrap.Attribute.WeldedInfo-      Rattletrap.AttributeMapping-      Rattletrap.AttributeType-      Rattletrap.AttributeValue-      Rattletrap.Cache-      Rattletrap.ClassAttributeMap-      Rattletrap.ClassMapping-      Rattletrap.Content-      Rattletrap.Crc-      Rattletrap.Data-      Rattletrap.Frame-      Rattletrap.Header-      Rattletrap.Helper-      Rattletrap.Initialization-      Rattletrap.Json-      Rattletrap.KeyFrame-      Rattletrap.Main-      Rattletrap.Mark-      Rattletrap.Message-      Rattletrap.Primitive-      Rattletrap.Primitive.CompressedWord-      Rattletrap.Primitive.CompressedWordVector-      Rattletrap.Primitive.Dictionary-      Rattletrap.Primitive.Float32-      Rattletrap.Primitive.Int32-      Rattletrap.Primitive.Int8-      Rattletrap.Primitive.Int8Vector-      Rattletrap.Primitive.List-      Rattletrap.Primitive.Section-      Rattletrap.Primitive.Text-      Rattletrap.Primitive.Vector-      Rattletrap.Primitive.Word32-      Rattletrap.Primitive.Word64-      Rattletrap.Primitive.Word8-      Rattletrap.Property-      Rattletrap.PropertyValue-      Rattletrap.RemoteId-      Rattletrap.Replay-      Rattletrap.Replication-      Rattletrap.Replication.Destroyed-      Rattletrap.Replication.Spawned-      Rattletrap.Replication.Updated-      Rattletrap.ReplicationValue-      Rattletrap.Utility-      Rattletrap.Version-  other-modules:-      Paths_rattletrap-  default-language: Haskell2010+    Rattletrap+    Rattletrap.BitBuilder+    Rattletrap.BitGet+    Rattletrap.BitPut+    Rattletrap.BitString+    Rattletrap.ByteGet+    Rattletrap.BytePut+    Rattletrap.Console.Config+    Rattletrap.Console.Flag+    Rattletrap.Console.Main+    Rattletrap.Console.Mode+    Rattletrap.Console.Option+    Rattletrap.Data+    Rattletrap.Exception.CrcMismatch+    Rattletrap.Exception.Empty+    Rattletrap.Exception.Fail+    Rattletrap.Exception.InvalidComponent+    Rattletrap.Exception.InvalidJson+    Rattletrap.Exception.MissingAttributeLimit+    Rattletrap.Exception.MissingAttributeName+    Rattletrap.Exception.MissingClassName+    Rattletrap.Exception.MissingObjectName+    Rattletrap.Exception.MissingProductName+    Rattletrap.Exception.NotEnoughInput+    Rattletrap.Exception.UnknownActor+    Rattletrap.Exception.UnknownAttribute+    Rattletrap.Exception.UnknownName+    Rattletrap.Exception.UnknownProduct+    Rattletrap.Exception.UnknownProperty+    Rattletrap.Exception.UnknownSystemId+    Rattletrap.Get+    Rattletrap.Schema+    Rattletrap.Type.Attribute+    Rattletrap.Type.Attribute.AppliedDamage+    Rattletrap.Type.Attribute.Boolean+    Rattletrap.Type.Attribute.Boost+    Rattletrap.Type.Attribute.Byte+    Rattletrap.Type.Attribute.CamSettings+    Rattletrap.Type.Attribute.ClubColors+    Rattletrap.Type.Attribute.CustomDemolish+    Rattletrap.Type.Attribute.CustomDemolishExtended+    Rattletrap.Type.Attribute.DamageState+    Rattletrap.Type.Attribute.Demolish+    Rattletrap.Type.Attribute.Enum+    Rattletrap.Type.Attribute.Explosion+    Rattletrap.Type.Attribute.ExtendedExplosion+    Rattletrap.Type.Attribute.FlaggedByte+    Rattletrap.Type.Attribute.FlaggedInt+    Rattletrap.Type.Attribute.Float+    Rattletrap.Type.Attribute.GameMode+    Rattletrap.Type.Attribute.GameServer+    Rattletrap.Type.Attribute.Int+    Rattletrap.Type.Attribute.Int64+    Rattletrap.Type.Attribute.Loadout+    Rattletrap.Type.Attribute.LoadoutOnline+    Rattletrap.Type.Attribute.Loadouts+    Rattletrap.Type.Attribute.LoadoutsOnline+    Rattletrap.Type.Attribute.Location+    Rattletrap.Type.Attribute.MusicStinger+    Rattletrap.Type.Attribute.PartyLeader+    Rattletrap.Type.Attribute.Pickup+    Rattletrap.Type.Attribute.PickupInfo+    Rattletrap.Type.Attribute.PickupNew+    Rattletrap.Type.Attribute.PlayerHistoryKey+    Rattletrap.Type.Attribute.PrivateMatchSettings+    Rattletrap.Type.Attribute.Product+    Rattletrap.Type.Attribute.ProductValue+    Rattletrap.Type.Attribute.QWord+    Rattletrap.Type.Attribute.RepStatTitle+    Rattletrap.Type.Attribute.Reservation+    Rattletrap.Type.Attribute.RigidBodyState+    Rattletrap.Type.Attribute.Rotation+    Rattletrap.Type.Attribute.StatEvent+    Rattletrap.Type.Attribute.String+    Rattletrap.Type.Attribute.TeamPaint+    Rattletrap.Type.Attribute.Title+    Rattletrap.Type.Attribute.UniqueId+    Rattletrap.Type.Attribute.WeldedInfo+    Rattletrap.Type.AttributeMapping+    Rattletrap.Type.AttributeType+    Rattletrap.Type.AttributeValue+    Rattletrap.Type.Cache+    Rattletrap.Type.ClassAttributeMap+    Rattletrap.Type.ClassMapping+    Rattletrap.Type.CompressedWord+    Rattletrap.Type.CompressedWordVector+    Rattletrap.Type.Content+    Rattletrap.Type.Dictionary+    Rattletrap.Type.F32+    Rattletrap.Type.Frame+    Rattletrap.Type.Header+    Rattletrap.Type.I32+    Rattletrap.Type.I64+    Rattletrap.Type.I8+    Rattletrap.Type.Initialization+    Rattletrap.Type.Int8Vector+    Rattletrap.Type.Keyframe+    Rattletrap.Type.List+    Rattletrap.Type.Mark+    Rattletrap.Type.Message+    Rattletrap.Type.ObjectTarget+    Rattletrap.Type.Property+    Rattletrap.Type.Property.Array+    Rattletrap.Type.Property.Bool+    Rattletrap.Type.Property.Byte+    Rattletrap.Type.Property.Float+    Rattletrap.Type.Property.Int+    Rattletrap.Type.Property.Name+    Rattletrap.Type.Property.QWord+    Rattletrap.Type.Property.Str+    Rattletrap.Type.Property.Struct+    Rattletrap.Type.PropertyValue+    Rattletrap.Type.Quaternion+    Rattletrap.Type.RemoteId+    Rattletrap.Type.RemoteId.Epic+    Rattletrap.Type.RemoteId.PlayStation+    Rattletrap.Type.RemoteId.PsyNet+    Rattletrap.Type.RemoteId.QQ+    Rattletrap.Type.RemoteId.Splitscreen+    Rattletrap.Type.RemoteId.Steam+    Rattletrap.Type.RemoteId.Switch+    Rattletrap.Type.RemoteId.Xbox+    Rattletrap.Type.Replay+    Rattletrap.Type.Replication+    Rattletrap.Type.Replication.Destroyed+    Rattletrap.Type.Replication.Spawned+    Rattletrap.Type.Replication.Updated+    Rattletrap.Type.ReplicationValue+    Rattletrap.Type.Rotation+    Rattletrap.Type.Section+    Rattletrap.Type.Str+    Rattletrap.Type.U32+    Rattletrap.Type.U64+    Rattletrap.Type.U8+    Rattletrap.Type.Vector+    Rattletrap.Type.Version+    Rattletrap.Utility.Bytes+    Rattletrap.Utility.Crc+    Rattletrap.Utility.Helper+    Rattletrap.Utility.Json+    Rattletrap.Utility.Monad+    Rattletrap.Version +  hs-source-dirs: src/lib+ executable rattletrap+  import: executable+  hs-source-dirs: src/exe   main-is: Main.hs-  hs-source-dirs:-      executables-  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N++test-suite rattletrap-test-suite+  import: executable   build-depends:-      aeson >=1.2.3 && <1.3-    , base >=4.10.1 && <4.11-    , bimap >=0.3.3 && <0.4-    , binary >=0.8.5 && <0.9-    , binary-bits >=0.5 && <0.6-    , bytestring >=0.10.8 && <0.11-    , containers >=0.5.10 && <0.6-    , data-binary-ieee754 >=0.4.4 && <0.5-    , http-client >=0.5.7 && <0.6-    , http-client-tls >=0.3.5 && <0.4-    , rattletrap-    , template-haskell >=2.12.0 && <2.13-    , text >=1.2.2 && <1.3-    , vector >=0.12.0 && <0.13-  other-modules:-      Paths_rattletrap-  default-language: Haskell2010+    bytestring,+    filepath, -test-suite test-  type: exitcode-stdio-1.0+  hs-source-dirs: src/test   main-is: Main.hs-  hs-source-dirs:-      tests-  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N-  build-depends:-      aeson >=1.2.3 && <1.3-    , base >=4.10.1 && <4.11-    , bimap >=0.3.3 && <0.4-    , binary >=0.8.5 && <0.9-    , binary-bits >=0.5 && <0.6-    , bytestring >=0.10.8 && <0.11-    , containers >=0.5.10 && <0.6-    , data-binary-ieee754 >=0.4.4 && <0.5-    , filepath >=1.4.1 && <1.5-    , hspec >=2.4.4 && <2.5-    , http-client >=0.5.7 && <0.6-    , http-client-tls >=0.3.5 && <0.4-    , rattletrap-    , template-haskell >=2.12.0 && <2.13-    , temporary >=1.2.1 && <1.3-    , text >=1.2.2 && <1.3-    , vector >=0.12.0 && <0.13-  other-modules:-      Paths_rattletrap-  default-language: Haskell2010+  type: exitcode-stdio-1.0
− replays/00080014003600090000036E0F65CCEB.replay

binary file changed (94347 → absent bytes)

− replays/07E925B1423653D44CB8B4B2524792C1.replay

binary file changed (30063 → absent bytes)

− replays/0AD21FEE4A512906BEB6C98136AAF49A.replay

binary file changed (27949 → absent bytes)

− replays/1205D96C4D819800927791820096CD49.replay

binary file changed (461470 → absent bytes)

− replays/160CA83E41083BFD8E6315B4BFCA0561.replay

binary file changed (565359 → absent bytes)

− replays/16D580EF479483E015207C901776F9FB.replay

binary file changed (30691 → absent bytes)

− replays/18D6738D415B70B5BE4C299588D3C141.replay

binary file changed (21996 → absent bytes)

− replays/1A126AC24CAA0DB0E98835BD960B8AF8.replay

binary file changed (98814 → absent bytes)

− replays/1AE415514DFC65DCBF8B8391AD35488D.replay

binary file changed (25926 → absent bytes)

− replays/1BC2D01444ACE577D01E988EADD4DFD0.replay

binary file changed (22739 → absent bytes)

− replays/1D1DE97D4941C86E43FE0093563DB621.replay

binary file changed (59943 → absent bytes)

− replays/1EF90FCC4F719F606A5327B3CDD782A4.replay

binary file changed (737542 → absent bytes)

− replays/1F3798E540B0C37A946561ABBB3037F9.replay

binary file changed (27316 → absent bytes)

− replays/211466D04B983F5A33CC2FA1D5928672.replay

binary file changed (38258 → absent bytes)

− replays/22660E3649FC7971E5653692473D4318.replay

binary file changed (1259407 → absent bytes)

− replays/22BACD794ABE7B92E50E9CBDBD9C59CE.replay

binary file changed (315922 → absent bytes)

− replays/27B6A7B64553F0F685874584F96BAB1B.replay

binary file changed (25332 → absent bytes)

− replays/29F582C34A65EB34D358A784CBE3C189.replay

binary file changed (21607 → absent bytes)

− replays/2CFE577044E651D3FA9DBF83ECA8BCC3.replay

binary file changed (1060102 → absent bytes)

− replays/338173964F9F71EBDD31058A1936CBB4.replay

binary file changed (1556372 → absent bytes)

− replays/372DBFCA4BDB340E4357B6BD43032802.replay

binary file changed (28065 → absent bytes)

− replays/387F059C47C09E253C875CA990EFD9F2.replay

binary file changed (737279 → absent bytes)

− replays/3EA147DD485B8DD24810689A7A989E44.replay

binary file changed (596690 → absent bytes)

− replays/4126861E477F4A03DE2A4080374D7908.replay

binary file changed (26284 → absent bytes)

− replays/42F0D8DA4FC89AE7B80FCAB7F637A8EA.replay

binary file changed (116440 → absent bytes)

− replays/504ED825482186E771FAA9B642CE5CE4.replay

binary file changed (145094 → absent bytes)

− replays/520E1BFF468CF6C3C48D1EA85D9C7909.replay

binary file changed (68945 → absent bytes)

− replays/52AA67F94090C19D33C5009E54D31FE4.replay

binary file changed (570657 → absent bytes)

− replays/540DA764423C8FB24EB9D486D982F16F.replay

binary file changed (34674 → absent bytes)

− replays/551CA4D44FF2B86015DE44A6B5790D4C.replay

binary file changed (754887 → absent bytes)

− replays/6320E51C49066A7C210A2993C2201D5F.replay

binary file changed (391424 → absent bytes)

− replays/6688EEE34BFEB3EC3A9E3283098CC712.replay

binary file changed (1026 → absent bytes)

− replays/6B0D1614417085A7AAD82EAA30D8DABE.replay

binary file changed (1294929 → absent bytes)

− replays/6D1B06D844A5BB91B81FD4B5B28F08BA.replay

binary file changed (91217 → absent bytes)

− replays/6F7CFCD24638F8A6567AB3A8B9958A90.replay

binary file changed (29715 → absent bytes)

− replays/7109EB9846D303E54B7ACBA792036213.replay

binary file changed (65002 → absent bytes)

− replays/7BF6073F4614CE0A438994B9A260DA6A.replay

binary file changed (22841 → absent bytes)

− replays/89CBA30E46FA5385BDD35DA4285D4D2E.replay

binary file changed (212429 → absent bytes)

− replays/8AE551FF406D7B82ED853B8C7BFF8CDA.replay

binary file changed (28946 → absent bytes)

− replays/9704208245D7DD851F2FB2BC7DFD9AC3.replay

binary file changed (36307 → absent bytes)

− replays/98E58A904D713F2DE202358E8573265D.replay

binary file changed (34047 → absent bytes)

− replays/A128B3AB45D5A18E3EF9CF93C9576BCE.replay

binary file changed (743537 → absent bytes)

− replays/A52F804845573D8DA65E97BF59026A43.replay

binary file changed (73064 → absent bytes)

− replays/A558B1B44124D6E021640884E8EEC2A7.replay

binary file changed (266615 → absent bytes)

− replays/A6711CE74272B2E663DCC9A200A218E3.replay

binary file changed (221897 → absent bytes)

− replays/A7F001A1417A19BFA8C90990D8F7C2FF.replay

binary file changed (396148 → absent bytes)

− replays/AFB1F46A49737E36928E1EABC6F5B7AD.replay

binary file changed (32310 → absent bytes)

− replays/B9F9B87D4A9D0A3D25D4EC91C0401DE2.replay

binary file changed (25888 → absent bytes)

− replays/C14F7E0E4D9B5E6BE9AD5D8ED56B174C.replay

binary file changed (25904 → absent bytes)

− replays/C8372B1345B1803DEF039F815DBD802D.replay

binary file changed (23625 → absent bytes)

− replays/CC4CA70D4F7A67EBAD0ED9B9923106F7.replay

binary file changed (30103 → absent bytes)

− replays/D0449F5F4AA775B86FFA7DA2B5A3204E.replay

binary file changed (640433 → absent bytes)

− replays/D428F81646A98C25902CE988AE5C14C8.replay

binary file changed (476589 → absent bytes)

− replays/D7FB197A451D69075A0C99A2F49A4053.replay

binary file changed (29095 → absent bytes)

− replays/DCB3A6B94A9DBE46FDE5EAA9B012F6C8.replay

binary file changed (1055071 → absent bytes)

− replays/DE56B1B146687EA2E62E31A37A08C8EB.replay

binary file changed (368882 → absent bytes)

− replays/EAE311E84BA35B590A6FDBA6DD4F2FEB.replay

binary file changed (30151 → absent bytes)

− replays/EAE8DADA4BB2DC5422792C9B4A67392D.replay

binary file changed (25462 → absent bytes)

− replays/F299F176491554B11E34AB91CA76B2CE.replay

binary file changed (22732 → absent bytes)

− replays/F7B9E14545C7467B89A00895980FCD73.replay

binary file changed (25580 → absent bytes)

− replays/F811C1D24888015E23B598AD8628C742.replay

binary file changed (605 → absent bytes)

− replays/FDC79DA84DD463D4BCCE6B892829AC88.replay

binary file changed (783619 → absent bytes)

+ src/exe/Main.hs view
@@ -0,0 +1,4 @@+import qualified Rattletrap++main :: IO ()+main = Rattletrap.main
+ src/lib/Rattletrap.hs view
@@ -0,0 +1,12 @@+module Rattletrap+  ( Rattletrap.Console.Main.main,+    Rattletrap.Console.Main.rattletrap,+    Rattletrap.Utility.Helper.decodeReplayFile,+    Rattletrap.Utility.Helper.encodeReplayJson,+    Rattletrap.Utility.Helper.decodeReplayJson,+    Rattletrap.Utility.Helper.encodeReplayFile,+  )+where++import qualified Rattletrap.Console.Main+import qualified Rattletrap.Utility.Helper
+ src/lib/Rattletrap/BitBuilder.hs view
@@ -0,0 +1,33 @@+{- hlint ignore "Avoid restricted extensions" -}+{-# LANGUAGE BangPatterns #-}++module Rattletrap.BitBuilder where++import qualified Data.Bits as Bits+import qualified Data.ByteString.Builder as Builder+import qualified Data.Word as Word++data BitBuilder = BitBuilder+  { buffer :: Word.Word8,+    builder :: Builder.Builder,+    offset :: Int+  }++empty :: BitBuilder+empty = BitBuilder {buffer = 0x00, builder = mempty, offset = 0}++push :: Bool -> BitBuilder -> BitBuilder+push b x =+  let !newBuffer = if b then Bits.setBit (buffer x) (offset x) else buffer x+   in if offset x == 7+        then+          BitBuilder+            { buffer = 0x00,+              builder = builder x <> Builder.word8 newBuffer,+              offset = 0+            }+        else x {buffer = newBuffer, offset = offset x + 1}++toBuilder :: BitBuilder -> Builder.Builder+toBuilder x =+  if offset x == 0 then builder x else builder x <> Builder.word8 (buffer x)
+ src/lib/Rattletrap/BitGet.hs view
@@ -0,0 +1,52 @@+module Rattletrap.BitGet where++import qualified Control.Exception as Exception+import qualified Control.Monad as Monad+import qualified Data.Bits as Bits+import qualified Data.ByteString as ByteString+import qualified Data.Functor.Identity as Identity+import qualified Rattletrap.BitString as BitString+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.Exception.NotEnoughInput as NotEnoughInput+import qualified Rattletrap.Get as Get++type BitGet = Get.Get BitString.BitString Identity.Identity++toByteGet :: BitGet a -> ByteGet.ByteGet a+toByteGet g = do+  s1 <- Get.get+  case Identity.runIdentity . Get.run g $ BitString.fromByteString s1 of+    Left (ls, e) -> Get.labels ls $ ByteGet.throw e+    Right (s2, x) -> do+      Get.put $ BitString.byteString s2+      pure x++fromByteGet :: ByteGet.ByteGet a -> Int -> BitGet a+fromByteGet f n = do+  x <- byteString n+  Get.embed f x++bits :: (Bits.Bits a) => Int -> BitGet a+bits n = do+  let f :: (Bits.Bits a) => Bool -> a -> a+      f bit x = let y = Bits.shiftL x 1 in if bit then Bits.setBit y 0 else y+  xs <- Monad.replicateM n bool+  pure $ foldr f Bits.zeroBits xs++bool :: BitGet Bool+bool = do+  s1 <- Get.get+  case BitString.pop s1 of+    Nothing -> throw NotEnoughInput.NotEnoughInput+    Just (x, s2) -> do+      Get.put s2+      pure x++byteString :: Int -> BitGet ByteString.ByteString+byteString n = fmap ByteString.pack . Monad.replicateM n $ bits 8++throw :: (Exception.Exception e) => e -> BitGet a+throw = Get.throw++label :: String -> BitGet a -> BitGet a+label = Get.label
+ src/lib/Rattletrap/BitPut.hs view
@@ -0,0 +1,32 @@+module Rattletrap.BitPut where++import qualified Data.Bits as Bits+import qualified Data.ByteString as ByteString+import qualified Rattletrap.BitBuilder as BitBuilder+import qualified Rattletrap.BytePut as BytePut++newtype BitPut = BitPut (BitBuilder.BitBuilder -> BitBuilder.BitBuilder)++instance Semigroup BitPut where+  f1 <> f2 = BitPut $ run f2 . run f1++instance Monoid BitPut where+  mempty = BitPut id++run :: BitPut -> BitBuilder.BitBuilder -> BitBuilder.BitBuilder+run (BitPut f) = f++toBytePut :: BitPut -> BytePut.BytePut+toBytePut b = BitBuilder.toBuilder $ run b BitBuilder.empty++fromBytePut :: BytePut.BytePut -> BitPut+fromBytePut = byteString . BytePut.toByteString++bits :: (Bits.Bits a) => Int -> a -> BitPut+bits n x = foldMap (bool . Bits.testBit x) [0 .. n - 1]++bool :: Bool -> BitPut+bool = BitPut . BitBuilder.push++byteString :: ByteString.ByteString -> BitPut+byteString = foldMap (bits 8) . ByteString.unpack
+ src/lib/Rattletrap/BitString.hs view
@@ -0,0 +1,23 @@+module Rattletrap.BitString where++import qualified Data.Bits as Bits+import qualified Data.ByteString as ByteString++data BitString = BitString+  { byteString :: ByteString.ByteString,+    offset :: Int+  }+  deriving (Eq, Show)++fromByteString :: ByteString.ByteString -> BitString+fromByteString byteString = BitString {byteString, offset = 0}++pop :: BitString -> Maybe (Bool, BitString)+pop old = do+  (word, byteString) <- ByteString.uncons $ byteString old+  let bit = Bits.testBit word $ offset old+      new =+        if offset old == 7+          then fromByteString byteString+          else old {offset = offset old + 1}+  pure (bit, new)
+ src/lib/Rattletrap/ByteGet.hs view
@@ -0,0 +1,82 @@+module Rattletrap.ByteGet where++import qualified Control.Exception as Exception+import qualified Data.Bits as Bits+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Functor.Identity as Identity+import qualified Data.Int as Int+import qualified Data.Word as Word+import qualified GHC.Float as Float+import qualified Rattletrap.Exception.NotEnoughInput as NotEnoughInput+import qualified Rattletrap.Get as Get++type ByteGet = Get.Get ByteString.ByteString Identity.Identity++run ::+  ByteGet a ->+  ByteString.ByteString ->+  Either ([String], Exception.SomeException) a+run g = fmap snd . Identity.runIdentity . Get.run g++byteString :: Int -> ByteGet ByteString.ByteString+byteString n = do+  s1 <- Get.get+  let (x, s2) = ByteString.splitAt n s1+  if ByteString.length x == n+    then do+      Get.put s2+      pure x+    else throw NotEnoughInput.NotEnoughInput++float :: ByteGet Float+float = fmap Float.castWord32ToFloat word32++int8 :: ByteGet Int.Int8+int8 = fmap fromIntegral word8++int32 :: ByteGet Int.Int32+int32 = fmap fromIntegral word32++int64 :: ByteGet Int.Int64+int64 = fmap fromIntegral word64++remaining :: ByteGet LazyByteString.ByteString+remaining = do+  x <- Get.get+  Get.put ByteString.empty+  pure $ LazyByteString.fromStrict x++word8 :: ByteGet Word.Word8+word8 = fmap ByteString.head $ byteString 1++word32 :: ByteGet Word.Word32+word32 = do+  x <- byteString 4+  pure $+    Bits.shiftL (fromIntegral $ ByteString.index x 0) 0+      + Bits.shiftL (fromIntegral $ ByteString.index x 1) 8+      + Bits.shiftL (fromIntegral $ ByteString.index x 2) 16+      + Bits.shiftL (fromIntegral $ ByteString.index x 3) 24++word64 :: ByteGet Word.Word64+word64 = do+  x <- byteString 8+  pure $+    Bits.shiftL (fromIntegral $ ByteString.index x 0) 0+      + Bits.shiftL (fromIntegral $ ByteString.index x 1) 8+      + Bits.shiftL (fromIntegral $ ByteString.index x 2) 16+      + Bits.shiftL (fromIntegral $ ByteString.index x 3) 24+      + Bits.shiftL (fromIntegral $ ByteString.index x 4) 32+      + Bits.shiftL (fromIntegral $ ByteString.index x 5) 40+      + Bits.shiftL (fromIntegral $ ByteString.index x 6) 48+      + Bits.shiftL (fromIntegral $ ByteString.index x 7) 56++throw :: (Exception.Exception e) => e -> ByteGet a+throw = Get.throw++embed :: ByteGet a -> ByteString.ByteString -> ByteGet a+embed = Get.embed++label :: String -> ByteGet a -> ByteGet a+label = Get.label
+ src/lib/Rattletrap/BytePut.hs view
@@ -0,0 +1,39 @@+module Rattletrap.BytePut where++import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Builder as Builder+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Int as Int+import qualified Data.Word as Word++type BytePut = Builder.Builder++toByteString :: BytePut -> ByteString.ByteString+toByteString = LazyByteString.toStrict . toLazyByteString++toLazyByteString :: BytePut -> LazyByteString.ByteString+toLazyByteString = Builder.toLazyByteString++byteString :: ByteString.ByteString -> BytePut+byteString = Builder.byteString++float :: Float -> BytePut+float = Builder.floatLE++int8 :: Int.Int8 -> BytePut+int8 = Builder.int8++int32 :: Int.Int32 -> BytePut+int32 = Builder.int32LE++int64 :: Int.Int64 -> BytePut+int64 = Builder.int64LE++word8 :: Word.Word8 -> BytePut+word8 = Builder.word8++word32 :: Word.Word32 -> BytePut+word32 = Builder.word32LE++word64 :: Word.Word64 -> BytePut+word64 = Builder.word64LE
+ src/lib/Rattletrap/Console/Config.hs view
@@ -0,0 +1,57 @@+module Rattletrap.Console.Config where++import qualified Rattletrap.Console.Flag as Flag+import qualified Rattletrap.Console.Mode as Mode+import qualified System.FilePath as FilePath++data Config = Config+  { compact :: Bool,+    fast :: Bool,+    help :: Bool,+    input :: Maybe String,+    mode :: Maybe Mode.Mode,+    output :: Maybe String,+    schema :: Bool,+    skipCrc :: Bool,+    version :: Bool+  }+  deriving (Eq, Show)++initial :: Config+initial =+  Config+    { compact = False,+      fast = False,+      help = False,+      input = Nothing,+      mode = Nothing,+      output = Nothing,+      schema = False,+      skipCrc = False,+      version = False+    }++applyFlag :: Config -> Flag.Flag -> Either String Config+applyFlag config flag = case flag of+  Flag.Compact -> Right config {compact = True}+  Flag.Fast -> Right config {fast = True}+  Flag.Help -> Right config {help = True}+  Flag.Input x -> Right config {input = Just x}+  Flag.Mode x -> do+    y <- Mode.fromString x+    Right config {mode = Just y}+  Flag.Output x -> Right config {output = Just x}+  Flag.Schema -> Right config {schema = True}+  Flag.SkipCrc -> Right config {skipCrc = True}+  Flag.Version -> Right config {version = True}++getMode :: Config -> Mode.Mode+getMode config =+  let i = fmap FilePath.takeExtension $ input config+      o = fmap FilePath.takeExtension $ output config+   in case (i, o) of+        (Just ".json", _) -> Mode.Encode+        (Just ".replay", _) -> Mode.Decode+        (_, Just ".json") -> Mode.Decode+        (_, Just ".replay") -> Mode.Encode+        _ -> Mode.Decode
+ src/lib/Rattletrap/Console/Flag.hs view
@@ -0,0 +1,13 @@+module Rattletrap.Console.Flag where++data Flag+  = Compact+  | Fast+  | Help+  | Input FilePath+  | Mode String+  | Output FilePath+  | Schema+  | SkipCrc+  | Version+  deriving (Eq, Show)
+ src/lib/Rattletrap/Console/Main.hs view
@@ -0,0 +1,329 @@+module Rattletrap.Console.Main where++import qualified Control.Exception as Exception+import qualified Control.Monad as Monad+import qualified Data.Bool as Bool+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Network.HTTP.Client as Client+import qualified Network.HTTP.Client.TLS as Client+import qualified Rattletrap.Console.Config as Config+import qualified Rattletrap.Console.Mode as Mode+import qualified Rattletrap.Console.Option as Option+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute as Attribute+import qualified Rattletrap.Type.Attribute.AppliedDamage as Attribute.AppliedDamage+import qualified Rattletrap.Type.Attribute.Boolean as Attribute.Boolean+import qualified Rattletrap.Type.Attribute.Boost as Attribute.Boost+import qualified Rattletrap.Type.Attribute.Byte as Attribute.Byte+import qualified Rattletrap.Type.Attribute.CamSettings as Attribute.CamSettings+import qualified Rattletrap.Type.Attribute.ClubColors as Attribute.ClubColors+import qualified Rattletrap.Type.Attribute.CustomDemolish as Attribute.CustomDemolish+import qualified Rattletrap.Type.Attribute.CustomDemolishExtended as Attribute.CustomDemolishExtended+import qualified Rattletrap.Type.Attribute.DamageState as Attribute.DamageState+import qualified Rattletrap.Type.Attribute.Demolish as Attribute.Demolish+import qualified Rattletrap.Type.Attribute.Enum as Attribute.Enum+import qualified Rattletrap.Type.Attribute.Explosion as Attribute.Explosion+import qualified Rattletrap.Type.Attribute.ExtendedExplosion as Attribute.ExtendedExplosion+import qualified Rattletrap.Type.Attribute.FlaggedByte as Attribute.FlaggedByte+import qualified Rattletrap.Type.Attribute.FlaggedInt as Attribute.FlaggedInt+import qualified Rattletrap.Type.Attribute.Float as Attribute.Float+import qualified Rattletrap.Type.Attribute.GameMode as Attribute.GameMode+import qualified Rattletrap.Type.Attribute.GameServer as Attribute.GameServer+import qualified Rattletrap.Type.Attribute.Int as Attribute.Int+import qualified Rattletrap.Type.Attribute.Int64 as Attribute.Int64+import qualified Rattletrap.Type.Attribute.Loadout as Attribute.Loadout+import qualified Rattletrap.Type.Attribute.LoadoutOnline as Attribute.LoadoutOnline+import qualified Rattletrap.Type.Attribute.Loadouts as Attribute.Loadouts+import qualified Rattletrap.Type.Attribute.LoadoutsOnline as Attribute.LoadoutsOnline+import qualified Rattletrap.Type.Attribute.Location as Attribute.Location+import qualified Rattletrap.Type.Attribute.MusicStinger as Attribute.MusicStinger+import qualified Rattletrap.Type.Attribute.PartyLeader as Attribute.PartyLeader+import qualified Rattletrap.Type.Attribute.Pickup as Attribute.Pickup+import qualified Rattletrap.Type.Attribute.PickupInfo as Attribute.PickupInfo+import qualified Rattletrap.Type.Attribute.PickupNew as Attribute.PickupNew+import qualified Rattletrap.Type.Attribute.PlayerHistoryKey as Attribute.PlayerHistoryKey+import qualified Rattletrap.Type.Attribute.PrivateMatchSettings as Attribute.PrivateMatchSettings+import qualified Rattletrap.Type.Attribute.Product as Attribute.Product+import qualified Rattletrap.Type.Attribute.ProductValue as Attribute.ProductValue+import qualified Rattletrap.Type.Attribute.QWord as Attribute.QWord+import qualified Rattletrap.Type.Attribute.RepStatTitle as Attribute.RepStatTitle+import qualified Rattletrap.Type.Attribute.Reservation as Attribute.Reservation+import qualified Rattletrap.Type.Attribute.RigidBodyState as Attribute.RigidBodyState+import qualified Rattletrap.Type.Attribute.Rotation as Attribute.Rotation+import qualified Rattletrap.Type.Attribute.StatEvent as Attribute.StatEvent+import qualified Rattletrap.Type.Attribute.String as Attribute.String+import qualified Rattletrap.Type.Attribute.TeamPaint as Attribute.TeamPaint+import qualified Rattletrap.Type.Attribute.Title as Attribute.Title+import qualified Rattletrap.Type.Attribute.UniqueId as Attribute.UniqueId+import qualified Rattletrap.Type.Attribute.WeldedInfo as Attribute.WeldedInfo+import qualified Rattletrap.Type.AttributeMapping as AttributeMapping+import qualified Rattletrap.Type.AttributeValue as AttributeValue+import qualified Rattletrap.Type.Cache as Cache+import qualified Rattletrap.Type.ClassMapping as ClassMapping+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.CompressedWordVector as CompressedWordVector+import qualified Rattletrap.Type.Content as Content+import qualified Rattletrap.Type.Dictionary as Dictionary+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Type.Frame as Frame+import qualified Rattletrap.Type.Header as Header+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.I64 as I64+import qualified Rattletrap.Type.I8 as I8+import qualified Rattletrap.Type.Initialization as Initialization+import qualified Rattletrap.Type.Int8Vector as Int8Vector+import qualified Rattletrap.Type.Keyframe as Keyframe+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Mark as Mark+import qualified Rattletrap.Type.Message as Message+import qualified Rattletrap.Type.ObjectTarget as ObjectTarget+import qualified Rattletrap.Type.Property as Property+import qualified Rattletrap.Type.Property.Array as Property.Array+import qualified Rattletrap.Type.Property.Byte as Property.Byte+import qualified Rattletrap.Type.Property.Struct as Property.Struct+import qualified Rattletrap.Type.PropertyValue as PropertyValue+import qualified Rattletrap.Type.Quaternion as Quaternion+import qualified Rattletrap.Type.RemoteId as RemoteId+import qualified Rattletrap.Type.RemoteId.PlayStation as RemoteId.PlayStation+import qualified Rattletrap.Type.RemoteId.PsyNet as RemoteId.PsyNet+import qualified Rattletrap.Type.RemoteId.Switch as RemoteId.Switch+import qualified Rattletrap.Type.Replay as Replay+import qualified Rattletrap.Type.Replication as Replication+import qualified Rattletrap.Type.Replication.Destroyed as Replication.Destroyed+import qualified Rattletrap.Type.Replication.Spawned as Replication.Spawned+import qualified Rattletrap.Type.Replication.Updated as Replication.Updated+import qualified Rattletrap.Type.ReplicationValue as ReplicationValue+import qualified Rattletrap.Type.Rotation as Rotation+import qualified Rattletrap.Type.Section as Section+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Utility.Helper as Rattletrap+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Version as Version+import qualified System.Console.GetOpt as Console+import qualified System.Environment as Environment+import qualified System.Exit as Exit+import qualified System.FilePath as FilePath+import qualified System.IO as IO++main :: IO ()+main = do+  name <- Environment.getProgName+  arguments <- Environment.getArgs+  rattletrap name arguments++rattletrap :: String -> [String] -> IO ()+rattletrap name arguments = do+  config <- getConfig arguments+  if Config.help config+    then helpMain name+    else+      if Config.version config+        then versionMain+        else+          if Config.schema config+            then schemaMain config+            else defaultMain name config++helpMain :: String -> IO ()+helpMain name = do+  putStr $+    Console.usageInfo+      (unwords [name, "version", Version.string])+      Option.all++versionMain :: IO ()+versionMain = do+  putStrLn Version.string++schemaMain :: Config.Config -> IO ()+schemaMain config = putOutput config $ encodeJson config schema++defaultMain :: String -> Config.Config -> IO ()+defaultMain name config = do+  input <- getInput name config+  let decode = getDecoder config+  replay <- case decode input of+    Left (ls, e) -> do+      IO.hPutStr IO.stderr $+        unlines+          [ "ERROR: " <> Exception.displayException e,+            "-- Context: " <> List.intercalate ", " ls,+            "-- You are using Rattletrap version " <> Version.string,+            "-- " <> show config,+            "-- Please report this problem at https://github.com/tfausak/rattletrap/issues/new"+          ]+      Exit.exitFailure+    Right x -> pure x+  let encode = getEncoder config+  putOutput config (encode replay)++schema :: Json.Value+schema =+  let contentSchema = Content.schema $ RList.schema Frame.schema+   in Json.object+        [ Json.pair "$schema" "http://json-schema.org/draft-07/schema",+          Json.pair "$id" Replay.schemaUrl,+          Json.pair "$ref" "#/definitions/replay",+          Json.pair "definitions" . Json.object $+            fmap+              (\s -> Json.pair (Text.unpack $ Schema.name s) $ Schema.json s)+              [ Attribute.schema,+                Attribute.AppliedDamage.schema,+                Attribute.Boolean.schema,+                Attribute.Boost.schema,+                Attribute.Byte.schema,+                Attribute.CamSettings.schema,+                Attribute.ClubColors.schema,+                Attribute.CustomDemolish.schema,+                Attribute.CustomDemolishExtended.schema,+                Attribute.DamageState.schema,+                Attribute.Demolish.schema,+                Attribute.Enum.schema,+                Attribute.Explosion.schema,+                Attribute.ExtendedExplosion.schema,+                Attribute.FlaggedByte.schema,+                Attribute.FlaggedInt.schema,+                Attribute.Float.schema,+                Attribute.GameMode.schema,+                Attribute.GameServer.schema,+                Attribute.Int.schema,+                Attribute.Int64.schema,+                Attribute.Loadout.schema,+                Attribute.LoadoutOnline.schema,+                Attribute.Loadouts.schema,+                Attribute.LoadoutsOnline.schema,+                Attribute.Location.schema,+                Attribute.MusicStinger.schema,+                Attribute.PartyLeader.schema,+                Attribute.Pickup.schema,+                Attribute.PickupInfo.schema,+                Attribute.PickupNew.schema,+                Attribute.PlayerHistoryKey.schema,+                Attribute.PrivateMatchSettings.schema,+                Attribute.Product.schema,+                Attribute.ProductValue.schema,+                Attribute.QWord.schema,+                Attribute.RepStatTitle.schema,+                Attribute.Reservation.schema,+                Attribute.RigidBodyState.schema,+                Attribute.Rotation.schema,+                Attribute.StatEvent.schema,+                Attribute.String.schema,+                Attribute.TeamPaint.schema,+                Attribute.Title.schema,+                Attribute.UniqueId.schema,+                Attribute.WeldedInfo.schema,+                AttributeMapping.schema,+                AttributeValue.schema,+                Cache.schema,+                ClassMapping.schema,+                CompressedWord.schema,+                CompressedWordVector.schema,+                contentSchema,+                Dictionary.elementSchema Property.schema,+                Dictionary.schema Property.schema,+                F32.schema,+                Frame.schema,+                Header.schema,+                I32.schema,+                I64.schema,+                I8.schema,+                Initialization.schema,+                Int8Vector.schema,+                Keyframe.schema,+                RList.schema Attribute.Product.schema,+                Mark.schema,+                Message.schema,+                ObjectTarget.schema,+                Property.schema,+                Property.Array.schema Property.schema,+                Property.Byte.schema,+                Property.Struct.schema Property.schema,+                PropertyValue.schema Property.schema,+                Quaternion.schema,+                RemoteId.schema,+                RemoteId.PlayStation.schema,+                RemoteId.PsyNet.schema,+                RemoteId.Switch.schema,+                Replay.schema (Section.schema Header.schema)+                  . Section.schema+                  $ contentSchema,+                Replication.Destroyed.schema,+                Replication.schema,+                Replication.Spawned.schema,+                Replication.Updated.schema,+                ReplicationValue.schema,+                Rotation.schema,+                Schema.boolean,+                Schema.integer,+                Schema.null,+                Schema.number,+                Schema.string,+                Section.schema contentSchema,+                Section.schema Header.schema,+                Str.schema,+                U32.schema,+                U64.schema,+                U8.schema,+                Vector.schema+              ]+        ]++getDecoder ::+  Config.Config ->+  ByteString.ByteString ->+  Either ([String], Exception.SomeException) Replay.Replay+getDecoder config = case Config.getMode config of+  Mode.Decode ->+    Rattletrap.decodeReplayFile (Config.fast config) (Config.skipCrc config)+  Mode.Encode -> Rattletrap.decodeReplayJson++getEncoder :: Config.Config -> Replay.Replay -> LazyByteString.ByteString+getEncoder config = case Config.getMode config of+  Mode.Decode -> encodeJson config+  Mode.Encode -> Rattletrap.encodeReplayFile $ Config.fast config++getInput :: String -> Config.Config -> IO ByteString.ByteString+getInput name config = case Config.input config of+  Nothing -> do+    isTerminalDevice <- IO.hIsTerminalDevice IO.stdin+    Monad.when isTerminalDevice . IO.hPutStr IO.stderr $+      unlines+        [ "-- You did not supply any input, so Rattletrap will read from STDIN.",+          "-- If that is unexpected, try running: "+            <> FilePath.combine "." name+            <> " --help"+        ]+    ByteString.getContents+  Just fileOrUrl -> case Client.parseUrlThrow fileOrUrl of+    Nothing -> ByteString.readFile fileOrUrl+    Just request -> do+      manager <- Client.newTlsManager+      response <- Client.httpLbs request manager+      pure (LazyByteString.toStrict (Client.responseBody response))++putOutput :: Config.Config -> LazyByteString.ByteString -> IO ()+putOutput =+  maybe LazyByteString.putStr LazyByteString.writeFile . Config.output++encodeJson :: (Json.ToJSON a) => Config.Config -> a -> LazyByteString.ByteString+encodeJson = Bool.bool Json.encodePretty Json.encode . Config.compact++getConfig :: [String] -> IO Config.Config+getConfig arguments = do+  let (flags, unexpectedArguments, unknownOptions, problems) =+        Console.getOpt' Console.Permute Option.all arguments+  Monad.forM_ unexpectedArguments $ \x ->+    IO.hPutStrLn IO.stderr $ "WARNING: unexpected argument `" <> x <> "'"+  Monad.forM_ unknownOptions $+    \x -> IO.hPutStrLn IO.stderr $ "WARNING: unknown option `" <> x <> "'"+  Monad.forM_ problems $ \x -> IO.hPutStr IO.stderr $ "ERROR: " <> x+  Monad.unless (null problems) Exit.exitFailure+  either fail pure $ Monad.foldM Config.applyFlag Config.initial flags
+ src/lib/Rattletrap/Console/Mode.hs view
@@ -0,0 +1,12 @@+module Rattletrap.Console.Mode where++data Mode+  = Decode+  | Encode+  deriving (Eq, Show)++fromString :: String -> Either String Mode+fromString string = case string of+  "decode" -> Right Decode+  "encode" -> Right Encode+  _ -> Left $ "invalid mode: " <> show string
+ src/lib/Rattletrap/Console/Option.hs view
@@ -0,0 +1,68 @@+module Rattletrap.Console.Option where++import qualified Rattletrap.Console.Flag as Flag+import qualified System.Console.GetOpt as Console++type Option = Console.OptDescr Flag.Flag++all :: [Option]+all = [compact, fast, help, input, mode, output, schema, skipCrc, version]++compact :: Option+compact =+  Console.Option+    ['c']+    ["compact"]+    (Console.NoArg Flag.Compact)+    "minify JSON output"++fast :: Option+fast =+  Console.Option+    ['f']+    ["fast"]+    (Console.NoArg Flag.Fast)+    "only encode or decode the header"++help :: Option+help = Console.Option ['h'] ["help"] (Console.NoArg Flag.Help) "show the help"++input :: Option+input =+  Console.Option+    ['i']+    ["input"]+    (Console.ReqArg Flag.Input "FILE|URL")+    "input file or URL"++mode :: Option+mode =+  Console.Option+    ['m']+    ["mode"]+    (Console.ReqArg Flag.Mode "MODE")+    "decode or encode"++output :: Option+output =+  Console.Option+    ['o']+    ["output"]+    (Console.ReqArg Flag.Output "FILE")+    "output file"++schema :: Option+schema =+  Console.Option [] ["schema"] (Console.NoArg Flag.Schema) "output the schema"++skipCrc :: Option+skipCrc =+  Console.Option [] ["skip-crc"] (Console.NoArg Flag.SkipCrc) "skip the CRC"++version :: Option+version =+  Console.Option+    ['v']+    ["version"]+    (Console.NoArg Flag.Version)+    "show the version"
+ src/lib/Rattletrap/Data.hs view
@@ -0,0 +1,469 @@+-- brittany --columns 120+module Rattletrap.Data where++import qualified Data.Bifunctor as Bifunctor+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Text as Text+import qualified Rattletrap.Type.AttributeType as AttributeType++parentClasses :: Map.Map Text.Text Text.Text+parentClasses =+  Map.fromList $+    fmap+      (Bifunctor.bimap Text.pack Text.pack)+      [ ("Engine.Actor", "Core.Object"),+        ("Engine.GameReplicationInfo", "Engine.ReplicationInfo"),+        ("Engine.Info", "Engine.Actor"),+        ("Engine.Pawn", "Engine.Actor"),+        ("Engine.PlayerReplicationInfo", "Engine.ReplicationInfo"),+        ("Engine.ReplicationInfo", "Engine.Info"),+        ("Engine.TeamInfo", "Engine.ReplicationInfo"),+        ("ProjectX.GRI_X", "Engine.GameReplicationInfo"),+        ("ProjectX.Pawn_X", "Engine.Pawn"),+        ("ProjectX.PRI_X", "Engine.PlayerReplicationInfo"),+        ("TAGame.Ball_God_TA", "TAGame.Ball_TA"),+        ("TAGame.Ball_Haunted_TA", "TAGame.Ball_TA"),+        ("TAGame.Ball_TA", "TAGame.RBActor_TA"),+        ("TAGame.CameraSettingsActor_TA", "Engine.ReplicationInfo"),+        ("TAGame.Car_Season_TA", "TAGame.PRI_TA"),+        ("TAGame.Car_TA", "TAGame.Vehicle_TA"),+        ("TAGame.CarComponent_Boost_TA", "TAGame.CarComponent_TA"),+        ("TAGame.CarComponent_Dodge_TA", "TAGame.CarComponent_TA"),+        ("TAGame.CarComponent_DoubleJump_TA", "TAGame.CarComponent_TA"),+        ("TAGame.CarComponent_FlipCar_TA", "TAGame.CarComponent_TA"),+        ("TAGame.CarComponent_Jump_TA", "TAGame.CarComponent_TA"),+        ("TAGame.CarComponent_TA", "Engine.ReplicationInfo"),+        ("TAGame.CrowdActor_TA", "Engine.ReplicationInfo"),+        ("TAGame.CrowdManager_TA", "Engine.ReplicationInfo"),+        ("TAGame.GameEvent_Football_TA", "TAGame.GameEvent_Soccar_TA"),+        ("TAGame.GameEvent_GodBall_TA", "TAGame.GameEvent_Soccar_TA"),+        ("TAGame.GameEvent_Season_TA", "TAGame.GameEvent_Soccar_TA"),+        ("TAGame.GameEvent_Soccar_TA", "TAGame.GameEvent_Team_TA"),+        ("TAGame.GameEvent_SoccarPrivate_TA", "TAGame.GameEvent_Soccar_TA"),+        ("TAGame.GameEvent_SoccarSplitscreen_TA", "TAGame.GameEvent_SoccarPrivate_TA"),+        ("TAGame.GameEvent_TA", "Engine.ReplicationInfo"),+        ("TAGame.GameEvent_Team_TA", "TAGame.GameEvent_TA"),+        ("TAGame.GRI_TA", "ProjectX.GRI_X"),+        ("TAGame.HauntedBallTrapTrigger_TA", "Engine.Actor"),+        ("TAGame.InMapScoreboard_TA", "Engine.Actor"),+        ("TAGame.PRI_TA", "ProjectX.PRI_X"),+        ("TAGame.RBActor_TA", "ProjectX.Pawn_X"),+        ("TAGame.SpecialPickup_BallCarSpring_TA", "TAGame.SpecialPickup_Spring_TA"),+        ("TAGame.SpecialPickup_BallFreeze_TA", "TAGame.SpecialPickup_Targeted_TA"),+        ("TAGame.SpecialPickup_BallGravity_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_BallLasso_TA", "TAGame.SpecialPickup_GrapplingHook_TA"),+        ("TAGame.SpecialPickup_BallVelcro_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_Batarang_TA", "TAGame.SpecialPickup_BallLasso_TA"),+        ("TAGame.SpecialPickup_BoostOverride_TA", "TAGame.SpecialPickup_Targeted_TA"),+        ("TAGame.SpecialPickup_Football_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_GrapplingHook_TA", "TAGame.SpecialPickup_Targeted_TA"),+        ("TAGame.SpecialPickup_HauntedBallBeam_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_HitForce_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_Rugby_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_Spring_TA", "TAGame.SpecialPickup_Targeted_TA"),+        ("TAGame.SpecialPickup_Swapper_TA", "TAGame.SpecialPickup_Targeted_TA"),+        ("TAGame.SpecialPickup_TA", "TAGame.CarComponent_TA"),+        ("TAGame.SpecialPickup_Targeted_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.SpecialPickup_Tornado_TA", "TAGame.SpecialPickup_TA"),+        ("TAGame.Team_Soccar_TA", "TAGame.Team_TA"),+        ("TAGame.Team_TA", "Engine.TeamInfo"),+        ("TAGame.Vehicle_TA", "TAGame.RBActor_TA"),+        ("TAGame.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_TA"),+        ("TAGame.VehiclePickup_TA", "Engine.ReplicationInfo")+      ]++classesWithLocation :: Set.Set Text.Text+classesWithLocation =+  Set.fromList $+    fmap+      Text.pack+      [ "Archetypes.Ball.Ball_BasketBall_Mutator",+        "Archetypes.Ball.Ball_Basketball",+        "Archetypes.Ball.Ball_BasketBall",+        "Archetypes.Ball.Ball_Breakout",+        "Archetypes.Ball.Ball_Default",+        "Archetypes.Ball.Ball_Puck",+        "Archetypes.Ball.Ball_Trajectory",+        "Archetypes.Ball.CubeBall",+        "Archetypes.Car.Car_Default",+        "Archetypes.GameEvent.GameEvent_Season:CarArchetype",+        "Archetypes.SpecialPickups.SpecialPickup_Rugby",+        "ProjectX.NetModeReplicator",+        "TAGame.Ball_Breakout_TA",+        "TAGame.Ball_God_TA",+        "TAGame.Ball_Haunted_TA",+        "TAGame.Ball_TA",+        "TAGame.CameraSettingsActor_TA",+        "TAGame.Cannon_TA",+        "TAGame.Car_Season_TA",+        "TAGame.Car_TA",+        "TAGame.CarComponent_Boost_TA",+        "TAGame.CarComponent_Dodge_TA",+        "TAGame.CarComponent_DoubleJump_TA",+        "TAGame.CarComponent_FlipCar_TA",+        "TAGame.CarComponent_Jump_TA",+        "TAGame.Default__CameraSettingsActor_TA",+        "TAGame.Default__PRI_TA",+        "TAGame.GameEvent_Football_TA",+        "TAGame.GameEvent_FTE_TA",+        "TAGame.GameEvent_GodBall_TA",+        "TAGame.GameEvent_Season_TA",+        "TAGame.GameEvent_Soccar_TA",+        "TAGame.GameEvent_SoccarPrivate_TA",+        "TAGame.GameEvent_SoccarSplitscreen_TA",+        "TAGame.GameEvent_Tutorial_TA",+        "TAGame.GRI_TA",+        "TAGame.MaxTimeWarningData_TA",+        "TAGame.PickupTimer_TA",+        "TAGame.PRI_TA",+        "TAGame.RumblePickups_TA",+        "TAGame.SpecialPickup_BallCarSpring_TA",+        "TAGame.SpecialPickup_BallFreeze_TA",+        "TAGame.SpecialPickup_BallGravity_TA",+        "TAGame.SpecialPickup_BallLasso_TA",+        "TAGame.SpecialPickup_BallVelcro_TA",+        "TAGame.SpecialPickup_Batarang_TA",+        "TAGame.SpecialPickup_BoostOverride_TA",+        "TAGame.SpecialPickup_Football_TA",+        "TAGame.SpecialPickup_GrapplingHook_TA",+        "TAGame.SpecialPickup_HauntedBallBeam_TA",+        "TAGame.SpecialPickup_HitForce_TA",+        "TAGame.SpecialPickup_Rugby_TA",+        "TAGame.SpecialPickup_Swapper_TA",+        "TAGame.SpecialPickup_Tornado_TA",+        "TAGame.Team_Soccar_TA",+        "TheWorld:PersistentLevel.BreakOutActor_Platform_TA",+        "TheWorld:PersistentLevel.CrowdActor_TA",+        "TheWorld:PersistentLevel.CrowdManager_TA",+        "TheWorld:PersistentLevel.InMapScoreboard_TA",+        "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"+      ]++classesWithRotation :: Set.Set Text.Text+classesWithRotation =+  Set.fromList $+    fmap+      Text.pack+      [ "Archetypes.Ball.Ball_BasketBall_Mutator",+        "Archetypes.Ball.Ball_Basketball",+        "Archetypes.Ball.Ball_BasketBall",+        "Archetypes.Ball.Ball_Breakout",+        "Archetypes.Ball.Ball_Default",+        "Archetypes.Ball.Ball_Puck",+        "Archetypes.Ball.Ball_Trajectory",+        "Archetypes.Ball.CubeBall",+        "Archetypes.Car.Car_Default",+        "Archetypes.GameEvent.GameEvent_Season:CarArchetype",+        "Archetypes.SpecialPickups.SpecialPickup_Rugby",+        "TAGame.Ball_Breakout_TA",+        "TAGame.Ball_God_TA",+        "TAGame.Ball_Haunted_TA",+        "TAGame.Ball_TA",+        "TAGame.Car_Season_TA",+        "TAGame.Car_TA"+      ]++objectClasses :: Map.Map Text.Text Text.Text+objectClasses =+  Map.fromList $+    fmap+      (Bifunctor.bimap Text.pack Text.pack)+      [ ("Archetypes.Ball.Ball_Anniversary", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_BasketBall_Mutator", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Basketball", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_BasketBall", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Beachball", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Breakout", "TAGame.Ball_Breakout_TA"),+        ("Archetypes.Ball.Ball_Default", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Football", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_God", "TAGame.Ball_God_TA"),+        ("Archetypes.Ball.Ball_Haunted", "TAGame.Ball_Haunted_TA"),+        ("Archetypes.Ball.Ball_Puck", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Training", "TAGame.Ball_TA"),+        ("Archetypes.Ball.Ball_Trajectory", "TAGame.Ball_TA"),+        ("Archetypes.Ball.CubeBall", "TAGame.Ball_TA"),+        ("Archetypes.Car.Car_Default", "TAGame.Car_TA"),+        ("Archetypes.Car.Car_PostGameLobby", "TAGame.Car_TA"),+        ("Archetypes.CarComponents.CarComponent_Boost", "TAGame.CarComponent_Boost_TA"),+        ("Archetypes.CarComponents.CarComponent_Dodge", "TAGame.CarComponent_Dodge_TA"),+        ("Archetypes.CarComponents.CarComponent_DoubleJump", "TAGame.CarComponent_DoubleJump_TA"),+        ("Archetypes.CarComponents.CarComponent_FlipCar", "TAGame.CarComponent_FlipCar_TA"),+        ("Archetypes.CarComponents.CarComponent_Jump", "TAGame.CarComponent_Jump_TA"),+        ("Archetypes.GameEvent.GameEvent_Basketball", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_BasketballPrivate", "TAGame.GameEvent_SoccarPrivate_TA"),+        ("Archetypes.GameEvent.GameEvent_BasketballSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA"),+        ("Archetypes.GameEvent.GameEvent_Breakout", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_FTE_Part1_Prime", "TAGame.GameEvent_FTE_TA"),+        ("Archetypes.GameEvent.GameEvent_Hockey", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_HockeyPrivate", "TAGame.GameEvent_SoccarPrivate_TA"),+        ("Archetypes.GameEvent.GameEvent_HockeySplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA"),+        ("Archetypes.GameEvent.GameEvent_Items", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_Season:CarArchetype", "TAGame.Car_TA"),+        ("Archetypes.GameEvent.GameEvent_Season", "TAGame.GameEvent_Season_TA"),+        ("Archetypes.GameEvent.GameEvent_Soccar", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_SoccarLan", "TAGame.GameEvent_Soccar_TA"),+        ("Archetypes.GameEvent.GameEvent_SoccarPrivate", "TAGame.GameEvent_SoccarPrivate_TA"),+        ("Archetypes.GameEvent.GameEvent_SoccarSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BallFreeze", "TAGame.SpecialPickup_BallFreeze_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BallGrapplingHook", "TAGame.SpecialPickup_GrapplingHook_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BallLasso", "TAGame.SpecialPickup_BallLasso_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BallSpring", "TAGame.SpecialPickup_BallCarSpring_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BallVelcro", "TAGame.SpecialPickup_BallVelcro_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_Batarang", "TAGame.SpecialPickup_Batarang_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_BoostOverride", "TAGame.SpecialPickup_BoostOverride_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_CarSpring", "TAGame.SpecialPickup_BallCarSpring_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_Football", "TAGame.SpecialPickup_Football_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_GravityWell", "TAGame.SpecialPickup_BallGravity_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_HauntedBallBeam", "TAGame.SpecialPickup_HauntedBallBeam_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_Rugby", "TAGame.SpecialPickup_Rugby_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_StrongHit", "TAGame.SpecialPickup_HitForce_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_Swapper", "TAGame.SpecialPickup_Swapper_TA"),+        ("Archetypes.SpecialPickups.SpecialPickup_Tornado", "TAGame.SpecialPickup_Tornado_TA"),+        ("Archetypes.Teams.Team0", "TAGame.Team_Soccar_TA"),+        ("Archetypes.Teams.Team1", "TAGame.Team_Soccar_TA"),+        ("Archetypes.Tutorial.Cannon", "TAGame.Cannon_TA"),+        ("GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_Breakout.GameInfo.GameInfo_Breakout:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_FootBall.GameInfo.GameInfo_FootBall:Archetype", "TAGame.GameEvent_Football_TA"),+        ("GameInfo_FootBall.GameInfo.GameInfo_FootBall:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_FTE.GameInfo.GameInfo_FTE:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("gameinfo_godball.GameInfo.gameinfo_godball:Archetype", "TAGame.GameEvent_GodBall_TA"),+        ("GameInfo_GodBall.GameInfo.GameInfo_GodBall:Archetype", "TAGame.GameEvent_GodBall_TA"),+        ("gameinfo_godball.GameInfo.gameinfo_godball:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_GodBall.GameInfo.GameInfo_GodBall:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_Items.GameInfo.GameInfo_Items:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("GameInfo_Tutorial.GameEvent.GameEvent_Tutorial_Aerial", "TAGame.GameEvent_Tutorial_TA"),+        ("GameInfo_Tutorial.GameInfo.GameInfo_Tutorial:GameReplicationInfoArchetype", "TAGame.GRI_TA"),+        ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_0", "TAGame.HauntedBallTrapTrigger_TA"),+        ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_1", "TAGame.HauntedBallTrapTrigger_TA"),+        ("ProjectX.Default__NetModeReplicator_X", "ProjectX.NetModeReplicator"),+        ("TAGame.Default__CameraSettingsActor_TA", "TAGame.CameraSettingsActor_TA"),+        ("TAGame.Default__MaxTimeWarningData_TA", "TAGame.MaxTimeWarningData_TA"),+        ("TAGame.Default__PickupTimer_TA", "TAGame.PickupTimer_TA"),+        ("TAGame.Default__PRI_TA", "TAGame.PRI_TA"),+        ("TAGame.Default__RumblePickups_TA", "TAGame.RumblePickups_TA"),+        ("TheWorld:PersistentLevel.BreakOutActor_Platform_TA", "TAGame.BreakOutActor_Platform_TA"),+        ("TheWorld:PersistentLevel.CrowdActor_TA", "TAGame.CrowdActor_TA"),+        ("TheWorld:PersistentLevel.CrowdManager_TA", "TAGame.CrowdManager_TA"),+        ("TheWorld:PersistentLevel.InMapScoreboard_TA", "TAGame.InMapScoreboard_TA"),+        ("TheWorld:PersistentLevel.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_Boost_TA")+      ]++attributeTypes :: Map.Map Text.Text AttributeType.AttributeType+attributeTypes =+  Map.fromList $+    fmap+      (Bifunctor.first Text.pack)+      [ ("Engine.Actor:bBlockActors", AttributeType.Boolean),+        ("Engine.Actor:bCollideActors", AttributeType.Boolean),+        ("Engine.Actor:bHidden", AttributeType.Boolean),+        ("Engine.Actor:bTearOff", AttributeType.Boolean),+        ("Engine.Actor:DrawScale", AttributeType.Float),+        ("Engine.Actor:RemoteRole", AttributeType.Enum),+        ("Engine.Actor:Role", AttributeType.Enum),+        ("Engine.Actor:Rotation", AttributeType.Rotation),+        ("Engine.GameReplicationInfo:bMatchIsOver", AttributeType.Boolean),+        ("Engine.GameReplicationInfo:GameClass", AttributeType.FlaggedInt),+        ("Engine.GameReplicationInfo:ServerName", AttributeType.String),+        ("Engine.Pawn:HealthMax", AttributeType.Int),+        ("Engine.Pawn:PlayerReplicationInfo", AttributeType.FlaggedInt),+        ("Engine.PlayerReplicationInfo:bBot", AttributeType.Boolean),+        ("Engine.PlayerReplicationInfo:bIsSpectator", AttributeType.Boolean),+        ("Engine.PlayerReplicationInfo:bReadyToPlay", AttributeType.Boolean),+        ("Engine.PlayerReplicationInfo:bTimedOut", AttributeType.Boolean),+        ("Engine.PlayerReplicationInfo:bWaitingPlayer", AttributeType.Boolean),+        ("Engine.PlayerReplicationInfo:Ping", AttributeType.Byte),+        ("Engine.PlayerReplicationInfo:PlayerID", AttributeType.Int),+        ("Engine.PlayerReplicationInfo:PlayerName", AttributeType.String),+        ("Engine.PlayerReplicationInfo:RemoteUserData", AttributeType.String),+        ("Engine.PlayerReplicationInfo:Score", AttributeType.Int),+        ("Engine.PlayerReplicationInfo:Team", AttributeType.FlaggedInt),+        ("Engine.PlayerReplicationInfo:UniqueId", AttributeType.UniqueId),+        ("Engine.ReplicatedActor_ORS:ReplicatedOwner", AttributeType.FlaggedInt),+        ("Engine.TeamInfo:Score", AttributeType.Int),+        ("ProjectX.GRI_X:bGameStarted", AttributeType.Boolean),+        ("ProjectX.GRI_X:GameServerID", AttributeType.GameServer),+        ("ProjectX.GRI_X:MatchGuid", AttributeType.String),+        ("ProjectX.GRI_X:MatchGUID", AttributeType.String),+        ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", AttributeType.Int),+        ("ProjectX.GRI_X:ReplicatedGamePlaylist", AttributeType.Int),+        ("ProjectX.GRI_X:ReplicatedServerRegion", AttributeType.String),+        ("ProjectX.GRI_X:Reservations", AttributeType.Reservation),+        ("TAGame.Ball_Breakout_TA:AppliedDamage", AttributeType.AppliedDamage),+        ("TAGame.Ball_Breakout_TA:DamageIndex", AttributeType.Int),+        ("TAGame.Ball_Breakout_TA:LastTeamTouch", AttributeType.Byte),+        ("TAGame.Ball_God_TA:TargetSpeed", AttributeType.Float),+        ("TAGame.Ball_Haunted_TA:bIsBallBeamed", AttributeType.Boolean),+        ("TAGame.Ball_Haunted_TA:DeactivatedGoalIndex", AttributeType.Byte),+        ("TAGame.Ball_Haunted_TA:LastTeamTouch", AttributeType.Byte),+        ("TAGame.Ball_Haunted_TA:ReplicatedBeamBrokenValue", AttributeType.Byte),+        ("TAGame.Ball_Haunted_TA:TotalActiveBeams", AttributeType.Byte),+        ("TAGame.Ball_TA:GameEvent", AttributeType.FlaggedInt),+        ("TAGame.Ball_TA:HitTeamNum", AttributeType.Byte),+        ("TAGame.Ball_TA:ReplicatedAddedCarBounceScale", AttributeType.Float),+        ("TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale", AttributeType.Float),+        ("TAGame.Ball_TA:ReplicatedBallScale", AttributeType.Float),+        ("TAGame.Ball_TA:ReplicatedExplosionData", AttributeType.Explosion),+        ("TAGame.Ball_TA:ReplicatedExplosionDataExtended", AttributeType.ExtendedExplosion),+        ("TAGame.Ball_TA:ReplicatedPhysMatOverride", AttributeType.FlaggedInt),+        ("TAGame.Ball_TA:ReplicatedWorldBounceScale", AttributeType.Float),+        ("TAGame.BreakOutActor_Platform_TA:DamageState", AttributeType.DamageState),+        ("TAGame.CameraSettingsActor_TA:bMouseCameraToggleEnabled", AttributeType.Boolean),+        ("TAGame.CameraSettingsActor_TA:bUsingBehindView", AttributeType.Boolean),+        ("TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera", AttributeType.Boolean),+        ("TAGame.CameraSettingsActor_TA:bUsingSwivel", AttributeType.Boolean),+        ("TAGame.CameraSettingsActor_TA:CameraPitch", AttributeType.Byte),+        ("TAGame.CameraSettingsActor_TA:CameraYaw", AttributeType.Byte),+        ("TAGame.CameraSettingsActor_TA:PRI", AttributeType.FlaggedInt),+        ("TAGame.CameraSettingsActor_TA:ProfileSettings", AttributeType.CamSettings),+        ("TAGame.Cannon_TA:FireCount", AttributeType.Byte),+        ("TAGame.Cannon_TA:Pitch", AttributeType.Float),+        ("TAGame.Car_TA:AddedBallForceMultiplier", AttributeType.Float),+        ("TAGame.Car_TA:AddedCarForceMultiplier", AttributeType.Float),+        ("TAGame.Car_TA:AttachedPickup", AttributeType.FlaggedInt),+        ("TAGame.Car_TA:ClubColors", AttributeType.ClubColors),+        ("TAGame.Car_TA:ReplicatedCarScale", AttributeType.Float),+        ("TAGame.Car_TA:ReplicatedDemolish_CustomFX", AttributeType.CustomDemolish),+        ("TAGame.Car_TA:ReplicatedDemolish", AttributeType.Demolish),+        ("TAGame.Car_TA:ReplicatedDemolishExtended", AttributeType.CustomDemolishExtended),+        ("TAGame.Car_TA:ReplicatedDemolishGoalExplosion", AttributeType.CustomDemolish),+        ("TAGame.Car_TA:RumblePickups", AttributeType.FlaggedInt),+        ("TAGame.Car_TA:TeamPaint", AttributeType.TeamPaint),+        ("TAGame.CarComponent_Boost_TA:bNoBoost", AttributeType.Boolean),+        ("TAGame.CarComponent_Boost_TA:BoostModifier", AttributeType.Float),+        ("TAGame.CarComponent_Boost_TA:bUnlimitedBoost", AttributeType.Boolean),+        ("TAGame.CarComponent_Boost_TA:RechargeDelay", AttributeType.Float),+        ("TAGame.CarComponent_Boost_TA:RechargeRate", AttributeType.Float),+        ("TAGame.CarComponent_Boost_TA:ReplicatedBoost", AttributeType.Boost),+        ("TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount", AttributeType.Byte),+        ("TAGame.CarComponent_Boost_TA:UnlimitedBoostRefCount", AttributeType.Int),+        ("TAGame.CarComponent_Dodge_TA:DodgeImpulse", AttributeType.Location),+        ("TAGame.CarComponent_Dodge_TA:DodgeTorque", AttributeType.Location),+        ("TAGame.CarComponent_DoubleJump_TA:DoubleJumpImpulse", AttributeType.Location),+        ("TAGame.CarComponent_FlipCar_TA:bFlipRight", AttributeType.Boolean),+        ("TAGame.CarComponent_FlipCar_TA:FlipCarTime", AttributeType.Float),+        ("TAGame.CarComponent_TA:ReplicatedActive", AttributeType.Byte),+        ("TAGame.CarComponent_TA:ReplicatedActivityTime", AttributeType.Float),+        ("TAGame.CarComponent_TA:Vehicle", AttributeType.FlaggedInt),+        ("TAGame.CrowdActor_TA:GameEvent", AttributeType.FlaggedInt),+        ("TAGame.CrowdActor_TA:ModifiedNoise", AttributeType.Float),+        ("TAGame.CrowdActor_TA:ReplicatedCountDownNumber", AttributeType.Int),+        ("TAGame.CrowdActor_TA:ReplicatedOneShotSound", AttributeType.FlaggedInt),+        ("TAGame.CrowdActor_TA:ReplicatedRoundCountDownNumber", AttributeType.Int),+        ("TAGame.CrowdManager_TA:GameEvent", AttributeType.FlaggedInt),+        ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:bClubMatch", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:bMatchEnded", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:bNoContest", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:bOverTime", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:bUnlimitedTime", AttributeType.Boolean),+        ("TAGame.GameEvent_Soccar_TA:GameTime", AttributeType.Int),+        ("TAGame.GameEvent_Soccar_TA:GameWinner", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_Soccar_TA:MatchWinner", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_Soccar_TA:MaxScore", AttributeType.Int),+        ("TAGame.GameEvent_Soccar_TA:MVP", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", AttributeType.MusicStinger),+        ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", AttributeType.Byte),+        ("TAGame.GameEvent_Soccar_TA:ReplicatedServerPerformanceState", AttributeType.Byte),+        ("TAGame.GameEvent_Soccar_TA:ReplicatedStatEvent", AttributeType.StatEvent),+        ("TAGame.GameEvent_Soccar_TA:RoundNum", AttributeType.Int),+        ("TAGame.GameEvent_Soccar_TA:SecondsRemaining", AttributeType.Int),+        ("TAGame.GameEvent_Soccar_TA:SeriesLength", AttributeType.Int),+        ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", AttributeType.PrivateMatchSettings),+        ("TAGame.GameEvent_TA:bAllowReadyUp", AttributeType.Boolean),+        ("TAGame.GameEvent_TA:bCanVoteToForfeit", AttributeType.Boolean),+        ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", AttributeType.Boolean),+        ("TAGame.GameEvent_TA:BotSkill", AttributeType.Int),+        ("TAGame.GameEvent_TA:GameMode", AttributeType.GameMode),+        ("TAGame.GameEvent_TA:MatchStartEpoch", AttributeType.Int64),+        ("TAGame.GameEvent_TA:MatchTypeClass", AttributeType.FlaggedInt),+        ("TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining", AttributeType.Int),+        ("TAGame.GameEvent_TA:ReplicatedRoundCountDownNumber", AttributeType.Int),+        ("TAGame.GameEvent_TA:ReplicatedStateIndex", AttributeType.Byte),+        ("TAGame.GameEvent_TA:ReplicatedStateName", AttributeType.Int),+        ("TAGame.GameEvent_Team_TA:bForfeit", AttributeType.Boolean),+        ("TAGame.GameEvent_Team_TA:MaxTeamSize", AttributeType.Int),+        ("TAGame.GRI_TA:NewDedicatedServerIP", AttributeType.String),+        ("TAGame.MaxTimeWarningData_TA:EndGameEpochTime", AttributeType.Int64),+        ("TAGame.MaxTimeWarningData_TA:EndGameWarningEpochTime", AttributeType.Int64),+        ("TAGame.PRI_TA:bIsDistracted", AttributeType.Boolean),+        ("TAGame.PRI_TA:bIsInSplitScreen", AttributeType.Boolean),+        ("TAGame.PRI_TA:bMatchMVP", AttributeType.Boolean),+        ("TAGame.PRI_TA:bOnlineLoadoutSet", AttributeType.Boolean),+        ("TAGame.PRI_TA:bOnlineLoadoutsSet", AttributeType.Boolean),+        ("TAGame.PRI_TA:BotProductName", AttributeType.Int),+        ("TAGame.PRI_TA:bReady", AttributeType.Boolean),+        ("TAGame.PRI_TA:bUsingBehindView", AttributeType.Boolean),+        ("TAGame.PRI_TA:bUsingItems", AttributeType.Boolean),+        ("TAGame.PRI_TA:bUsingSecondaryCamera", AttributeType.Boolean),+        ("TAGame.PRI_TA:CameraPitch", AttributeType.Byte),+        ("TAGame.PRI_TA:CameraSettings", AttributeType.CamSettings),+        ("TAGame.PRI_TA:CameraYaw", AttributeType.Byte),+        ("TAGame.PRI_TA:ClientLoadout", AttributeType.Loadout),+        ("TAGame.PRI_TA:ClientLoadoutOnline", AttributeType.LoadoutOnline),+        ("TAGame.PRI_TA:ClientLoadouts", AttributeType.Loadouts),+        ("TAGame.PRI_TA:ClientLoadoutsOnline", AttributeType.LoadoutsOnline),+        ("TAGame.PRI_TA:ClubID", AttributeType.Int64),+        ("TAGame.PRI_TA:CurrentVoiceRoom", AttributeType.String),+        ("TAGame.PRI_TA:MatchAssists", AttributeType.Int),+        ("TAGame.PRI_TA:MatchBreakoutDamage", AttributeType.Int),+        ("TAGame.PRI_TA:MatchGoals", AttributeType.Int),+        ("TAGame.PRI_TA:MatchSaves", AttributeType.Int),+        ("TAGame.PRI_TA:MatchScore", AttributeType.Int),+        ("TAGame.PRI_TA:MatchShots", AttributeType.Int),+        ("TAGame.PRI_TA:MaxTimeTillItem", AttributeType.Int),+        ("TAGame.PRI_TA:PartyLeader", AttributeType.PartyLeader),+        ("TAGame.PRI_TA:PawnType", AttributeType.Byte),+        ("TAGame.PRI_TA:PersistentCamera", AttributeType.FlaggedInt),+        ("TAGame.PRI_TA:PlayerHistoryKey", AttributeType.PlayerHistoryKey),+        ("TAGame.PRI_TA:PlayerHistoryValid", AttributeType.Boolean),+        ("TAGame.PRI_TA:PrimaryTitle", AttributeType.Title),+        ("TAGame.PRI_TA:ReplicatedGameEvent", AttributeType.FlaggedInt),+        ("TAGame.PRI_TA:ReplicatedWorstNetQualityBeyondLatency", AttributeType.Byte),+        ("TAGame.PRI_TA:RepStatTitles", AttributeType.RepStatTitle),+        ("TAGame.PRI_TA:SecondaryTitle", AttributeType.Title),+        ("TAGame.PRI_TA:SkillTier", AttributeType.FlaggedByte),+        ("TAGame.PRI_TA:SpectatorShortcut", AttributeType.Int),+        ("TAGame.PRI_TA:SteeringSensitivity", AttributeType.Float),+        ("TAGame.PRI_TA:TimeTillItem", AttributeType.Int),+        ("TAGame.PRI_TA:Title", AttributeType.Int),+        ("TAGame.PRI_TA:TotalXP", AttributeType.Int),+        ("TAGame.RBActor_TA:bFrozen", AttributeType.Boolean),+        ("TAGame.RBActor_TA:bIgnoreSyncing", AttributeType.Boolean),+        ("TAGame.RBActor_TA:bReplayActor", AttributeType.Boolean),+        ("TAGame.RBActor_TA:ReplicatedRBState", AttributeType.RigidBodyState),+        ("TAGame.RBActor_TA:WeldedInfo", AttributeType.WeldedInfo),+        ("TAGame.RumblePickups_TA:AttachedPickup", AttributeType.FlaggedInt),+        ("TAGame.RumblePickups_TA:ConcurrentItemCount", AttributeType.Int),+        ("TAGame.RumblePickups_TA:PickupInfo", AttributeType.PickupInfo),+        ("TAGame.SpecialPickup_BallFreeze_TA:RepOrigSpeed", AttributeType.Float),+        ("TAGame.SpecialPickup_BallVelcro_TA:AttachTime", AttributeType.Float),+        ("TAGame.SpecialPickup_BallVelcro_TA:bBroken", AttributeType.Boolean),+        ("TAGame.SpecialPickup_BallVelcro_TA:bHit", AttributeType.Boolean),+        ("TAGame.SpecialPickup_BallVelcro_TA:BreakTime", AttributeType.Float),+        ("TAGame.SpecialPickup_Football_TA:WeldedBall", AttributeType.FlaggedInt),+        ("TAGame.SpecialPickup_Rugby_TA:bBallWelded", AttributeType.Boolean),+        ("TAGame.SpecialPickup_Targeted_TA:Targeted", AttributeType.FlaggedInt),+        ("TAGame.Team_Soccar_TA:GameScore", AttributeType.Int),+        ("TAGame.Team_TA:ClubColors", AttributeType.ClubColors),+        ("TAGame.Team_TA:ClubID", AttributeType.Int64),+        ("TAGame.Team_TA:CustomTeamName", AttributeType.String),+        ("TAGame.Team_TA:Difficulty", AttributeType.Int),+        ("TAGame.Team_TA:GameEvent", AttributeType.FlaggedInt),+        ("TAGame.Team_TA:LogoData", AttributeType.FlaggedInt),+        ("TAGame.Vehicle_TA:bDriving", AttributeType.Boolean),+        ("TAGame.Vehicle_TA:bHasPostMatchCelebration", AttributeType.Boolean),+        ("TAGame.Vehicle_TA:bPodiumMode", AttributeType.Boolean),+        ("TAGame.Vehicle_TA:bReplicatedHandbrake", AttributeType.Boolean),+        ("TAGame.Vehicle_TA:ReplicatedSteer", AttributeType.Byte),+        ("TAGame.Vehicle_TA:ReplicatedThrottle", AttributeType.Byte),+        ("TAGame.VehiclePickup_TA:bNoPickup", AttributeType.Boolean),+        ("TAGame.VehiclePickup_TA:NewReplicatedPickupData", AttributeType.PickupNew),+        ("TAGame.VehiclePickup_TA:ReplicatedPickupData", AttributeType.Pickup)+      ]
+ src/lib/Rattletrap/Exception/CrcMismatch.hs view
@@ -0,0 +1,11 @@+module Rattletrap.Exception.CrcMismatch where++import qualified Control.Exception as Exception+import qualified Data.Word as Word++data CrcMismatch = CrcMismatch Word.Word32 Word.Word32+  deriving (Eq, Show)++instance Exception.Exception CrcMismatch where+  displayException (CrcMismatch x y) =+    unwords ["invalid CRC: expected", show x, "but got", show y]
+ src/lib/Rattletrap/Exception/Empty.hs view
@@ -0,0 +1,8 @@+module Rattletrap.Exception.Empty where++import qualified Control.Exception as Exception++data Empty = Empty+  deriving (Eq, Show)++instance Exception.Exception Empty
+ src/lib/Rattletrap/Exception/Fail.hs view
@@ -0,0 +1,10 @@+module Rattletrap.Exception.Fail where++import qualified Control.Exception as Exception++newtype Fail+  = Fail String+  deriving (Eq, Show)++instance Exception.Exception Fail where+  displayException (Fail x) = x
+ src/lib/Rattletrap/Exception/InvalidComponent.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.InvalidComponent where++import qualified Control.Exception as Exception++newtype InvalidComponent+  = InvalidComponent Word+  deriving (Eq, Show)++instance Exception.Exception InvalidComponent
+ src/lib/Rattletrap/Exception/InvalidJson.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.InvalidJson where++import qualified Control.Exception as Exception++newtype InvalidJson+  = InvalidJson String+  deriving (Eq, Show)++instance Exception.Exception InvalidJson
+ src/lib/Rattletrap/Exception/MissingAttributeLimit.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.MissingAttributeLimit where++import qualified Control.Exception as Exception++newtype MissingAttributeLimit+  = MissingAttributeLimit Word+  deriving (Eq, Show)++instance Exception.Exception MissingAttributeLimit
+ src/lib/Rattletrap/Exception/MissingAttributeName.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.MissingAttributeName where++import qualified Control.Exception as Exception++newtype MissingAttributeName+  = MissingAttributeName Word+  deriving (Eq, Show)++instance Exception.Exception MissingAttributeName
+ src/lib/Rattletrap/Exception/MissingClassName.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.MissingClassName where++import qualified Control.Exception as Exception++newtype MissingClassName+  = MissingClassName String+  deriving (Eq, Show)++instance Exception.Exception MissingClassName
+ src/lib/Rattletrap/Exception/MissingObjectName.hs view
@@ -0,0 +1,10 @@+module Rattletrap.Exception.MissingObjectName where++import qualified Control.Exception as Exception+import qualified Data.Word as Word++newtype MissingObjectName+  = MissingObjectName Word.Word32+  deriving (Eq, Show)++instance Exception.Exception MissingObjectName
+ src/lib/Rattletrap/Exception/MissingProductName.hs view
@@ -0,0 +1,10 @@+module Rattletrap.Exception.MissingProductName where++import qualified Control.Exception as Exception+import qualified Data.Word as Word++newtype MissingProductName+  = MissingProductName Word.Word32+  deriving (Eq, Show)++instance Exception.Exception MissingProductName
+ src/lib/Rattletrap/Exception/NotEnoughInput.hs view
@@ -0,0 +1,8 @@+module Rattletrap.Exception.NotEnoughInput where++import qualified Control.Exception as Exception++data NotEnoughInput = NotEnoughInput+  deriving (Eq, Show)++instance Exception.Exception NotEnoughInput
+ src/lib/Rattletrap/Exception/UnknownActor.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.UnknownActor where++import qualified Control.Exception as Exception++newtype UnknownActor+  = UnknownActor Word+  deriving (Eq, Show)++instance Exception.Exception UnknownActor
+ src/lib/Rattletrap/Exception/UnknownAttribute.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.UnknownAttribute where++import qualified Control.Exception as Exception++newtype UnknownAttribute+  = UnknownAttribute String+  deriving (Eq, Show)++instance Exception.Exception UnknownAttribute
+ src/lib/Rattletrap/Exception/UnknownName.hs view
@@ -0,0 +1,10 @@+module Rattletrap.Exception.UnknownName where++import qualified Control.Exception as Exception+import qualified Data.Word as Word++newtype UnknownName+  = UnknownName Word.Word32+  deriving (Eq, Show)++instance Exception.Exception UnknownName
+ src/lib/Rattletrap/Exception/UnknownProduct.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.UnknownProduct where++import qualified Control.Exception as Exception++newtype UnknownProduct+  = UnknownProduct String+  deriving (Eq, Show)++instance Exception.Exception UnknownProduct
+ src/lib/Rattletrap/Exception/UnknownProperty.hs view
@@ -0,0 +1,9 @@+module Rattletrap.Exception.UnknownProperty where++import qualified Control.Exception as Exception++newtype UnknownProperty+  = UnknownProperty String+  deriving (Eq, Show)++instance Exception.Exception UnknownProperty
+ src/lib/Rattletrap/Exception/UnknownSystemId.hs view
@@ -0,0 +1,10 @@+module Rattletrap.Exception.UnknownSystemId where++import qualified Control.Exception as Exception+import qualified Data.Word as Word++newtype UnknownSystemId+  = UnknownSystemId Word.Word8+  deriving (Eq, Show)++instance Exception.Exception UnknownSystemId
+ src/lib/Rattletrap/Get.hs view
@@ -0,0 +1,68 @@+module Rattletrap.Get where++import qualified Control.Applicative as Applicative+import qualified Control.Exception as Exception+import qualified Data.Bifunctor as Bifunctor+import qualified Rattletrap.Exception.Empty as Empty+import qualified Rattletrap.Exception.Fail as Fail++newtype Get s m a = Get (s -> m (Either ([String], Exception.SomeException) (s, a)))++instance (Functor m) => Functor (Get s m) where+  fmap f g = Get $ fmap (fmap (fmap f)) . run g++instance (Monad m) => Applicative (Get s m) where+  pure x = Get $ \s -> pure $ Right (s, x)++  gf <*> gx = Get $ \s1 -> do+    r <- run gf s1+    case r of+      Left e -> pure $ Left e+      Right (s2, f) -> run (fmap f gx) s2++instance (Monad m) => Monad (Get s m) where+  g >>= f = Get $ \s1 -> do+    r <- run g s1+    case r of+      Left e -> pure $ Left e+      Right (s2, x) -> run (f x) s2++instance (Monad m) => MonadFail (Get s m) where+  fail = throw . Fail.Fail++instance (Monad m) => Applicative.Alternative (Get s m) where+  empty = throw Empty.Empty++  gx <|> gy = Get $ \s -> do+    r <- run gx s+    case r of+      Left _ -> run gy s+      Right x -> pure $ Right x++run :: Get s m a -> s -> m (Either ([String], Exception.SomeException) (s, a))+run (Get f) = f++get :: (Applicative m) => Get s m s+get = Get $ \s -> pure $ Right (s, s)++put :: (Applicative m) => s -> Get s m ()+put s = Get $ \_ -> pure $ Right (s, ())++lift :: (Functor m) => m a -> Get s m a+lift m = Get $ \s -> fmap (\x -> Right (s, x)) m++throw :: (Exception.Exception e, Applicative m) => e -> Get s m a+throw = Get . const . pure . Left . (,) [] . Exception.toException++embed :: (Monad m) => Get s m a -> s -> Get t m a+embed g s = do+  r <- lift $ run g s+  case r of+    Left (ls, e) -> labels ls $ throw e+    Right (_, x) -> pure x++labels :: (Functor m) => [String] -> Get s m a -> Get s m a+labels ls g = Get $ fmap (Bifunctor.first $ Bifunctor.first (ls <>)) . run g++label :: (Functor m) => String -> Get s m a -> Get s m a+label = labels . pure
+ src/lib/Rattletrap/Schema.hs view
@@ -0,0 +1,68 @@+module Rattletrap.Schema where++import qualified Data.Text as Text+import qualified Rattletrap.Utility.Json as Json++data Schema = Schema+  { name :: Text.Text,+    json :: Json.Value+  }+  deriving (Eq, Show)++named :: String -> Json.Value -> Schema+named n j = Schema {name = Text.pack n, json = j}++ref :: Schema -> Json.Value+ref s = Json.object [Json.pair "$ref" $ Text.pack "#/definitions/" <> name s]++object :: [((Json.Key, Json.Value), Bool)] -> Json.Value+object xs =+  Json.object+    [ Json.pair "type" "object",+      Json.pair "properties" . Json.object $+        fmap+          ((\(k, v) -> Json.pair (Json.keyToString k) v) . fst)+          xs,+      Json.pair "required" . fmap (fst . fst) $ filter snd xs+    ]++maybe :: Schema -> Schema+maybe s =+  Schema+    { name = Text.pack "maybe-" <> name s,+      json = oneOf [ref s, json Rattletrap.Schema.null]+    }++oneOf :: [Json.Value] -> Json.Value+oneOf xs = Json.object [Json.pair "oneOf" xs]++tuple :: [Json.Value] -> Json.Value+tuple xs =+  Json.object+    [ Json.pair "type" "array",+      Json.pair "items" xs,+      Json.pair "minItems" $ length xs,+      Json.pair "maxItems" $ length xs+    ]++array :: Schema -> Schema+array s =+  Schema+    { name = Text.pack "array-" <> name s,+      json = Json.object [Json.pair "type" "array", Json.pair "items" $ ref s]+    }++boolean :: Schema+boolean = named "boolean" $ Json.object [Json.pair "type" "boolean"]++integer :: Schema+integer = named "integer" $ Json.object [Json.pair "type" "integer"]++null :: Schema+null = named "null" $ Json.object [Json.pair "type" "null"]++number :: Schema+number = named "number" $ Json.object [Json.pair "type" "number"]++string :: Schema+string = named "string" $ Json.object [Json.pair "type" "string"]
+ src/lib/Rattletrap/Type/Attribute.hs view
@@ -0,0 +1,109 @@+module Rattletrap.Type.Attribute where++import qualified Control.Exception as Exception+import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Exception.MissingAttributeLimit as MissingAttributeLimit+import qualified Rattletrap.Exception.MissingAttributeName as MissingAttributeName+import qualified Rattletrap.Exception.UnknownActor as UnknownActor+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.AttributeValue as AttributeValue+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Attribute = Attribute+  { id :: CompressedWord.CompressedWord,+    -- | Read-only! Changing an attribute's name requires editing the class+    -- attribute map.+    name :: Str.Str,+    value :: AttributeValue.AttributeValue+  }+  deriving (Eq, Show)++instance Json.FromJSON Attribute where+  parseJSON = Json.withObject "Attribute" $ \object -> do+    id_ <- Json.required object "id"+    name <- Json.required object "name"+    value <- Json.required object "value"+    pure Attribute {Rattletrap.Type.Attribute.id = id_, name, value}++instance Json.ToJSON Attribute where+  toJSON x =+    Json.object+      [ Json.pair "id" $ Rattletrap.Type.Attribute.id x,+        Json.pair "name" $ name x,+        Json.pair "value" $ value x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute" $+    Schema.object+      [ (Json.pair "id" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "name" $ Schema.ref Str.schema, True),+        (Json.pair "value" $ Schema.ref AttributeValue.schema, True)+      ]++bitPut :: Attribute -> BitPut.BitPut+bitPut attribute =+  CompressedWord.bitPut (Rattletrap.Type.Attribute.id attribute)+    <> AttributeValue.bitPut (value attribute)++bitGet ::+  Version.Version ->+  Maybe Str.Str ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  CompressedWord.CompressedWord ->+  BitGet.BitGet Attribute+bitGet version buildVersion classes actors actor =+  BitGet.label "Attribute" $ do+    attributes <- lookupAttributeMap classes actors actor+    limit <- lookupAttributeIdLimit attributes actor+    id_ <- BitGet.label "id" $ CompressedWord.bitGet limit+    name <- lookupAttributeName classes attributes id_+    value <-+      BitGet.label "value" $+        AttributeValue.bitGet+          version+          buildVersion+          (ClassAttributeMap.objectMap classes)+          name+    pure Attribute {Rattletrap.Type.Attribute.id = id_, name, value}++lookupAttributeMap ::+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  CompressedWord.CompressedWord ->+  BitGet.BitGet (Map.Map U32.U32 U32.U32)+lookupAttributeMap classes actors actor =+  fromMaybe+    (UnknownActor.UnknownActor $ CompressedWord.value actor)+    (ClassAttributeMap.getAttributeMap classes actors actor)++lookupAttributeIdLimit ::+  Map.Map U32.U32 U32.U32 ->+  CompressedWord.CompressedWord ->+  BitGet.BitGet Word+lookupAttributeIdLimit attributes actor =+  fromMaybe+    (MissingAttributeLimit.MissingAttributeLimit $ CompressedWord.value actor)+    (ClassAttributeMap.getAttributeIdLimit attributes)++lookupAttributeName ::+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map U32.U32 U32.U32 ->+  CompressedWord.CompressedWord ->+  BitGet.BitGet Str.Str+lookupAttributeName classes attributes attribute =+  fromMaybe+    (MissingAttributeName.MissingAttributeName $ CompressedWord.value attribute)+    (ClassAttributeMap.getAttributeName classes attributes attribute)++fromMaybe :: (Exception.Exception e) => e -> Maybe a -> BitGet.BitGet a+fromMaybe message = maybe (BitGet.throw message) pure
+ src/lib/Rattletrap/Type/Attribute/AppliedDamage.hs view
@@ -0,0 +1,60 @@+module Rattletrap.Type.Attribute.AppliedDamage where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data AppliedDamage = AppliedDamage+  { unknown1 :: U8.U8,+    location :: Vector.Vector,+    unknown3 :: I32.I32,+    unknown4 :: I32.I32+  }+  deriving (Eq, Show)++instance Json.FromJSON AppliedDamage where+  parseJSON = Json.withObject "AppliedDamage" $ \object -> do+    unknown1 <- Json.required object "unknown1"+    location <- Json.required object "location"+    unknown3 <- Json.required object "unknown3"+    unknown4 <- Json.required object "unknown4"+    pure AppliedDamage {unknown1, location, unknown3, unknown4}++instance Json.ToJSON AppliedDamage where+  toJSON x =+    Json.object+      [ Json.pair "unknown1" $ unknown1 x,+        Json.pair "location" $ location x,+        Json.pair "unknown3" $ unknown3 x,+        Json.pair "unknown4" $ unknown4 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-applied-damage" $+    Schema.object+      [ (Json.pair "unknown1" $ Schema.ref U8.schema, True),+        (Json.pair "location" $ Schema.ref Vector.schema, True),+        (Json.pair "unknown3" $ Schema.ref I32.schema, True),+        (Json.pair "unknown4" $ Schema.ref I32.schema, True)+      ]++bitPut :: AppliedDamage -> BitPut.BitPut+bitPut appliedDamageAttribute =+  U8.bitPut (unknown1 appliedDamageAttribute)+    <> Vector.bitPut (location appliedDamageAttribute)+    <> I32.bitPut (unknown3 appliedDamageAttribute)+    <> I32.bitPut (unknown4 appliedDamageAttribute)++bitGet :: Version.Version -> BitGet.BitGet AppliedDamage+bitGet version = BitGet.label "AppliedDamage" $ do+  unknown1 <- BitGet.label "unknown1" U8.bitGet+  location <- BitGet.label "location" $ Vector.bitGet version+  unknown3 <- BitGet.label "unknown3" I32.bitGet+  unknown4 <- BitGet.label "unknown4" I32.bitGet+  pure AppliedDamage {unknown1, location, unknown3, unknown4}
+ src/lib/Rattletrap/Type/Attribute/Boolean.hs view
@@ -0,0 +1,28 @@+module Rattletrap.Type.Attribute.Boolean where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype Boolean = Boolean+  { value :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON Boolean where+  parseJSON = fmap Boolean . Json.parseJSON++instance Json.ToJSON Boolean where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-boolean" $ Schema.ref Schema.boolean++bitPut :: Boolean -> BitPut.BitPut+bitPut booleanAttribute = BitPut.bool (value booleanAttribute)++bitGet :: BitGet.BitGet Boolean+bitGet = BitGet.label "Boolean" $ do+  value <- BitGet.label "value" BitGet.bool+  pure Boolean {value}
+ src/lib/Rattletrap/Type/Attribute/Boost.hs view
@@ -0,0 +1,57 @@+module Rattletrap.Type.Attribute.Boost where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data Boost = Boost+  { grantCount :: U8.U8,+    boostAmount :: U8.U8,+    unused1 :: U8.U8,+    unused2 :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON Boost where+  parseJSON = Json.withObject "Boost" $ \object -> do+    grantCount <- Json.required object "grantCount"+    boostAmount <- Json.required object "boostAmount"+    unused1 <- Json.required object "unused1"+    unused2 <- Json.required object "unused2"+    pure Boost {grantCount, boostAmount, unused1, unused2}++instance Json.ToJSON Boost where+  toJSON x =+    Json.object+      [ Json.pair "grantCount" $ grantCount x,+        Json.pair "boostAmount" $ boostAmount x,+        Json.pair "unused1" $ unused1 x,+        Json.pair "unused2" $ unused2 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "boost" $+    Schema.object+      [ (Json.pair "grantCount" $ Schema.ref U8.schema, True),+        (Json.pair "boostAmount" $ Schema.ref U8.schema, True),+        (Json.pair "unused1" $ Schema.ref U8.schema, True),+        (Json.pair "unused2" $ Schema.ref U8.schema, True)+      ]++bitPut :: Boost -> BitPut.BitPut+bitPut appliedDamageAttribute =+  U8.bitPut (grantCount appliedDamageAttribute)+    <> U8.bitPut (boostAmount appliedDamageAttribute)+    <> U8.bitPut (unused1 appliedDamageAttribute)+    <> U8.bitPut (unused2 appliedDamageAttribute)++bitGet :: BitGet.BitGet Boost+bitGet = BitGet.label "Boost" $ do+  grantCount <- BitGet.label "grantCount" U8.bitGet+  boostAmount <- BitGet.label "boostAmount" U8.bitGet+  unused1 <- BitGet.label "unused1" U8.bitGet+  unused2 <- BitGet.label "unused2" U8.bitGet+  pure Boost {grantCount, boostAmount, unused1, unused2}
+ src/lib/Rattletrap/Type/Attribute/Byte.hs view
@@ -0,0 +1,29 @@+module Rattletrap.Type.Attribute.Byte where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++newtype Byte = Byte+  { value :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON Byte where+  parseJSON = fmap Byte . Json.parseJSON++instance Json.ToJSON Byte where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-byte" $ Schema.ref U8.schema++bitPut :: Byte -> BitPut.BitPut+bitPut byteAttribute = U8.bitPut (value byteAttribute)++bitGet :: BitGet.BitGet Byte+bitGet = BitGet.label "Byte" $ do+  value <- BitGet.label "value" U8.bitGet+  pure Byte {value}
+ src/lib/Rattletrap/Type/Attribute/CamSettings.hs view
@@ -0,0 +1,99 @@+module Rattletrap.Type.Attribute.CamSettings where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data CamSettings = CamSettings+  { fov :: F32.F32,+    height :: F32.F32,+    angle :: F32.F32,+    distance :: F32.F32,+    stiffness :: F32.F32,+    swivelSpeed :: F32.F32,+    transitionSpeed :: Maybe F32.F32+  }+  deriving (Eq, Show)++instance Json.FromJSON CamSettings where+  parseJSON = Json.withObject "CamSettings" $ \object -> do+    fov <- Json.required object "fov"+    height <- Json.required object "height"+    angle <- Json.required object "angle"+    distance <- Json.required object "distance"+    stiffness <- Json.required object "stiffness"+    swivelSpeed <- Json.required object "swivel_speed"+    transitionSpeed <- Json.optional object "transition_speed"+    pure+      CamSettings+        { fov,+          height,+          angle,+          distance,+          stiffness,+          swivelSpeed,+          transitionSpeed+        }++instance Json.ToJSON CamSettings where+  toJSON x =+    Json.object+      [ Json.pair "fov" $ fov x,+        Json.pair "height" $ height x,+        Json.pair "angle" $ angle x,+        Json.pair "distance" $ distance x,+        Json.pair "stiffness" $ stiffness x,+        Json.pair "swivel_speed" $ swivelSpeed x,+        Json.pair "transition_speed" $ transitionSpeed x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-cam-settings" $+    Schema.object+      [ (Json.pair "fov" $ Schema.ref F32.schema, True),+        (Json.pair "height" $ Schema.ref F32.schema, True),+        (Json.pair "angle" $ Schema.ref F32.schema, True),+        (Json.pair "distance" $ Schema.ref F32.schema, True),+        (Json.pair "stiffness" $ Schema.ref F32.schema, True),+        (Json.pair "swivel_speed" $ Schema.ref F32.schema, True),+        ( Json.pair "transition_speed" . Schema.json $ Schema.maybe F32.schema,+          False+        )+      ]++bitPut :: CamSettings -> BitPut.BitPut+bitPut camSettingsAttribute =+  F32.bitPut (fov camSettingsAttribute)+    <> F32.bitPut (height camSettingsAttribute)+    <> F32.bitPut (angle camSettingsAttribute)+    <> F32.bitPut (distance camSettingsAttribute)+    <> F32.bitPut (stiffness camSettingsAttribute)+    <> F32.bitPut (swivelSpeed camSettingsAttribute)+    <> foldMap F32.bitPut (transitionSpeed camSettingsAttribute)++bitGet :: Version.Version -> BitGet.BitGet CamSettings+bitGet version = BitGet.label "CamSettings" $ do+  fov <- BitGet.label "fov" F32.bitGet+  height <- BitGet.label "height" F32.bitGet+  angle <- BitGet.label "angle" F32.bitGet+  distance <- BitGet.label "distance" F32.bitGet+  stiffness <- BitGet.label "stiffness" F32.bitGet+  swivelSpeed <- BitGet.label "swivelSpeed" F32.bitGet+  transitionSpeed <-+    BitGet.label "transitionSpeed" $+      Monad.whenMaybe (Version.atLeast 868 20 0 version) F32.bitGet+  pure+    CamSettings+      { fov,+        height,+        angle,+        distance,+        stiffness,+        swivelSpeed,+        transitionSpeed+      }
+ src/lib/Rattletrap/Type/Attribute/ClubColors.hs view
@@ -0,0 +1,57 @@+module Rattletrap.Type.Attribute.ClubColors where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data ClubColors = ClubColors+  { blueFlag :: Bool,+    blueColor :: U8.U8,+    orangeFlag :: Bool,+    orangeColor :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON ClubColors where+  parseJSON = Json.withObject "ClubColors" $ \object -> do+    blueFlag <- Json.required object "blue_flag"+    blueColor <- Json.required object "blue_color"+    orangeFlag <- Json.required object "orange_flag"+    orangeColor <- Json.required object "orange_color"+    pure ClubColors {blueFlag, blueColor, orangeFlag, orangeColor}++instance Json.ToJSON ClubColors where+  toJSON x =+    Json.object+      [ Json.pair "blue_flag" $ blueFlag x,+        Json.pair "blue_color" $ blueColor x,+        Json.pair "orange_flag" $ orangeFlag x,+        Json.pair "orange_color" $ orangeColor x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-club-colors" $+    Schema.object+      [ (Json.pair "blue_flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "blue_color" $ Schema.ref U8.schema, True),+        (Json.pair "orange_flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "orange_color" $ Schema.ref U8.schema, True)+      ]++bitPut :: ClubColors -> BitPut.BitPut+bitPut clubColorsAttribute =+  BitPut.bool (blueFlag clubColorsAttribute)+    <> U8.bitPut (blueColor clubColorsAttribute)+    <> BitPut.bool (orangeFlag clubColorsAttribute)+    <> U8.bitPut (orangeColor clubColorsAttribute)++bitGet :: BitGet.BitGet ClubColors+bitGet = BitGet.label "ClubColors" $ do+  blueFlag <- BitGet.label "blueFlag" BitGet.bool+  blueColor <- BitGet.label "blueColor" U8.bitGet+  orangeFlag <- BitGet.label "orangeFlag" BitGet.bool+  orangeColor <- BitGet.label "orangeColor" U8.bitGet+  pure ClubColors {blueFlag, blueColor, orangeFlag, orangeColor}
+ src/lib/Rattletrap/Type/Attribute/CustomDemolish.hs view
@@ -0,0 +1,58 @@+module Rattletrap.Type.Attribute.CustomDemolish where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.Demolish as Demolish+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data CustomDemolish = CustomDemolish+  { flag :: Bool,+    id :: I32.I32,+    demolish :: Demolish.Demolish+  }+  deriving (Eq, Show)++instance Json.FromJSON CustomDemolish where+  parseJSON = Json.withObject "CustomDemolish" $ \object -> do+    flag <- Json.required object "flag"+    id_ <- Json.required object "id"+    demolish <- Json.required object "demolish"+    pure CustomDemolish {flag, Rattletrap.Type.Attribute.CustomDemolish.id = id_, demolish}++instance Json.ToJSON CustomDemolish where+  toJSON x =+    Json.object+      [ Json.pair "flag" $ flag x,+        Json.pair "id" $ Rattletrap.Type.Attribute.CustomDemolish.id x,+        Json.pair "demolish" $ demolish x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-custom-demolish" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "id" $ Schema.ref I32.schema, True),+        (Json.pair "demolish" $ Schema.ref Demolish.schema, True)+      ]++bitPut :: CustomDemolish -> BitPut.BitPut+bitPut x =+  BitPut.bool (flag x)+    <> I32.bitPut (Rattletrap.Type.Attribute.CustomDemolish.id x)+    <> Demolish.bitPut (demolish x)++bitGet :: Version.Version -> BitGet.BitGet CustomDemolish+bitGet version = BitGet.label "CustomDemolish" $ do+  flag <- BitGet.label "flag" BitGet.bool+  id_ <- BitGet.label "id" I32.bitGet+  demolish <- BitGet.label "demolish" $ Demolish.bitGet version+  pure+    CustomDemolish+      { flag,+        Rattletrap.Type.Attribute.CustomDemolish.id = id_,+        demolish+      }
+ src/lib/Rattletrap/Type/Attribute/CustomDemolishExtended.hs view
@@ -0,0 +1,71 @@+module Rattletrap.Type.Attribute.CustomDemolishExtended where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.CustomDemolish as CustomDemolish+import qualified Rattletrap.Type.ObjectTarget as ObjectTarget+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data CustomDemolishExtended = CustomDemolishExtended+  { attackerPri :: ObjectTarget.ObjectTarget,+    selfDemoFx :: ObjectTarget.ObjectTarget,+    selfDemolish :: Bool,+    customDemolish :: CustomDemolish.CustomDemolish+  }+  deriving (Eq, Show)++instance Json.FromJSON CustomDemolishExtended where+  parseJSON = Json.withObject "CustomDemolishExtended" $ \object -> do+    attackerPri <- Json.required object "attacker_pri"+    selfDemoFx <- Json.required object "self_demo_fx"+    selfDemolish <- Json.required object "self_demolish"+    customDemolish <- Json.required object "custom_demolish"+    pure+      CustomDemolishExtended+        { attackerPri,+          selfDemoFx,+          selfDemolish,+          customDemolish+        }++instance Json.ToJSON CustomDemolishExtended where+  toJSON x =+    Json.object+      [ Json.pair "attacker_pri" $ attackerPri x,+        Json.pair "self_demo_fx" $ selfDemoFx x,+        Json.pair "self_demolish" $ selfDemolish x,+        Json.pair "custom_demolish" $ customDemolish x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-custom-demolish-extended" $+    Schema.object+      [ (Json.pair "attacker_pri" $ Schema.ref ObjectTarget.schema, True),+        (Json.pair "self_demo_fx" $ Schema.ref ObjectTarget.schema, True),+        (Json.pair "self_demolish" $ Schema.ref Schema.boolean, True),+        (Json.pair "custom_demolish" $ Schema.ref CustomDemolish.schema, True)+      ]++bitPut :: CustomDemolishExtended -> BitPut.BitPut+bitPut demolishAttribute =+  ObjectTarget.bitPut (attackerPri demolishAttribute)+    <> ObjectTarget.bitPut (selfDemoFx demolishAttribute)+    <> BitPut.bool (selfDemolish demolishAttribute)+    <> CustomDemolish.bitPut (customDemolish demolishAttribute)++bitGet :: Version.Version -> BitGet.BitGet CustomDemolishExtended+bitGet version = BitGet.label "CustomDemolishExtended" $ do+  attackerPri <- BitGet.label "attackerPri" ObjectTarget.bitGet+  selfDemoFx <- BitGet.label "selfDemoFx" ObjectTarget.bitGet+  selfDemolish <- BitGet.label "selfDemolish" BitGet.bool+  customDemolish <- BitGet.label "customDemolish" $ CustomDemolish.bitGet version+  pure+    CustomDemolishExtended+      { attackerPri,+        selfDemoFx,+        selfDemolish,+        customDemolish+      }
+ src/lib/Rattletrap/Type/Attribute/DamageState.hs view
@@ -0,0 +1,88 @@+module Rattletrap.Type.Attribute.DamageState where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data DamageState = DamageState+  { unknown1 :: U8.U8,+    unknown2 :: Bool,+    unknown3 :: I32.I32,+    unknown4 :: Vector.Vector,+    unknown5 :: Bool,+    unknown6 :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON DamageState where+  parseJSON = Json.withObject "DamageState" $ \object -> do+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.required object "unknown2"+    unknown3 <- Json.required object "unknown3"+    unknown4 <- Json.required object "unknown4"+    unknown5 <- Json.required object "unknown5"+    unknown6 <- Json.required object "unknown6"+    pure+      DamageState+        { unknown1,+          unknown2,+          unknown3,+          unknown4,+          unknown5,+          unknown6+        }++instance Json.ToJSON DamageState where+  toJSON x =+    Json.object+      [ Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x,+        Json.pair "unknown3" $ unknown3 x,+        Json.pair "unknown4" $ unknown4 x,+        Json.pair "unknown5" $ unknown5 x,+        Json.pair "unknown6" $ unknown6 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-damage-state" $+    Schema.object+      [ (Json.pair "unknown1" $ Schema.ref U8.schema, True),+        (Json.pair "unknown2" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown3" $ Schema.ref I32.schema, True),+        (Json.pair "unknown4" $ Schema.ref Vector.schema, True),+        (Json.pair "unknown5" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown6" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: DamageState -> BitPut.BitPut+bitPut damageStateAttribute =+  U8.bitPut (unknown1 damageStateAttribute)+    <> BitPut.bool (unknown2 damageStateAttribute)+    <> I32.bitPut (unknown3 damageStateAttribute)+    <> Vector.bitPut (unknown4 damageStateAttribute)+    <> BitPut.bool (unknown5 damageStateAttribute)+    <> BitPut.bool (unknown6 damageStateAttribute)++bitGet :: Version.Version -> BitGet.BitGet DamageState+bitGet version = BitGet.label "DamageState" $ do+  unknown1 <- BitGet.label "unknown1" U8.bitGet+  unknown2 <- BitGet.label "unknown2" BitGet.bool+  unknown3 <- BitGet.label "unknown3" I32.bitGet+  unknown4 <- BitGet.label "unknown4" $ Vector.bitGet version+  unknown5 <- BitGet.label "unknown5" BitGet.bool+  unknown6 <- BitGet.label "unknown6" BitGet.bool+  pure+    DamageState+      { unknown1,+        unknown2,+        unknown3,+        unknown4,+        unknown5,+        unknown6+      }
+ src/lib/Rattletrap/Type/Attribute/Demolish.hs view
@@ -0,0 +1,87 @@+module Rattletrap.Type.Attribute.Demolish where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Demolish = Demolish+  { attackerFlag :: Bool,+    attackerActorId :: U32.U32,+    victimFlag :: Bool,+    victimActorId :: U32.U32,+    attackerVelocity :: Vector.Vector,+    victimVelocity :: Vector.Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON Demolish where+  parseJSON = Json.withObject "Demolish" $ \object -> do+    attackerFlag <- Json.required object "attacker_flag"+    attackerActorId <- Json.required object "attacker_actor_id"+    victimFlag <- Json.required object "victim_flag"+    victimActorId <- Json.required object "victim_actor_id"+    attackerVelocity <- Json.required object "attacker_velocity"+    victimVelocity <- Json.required object "victim_velocity"+    pure+      Demolish+        { attackerFlag,+          attackerActorId,+          victimFlag,+          victimActorId,+          attackerVelocity,+          victimVelocity+        }++instance Json.ToJSON Demolish where+  toJSON x =+    Json.object+      [ Json.pair "attacker_flag" $ attackerFlag x,+        Json.pair "attacker_actor_id" $ attackerActorId x,+        Json.pair "victim_flag" $ victimFlag x,+        Json.pair "victim_actor_id" $ victimActorId x,+        Json.pair "attacker_velocity" $ attackerVelocity x,+        Json.pair "victim_velocity" $ victimVelocity x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-demolish" $+    Schema.object+      [ (Json.pair "attacker_flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "attacker_actor_id" $ Schema.ref U32.schema, True),+        (Json.pair "victim_flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "victim_actor_id" $ Schema.ref U32.schema, True),+        (Json.pair "attacker_velocity" $ Schema.ref Vector.schema, True),+        (Json.pair "victim_velocity" $ Schema.ref Vector.schema, True)+      ]++bitPut :: Demolish -> BitPut.BitPut+bitPut demolishAttribute =+  BitPut.bool (attackerFlag demolishAttribute)+    <> U32.bitPut (attackerActorId demolishAttribute)+    <> BitPut.bool (victimFlag demolishAttribute)+    <> U32.bitPut (victimActorId demolishAttribute)+    <> Vector.bitPut (attackerVelocity demolishAttribute)+    <> Vector.bitPut (victimVelocity demolishAttribute)++bitGet :: Version.Version -> BitGet.BitGet Demolish+bitGet version = BitGet.label "Demolish" $ do+  attackerFlag <- BitGet.label "attackerFlag" BitGet.bool+  attackerActorId <- BitGet.label "attackerActorId" U32.bitGet+  victimFlag <- BitGet.label "victimFlag" BitGet.bool+  victimActorId <- BitGet.label "victimActorId" U32.bitGet+  attackerVelocity <- BitGet.label "attackerVelocity" $ Vector.bitGet version+  victimVelocity <- BitGet.label "victimVelocity" $ Vector.bitGet version+  pure+    Demolish+      { attackerFlag,+        attackerActorId,+        victimFlag,+        victimActorId,+        attackerVelocity,+        victimVelocity+      }
+ src/lib/Rattletrap/Type/Attribute/Enum.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.Enum where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Enum)++newtype Enum = Enum+  { value :: Word.Word16+  }+  deriving (Eq, Show)++instance Json.FromJSON Enum where+  parseJSON = fmap Enum . Json.parseJSON++instance Json.ToJSON Enum where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-enum" $ Schema.ref Schema.integer++bitPut :: Enum -> BitPut.BitPut+bitPut enumAttribute = BitPut.bits 11 (value enumAttribute)++bitGet :: BitGet.BitGet Enum+bitGet = BitGet.label "Enum" $ do+  value <- BitGet.label "value" $ BitGet.bits 11+  pure Enum {value}
+ src/lib/Rattletrap/Type/Attribute/Explosion.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Attribute.Explosion where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Explosion = Explosion+  { flag :: Bool,+    actorId :: I32.I32,+    location :: Vector.Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON Explosion where+  parseJSON = Json.withObject "Explosion" $ \object -> do+    flag <- Json.required object "flag"+    actorId <- Json.required object "actor_id"+    location <- Json.required object "location"+    pure Explosion {flag, actorId, location}++instance Json.ToJSON Explosion where+  toJSON x =+    Json.object+      [ Json.pair "flag" $ flag x,+        Json.pair "actor_id" $ actorId x,+        Json.pair "location" $ location x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-explosion" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "actor_id" $ Schema.ref I32.schema, True),+        (Json.pair "location" $ Schema.ref Vector.schema, True)+      ]++bitPut :: Explosion -> BitPut.BitPut+bitPut explosionAttribute =+  BitPut.bool (flag explosionAttribute)+    <> I32.bitPut (actorId explosionAttribute)+    <> Vector.bitPut (location explosionAttribute)++bitGet :: Version.Version -> BitGet.BitGet Explosion+bitGet version = BitGet.label "Explosion" $ do+  flag <- BitGet.label "flag" BitGet.bool+  actorId <- BitGet.label "actorId" I32.bitGet+  location <- BitGet.label "location" $ Vector.bitGet version+  pure Explosion {flag, actorId, location}
+ src/lib/Rattletrap/Type/Attribute/ExtendedExplosion.hs view
@@ -0,0 +1,43 @@+module Rattletrap.Type.Attribute.ExtendedExplosion where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.Explosion as Explosion+import qualified Rattletrap.Type.Attribute.FlaggedInt as FlaggedInt+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data ExtendedExplosion = ExtendedExplosion+  { explosion :: Explosion.Explosion,+    unknown :: FlaggedInt.FlaggedInt+  }+  deriving (Eq, Show)++instance Json.FromJSON ExtendedExplosion where+  parseJSON = Json.withObject "ExtendedExplosion" $ \object -> do+    explosion <- Json.required object "explosion"+    unknown <- Json.required object "unknown"+    pure ExtendedExplosion {explosion, unknown}++instance Json.ToJSON ExtendedExplosion where+  toJSON x =+    Json.object+      [Json.pair "explosion" $ explosion x, Json.pair "unknown" $ unknown x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-extended-explosion" $+    Schema.object+      [ (Json.pair "explosion" $ Schema.ref Explosion.schema, True),+        (Json.pair "unknown" $ Schema.ref FlaggedInt.schema, True)+      ]++bitPut :: ExtendedExplosion -> BitPut.BitPut+bitPut x = Explosion.bitPut (explosion x) <> FlaggedInt.bitPut (unknown x)++bitGet :: Version.Version -> BitGet.BitGet ExtendedExplosion+bitGet version = BitGet.label "ExtendedExplosion" $ do+  explosion <- BitGet.label "explosion" $ Explosion.bitGet version+  unknown <- BitGet.label "unknown" FlaggedInt.bitGet+  pure ExtendedExplosion {explosion, unknown}
+ src/lib/Rattletrap/Type/Attribute/FlaggedByte.hs view
@@ -0,0 +1,42 @@+module Rattletrap.Type.Attribute.FlaggedByte where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data FlaggedByte = FlaggedByte+  { flag :: Bool,+    byte :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON FlaggedByte where+  parseJSON = Json.withObject "FlaggedByte" $ \object -> do+    flag <- Json.required object "flag"+    byte <- Json.required object "byte"+    pure FlaggedByte {flag, byte}++instance Json.ToJSON FlaggedByte where+  toJSON x =+    Json.object [Json.pair "flag" $ flag x, Json.pair "byte" $ byte x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-flagged-byte" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "byte" $ Schema.ref U8.schema, True)+      ]++bitPut :: FlaggedByte -> BitPut.BitPut+bitPut flaggedByteAttribute =+  BitPut.bool (flag flaggedByteAttribute)+    <> U8.bitPut (byte flaggedByteAttribute)++bitGet :: BitGet.BitGet FlaggedByte+bitGet = BitGet.label "FlaggedByte" $ do+  flag <- BitGet.label "flag" BitGet.bool+  byte <- BitGet.label "byte" U8.bitGet+  pure FlaggedByte {flag, byte}
+ src/lib/Rattletrap/Type/Attribute/FlaggedInt.hs view
@@ -0,0 +1,41 @@+module Rattletrap.Type.Attribute.FlaggedInt where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Json as Json++data FlaggedInt = FlaggedInt+  { flag :: Bool,+    int :: I32.I32+  }+  deriving (Eq, Show)++instance Json.FromJSON FlaggedInt where+  parseJSON = Json.withObject "FlaggedInt" $ \object -> do+    flag <- Json.required object "flag"+    int <- Json.required object "int"+    pure FlaggedInt {flag, int}++instance Json.ToJSON FlaggedInt where+  toJSON x = Json.object [Json.pair "flag" $ flag x, Json.pair "int" $ int x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-flagged-int" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "int" $ Schema.ref I32.schema, True)+      ]++bitPut :: FlaggedInt -> BitPut.BitPut+bitPut flaggedIntAttribute =+  BitPut.bool (flag flaggedIntAttribute)+    <> I32.bitPut (int flaggedIntAttribute)++bitGet :: BitGet.BitGet FlaggedInt+bitGet = BitGet.label "FlaggedInt" $ do+  flag <- BitGet.label "flag" BitGet.bool+  int <- BitGet.label "int" I32.bitGet+  pure FlaggedInt {flag, int}
+ src/lib/Rattletrap/Type/Attribute/Float.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.Float where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Float)++newtype Float = Float+  { value :: F32.F32+  }+  deriving (Eq, Show)++instance Json.FromJSON Float where+  parseJSON = fmap Float . Json.parseJSON++instance Json.ToJSON Float where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-float" $ Schema.ref F32.schema++bitPut :: Float -> BitPut.BitPut+bitPut floatAttribute = F32.bitPut (value floatAttribute)++bitGet :: BitGet.BitGet Float+bitGet = BitGet.label "Float" $ do+  value <- BitGet.label "value" F32.bitGet+  pure Float {value}
+ src/lib/Rattletrap/Type/Attribute/GameMode.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.Attribute.GameMode where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data GameMode = GameMode+  { -- | This field is guaranteed to be small. In other words, it won't overflow.+    -- It's stored as a regular 'Int' rather than something more precise like an+    -- 'Int8' because it just gets passed to functions that expect 'Int's.+    -- There's no reason to do a bunch of conversions.+    numBits :: Int,+    word :: Word.Word8+  }+  deriving (Eq, Show)++instance Json.FromJSON GameMode where+  parseJSON = Json.withObject "GameMode" $ \object -> do+    numBits <- Json.required object "num_bits"+    word <- Json.required object "word"+    pure GameMode {numBits, word}++instance Json.ToJSON GameMode where+  toJSON x =+    Json.object [Json.pair "num_bits" $ numBits x, Json.pair "word" $ word x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-game-mode" $+    Schema.object+      [ (Json.pair "num_bits" $ Schema.ref Schema.integer, True),+        (Json.pair "word" $ Schema.ref Schema.integer, True)+      ]++bitPut :: GameMode -> BitPut.BitPut+bitPut gameModeAttribute = do+  BitPut.bits (numBits gameModeAttribute) (word gameModeAttribute)++bitGet :: Version.Version -> BitGet.BitGet GameMode+bitGet version = BitGet.label "GameMode" $ do+  let numBits = if Version.atLeast 868 12 0 version then 8 else 2 :: Int+  word <- BitGet.label "word" $ BitGet.bits numBits+  pure GameMode {numBits, word}
+ src/lib/Rattletrap/Type/Attribute/GameServer.hs view
@@ -0,0 +1,43 @@+module Rattletrap.Type.Attribute.GameServer where++import qualified Data.Foldable as Foldable+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.QWord as QWord+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++data GameServer+  = Old QWord.QWord+  | New Str.Str+  deriving (Eq, Show)++instance Json.FromJSON GameServer where+  parseJSON = Json.withObject "GameServer" $ \x ->+    Foldable.asum+      [fmap Old $ Json.required x "old", fmap New $ Json.required x "new"]++instance Json.ToJSON GameServer where+  toJSON x = case x of+    Old y -> Json.object [Json.pair "old" $ Json.toJSON y]+    New y -> Json.object [Json.pair "new" $ Json.toJSON y]++schema :: Schema.Schema+schema =+  Schema.named "attribute-game-server" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k v, True)])+      [("old", Schema.ref QWord.schema), ("new", Schema.ref Str.schema)]++bitPut :: GameServer -> BitPut.BitPut+bitPut x = case x of+  Old y -> QWord.bitPut y+  New y -> Str.bitPut y++bitGet :: Maybe Str.Str -> BitGet.BitGet GameServer+bitGet buildVersion =+  BitGet.label "GameServer" $+    if buildVersion >= Just (Str.fromString "221120.42953.406184")+      then BitGet.label "New" $ fmap New Str.bitGet+      else BitGet.label "Old" $ fmap Old QWord.bitGet
+ src/lib/Rattletrap/Type/Attribute/Int.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.Int where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Int)++newtype Int = Int+  { value :: I32.I32+  }+  deriving (Eq, Show)++instance Json.FromJSON Int where+  parseJSON = fmap Int . Json.parseJSON++instance Json.ToJSON Int where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-int" $ Schema.ref I32.schema++bitPut :: Int -> BitPut.BitPut+bitPut intAttribute = I32.bitPut (value intAttribute)++bitGet :: BitGet.BitGet Int+bitGet = BitGet.label "Int" $ do+  value <- BitGet.label "value" I32.bitGet+  pure Int {value}
+ src/lib/Rattletrap/Type/Attribute/Int64.hs view
@@ -0,0 +1,29 @@+module Rattletrap.Type.Attribute.Int64 where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I64 as I64+import qualified Rattletrap.Utility.Json as Json++newtype Int64 = Int64+  { value :: I64.I64+  }+  deriving (Eq, Show)++instance Json.FromJSON Int64 where+  parseJSON = fmap Int64 . Json.parseJSON++instance Json.ToJSON Int64 where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-int64" $ Schema.ref I64.schema++putInt64Attribute :: Int64 -> BitPut.BitPut+putInt64Attribute int64Attribute = I64.bitPut (value int64Attribute)++bitGet :: BitGet.BitGet Int64+bitGet = BitGet.label "Int64" $ do+  value <- BitGet.label "value" I64.bitGet+  pure Int64 {value}
+ src/lib/Rattletrap/Type/Attribute/Loadout.hs view
@@ -0,0 +1,194 @@+module Rattletrap.Type.Attribute.Loadout where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Loadout = Loadout+  { version :: U8.U8,+    body :: U32.U32,+    decal :: U32.U32,+    wheels :: U32.U32,+    -- | Now known as "rocket boost".+    rocketTrail :: U32.U32,+    antenna :: U32.U32,+    topper :: U32.U32,+    unknown1 :: U32.U32,+    unknown2 :: Maybe U32.U32,+    engineAudio :: Maybe U32.U32,+    trail :: Maybe U32.U32,+    goalExplosion :: Maybe U32.U32,+    banner :: Maybe U32.U32,+    unknown3 :: Maybe U32.U32,+    unknown4 :: Maybe U32.U32,+    unknown5 :: Maybe U32.U32,+    unknown6 :: Maybe U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON Loadout where+  parseJSON = Json.withObject "Loadout" $ \object -> do+    version <- Json.required object "version"+    body <- Json.required object "body"+    decal <- Json.required object "decal"+    wheels <- Json.required object "wheels"+    rocketTrail <- Json.required object "rocket_trail"+    antenna <- Json.required object "antenna"+    topper <- Json.required object "topper"+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.optional object "unknown2"+    engineAudio <- Json.optional object "engine_audio"+    trail <- Json.optional object "trail"+    goalExplosion <- Json.optional object "goal_explosion"+    banner <- Json.optional object "banner"+    unknown3 <- Json.optional object "unknown3"+    unknown4 <- Json.optional object "unknown4"+    unknown5 <- Json.optional object "unknown5"+    unknown6 <- Json.optional object "unknown6"+    pure+      Loadout+        { version,+          body,+          decal,+          wheels,+          rocketTrail,+          antenna,+          topper,+          unknown1,+          unknown2,+          engineAudio,+          trail,+          goalExplosion,+          banner,+          unknown3,+          unknown4,+          unknown5,+          unknown6+        }++instance Json.ToJSON Loadout where+  toJSON x =+    Json.object+      [ Json.pair "version" $ version x,+        Json.pair "body" $ body x,+        Json.pair "decal" $ decal x,+        Json.pair "wheels" $ wheels x,+        Json.pair "rocket_trail" $ rocketTrail x,+        Json.pair "antenna" $ antenna x,+        Json.pair "topper" $ topper x,+        Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x,+        Json.pair "engine_audio" $ engineAudio x,+        Json.pair "trail" $ trail x,+        Json.pair "goal_explosion" $ goalExplosion x,+        Json.pair "banner" $ banner x,+        Json.pair "unknown3" $ unknown3 x,+        Json.pair "unknown4" $ unknown4 x,+        Json.pair "unknown5" $ unknown5 x,+        Json.pair "unknown6" $ unknown6 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-loadout" $+    Schema.object+      [ (Json.pair "version" $ Schema.ref U8.schema, True),+        (Json.pair "body" $ Schema.ref U32.schema, True),+        (Json.pair "decal" $ Schema.ref U32.schema, True),+        (Json.pair "wheels" $ Schema.ref U32.schema, True),+        (Json.pair "rocket_trail" $ Schema.ref U32.schema, True),+        (Json.pair "antenna" $ Schema.ref U32.schema, True),+        (Json.pair "topper" $ Schema.ref U32.schema, True),+        (Json.pair "unknown1" $ Schema.ref U32.schema, True),+        (Json.pair "unknown2" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "engine_audio" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "trail" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "goal_explosion" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "banner" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "unknown3" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "unknown4" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "unknown5" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "unknown6" . Schema.json $ Schema.maybe U32.schema, False)+      ]++bitPut :: Loadout -> BitPut.BitPut+bitPut loadoutAttribute =+  U8.bitPut (version loadoutAttribute)+    <> U32.bitPut (body loadoutAttribute)+    <> U32.bitPut (decal loadoutAttribute)+    <> U32.bitPut (wheels loadoutAttribute)+    <> U32.bitPut (rocketTrail loadoutAttribute)+    <> U32.bitPut (antenna loadoutAttribute)+    <> U32.bitPut (topper loadoutAttribute)+    <> U32.bitPut (unknown1 loadoutAttribute)+    <> foldMap U32.bitPut (unknown2 loadoutAttribute)+    <> foldMap U32.bitPut (engineAudio loadoutAttribute)+    <> foldMap U32.bitPut (trail loadoutAttribute)+    <> foldMap U32.bitPut (goalExplosion loadoutAttribute)+    <> foldMap U32.bitPut (banner loadoutAttribute)+    <> foldMap U32.bitPut (unknown3 loadoutAttribute)+    <> foldMap U32.bitPut (unknown4 loadoutAttribute)+    <> foldMap U32.bitPut (unknown5 loadoutAttribute)+    <> foldMap U32.bitPut (unknown6 loadoutAttribute)++bitGet :: BitGet.BitGet Loadout+bitGet = BitGet.label "Loadout" $ do+  version <- BitGet.label "version" U8.bitGet+  body <- BitGet.label "body" U32.bitGet+  decal <- BitGet.label "decal" U32.bitGet+  wheels <- BitGet.label "wheels" U32.bitGet+  rocketTrail <- BitGet.label "rocketTrail" U32.bitGet+  antenna <- BitGet.label "antenna" U32.bitGet+  topper <- BitGet.label "topper" U32.bitGet+  unknown1 <- BitGet.label "unknown1" U32.bitGet+  unknown2 <-+    BitGet.label "unknown2" $+      Monad.whenMaybe (U8.toWord8 version >= 11) U32.bitGet+  engineAudio <-+    BitGet.label "engineAudio" $+      Monad.whenMaybe (U8.toWord8 version >= 16) U32.bitGet+  trail <-+    BitGet.label "trail" $+      Monad.whenMaybe (U8.toWord8 version >= 16) U32.bitGet+  goalExplosion <-+    BitGet.label "goalExplosion" $+      Monad.whenMaybe (U8.toWord8 version >= 16) U32.bitGet+  banner <-+    BitGet.label "banner" $+      Monad.whenMaybe (U8.toWord8 version >= 17) U32.bitGet+  unknown3 <-+    BitGet.label "unknown3" $+      Monad.whenMaybe (U8.toWord8 version >= 19) U32.bitGet+  unknown4 <-+    BitGet.label "unknown4" $+      Monad.whenMaybe (U8.toWord8 version >= 22) U32.bitGet+  unknown5 <-+    BitGet.label "unknown5" $+      Monad.whenMaybe (U8.toWord8 version >= 22) U32.bitGet+  unknown6 <-+    BitGet.label "unknown6" $+      Monad.whenMaybe (U8.toWord8 version >= 22) U32.bitGet+  pure+    Loadout+      { version,+        body,+        decal,+        wheels,+        rocketTrail,+        antenna,+        topper,+        unknown1,+        unknown2,+        engineAudio,+        trail,+        goalExplosion,+        banner,+        unknown3,+        unknown4,+        unknown5,+        unknown6+      }
+ src/lib/Rattletrap/Type/Attribute/LoadoutOnline.hs view
@@ -0,0 +1,47 @@+module Rattletrap.Type.Attribute.LoadoutOnline where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.Product as Product+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++newtype LoadoutOnline = LoadoutOnline+  { value :: RList.List (RList.List Product.Product)+  }+  deriving (Eq, Show)++instance Json.FromJSON LoadoutOnline where+  parseJSON = fmap LoadoutOnline . Json.parseJSON++instance Json.ToJSON LoadoutOnline where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema =+  Schema.named "attribute-loadout-online"+    . Schema.json+    . RList.schema+    $ RList.schema Product.schema++bitPut :: LoadoutOnline -> BitPut.BitPut+bitPut loadoutAttribute =+  let attributes = RList.toList $ value loadoutAttribute+   in (U8.bitPut . U8.fromWord8 . fromIntegral $ length attributes)+        <> foldMap Product.putProductAttributes attributes++bitGet ::+  Version.Version -> Map.Map U32.U32 Str.Str -> BitGet.BitGet LoadoutOnline+bitGet version objectMap = BitGet.label "LoadoutOnline" $ do+  size <- BitGet.label "size" U8.bitGet+  value <-+    BitGet.label "value"+      . RList.replicateM (fromIntegral $ U8.toWord8 size)+      $ Product.decodeProductAttributesBits version objectMap+  pure LoadoutOnline {value}
+ src/lib/Rattletrap/Type/Attribute/Loadouts.hs view
@@ -0,0 +1,42 @@+module Rattletrap.Type.Attribute.Loadouts where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.Loadout as Loadout+import qualified Rattletrap.Utility.Json as Json++data Loadouts = Loadouts+  { blue :: Loadout.Loadout,+    orange :: Loadout.Loadout+  }+  deriving (Eq, Show)++instance Json.FromJSON Loadouts where+  parseJSON = Json.withObject "Loadouts" $ \object -> do+    blue <- Json.required object "blue"+    orange <- Json.required object "orange"+    pure Loadouts {blue, orange}++instance Json.ToJSON Loadouts where+  toJSON x =+    Json.object [Json.pair "blue" $ blue x, Json.pair "orange" $ orange x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-loadouts" $+    Schema.object+      [ (Json.pair "blue" $ Schema.ref Loadout.schema, True),+        (Json.pair "orange" $ Schema.ref Loadout.schema, True)+      ]++bitPut :: Loadouts -> BitPut.BitPut+bitPut loadoutsAttribute =+  Loadout.bitPut (blue loadoutsAttribute)+    <> Loadout.bitPut (orange loadoutsAttribute)++bitGet :: BitGet.BitGet Loadouts+bitGet = BitGet.label "Loadouts" $ do+  blue <- BitGet.label "blue" Loadout.bitGet+  orange <- BitGet.label "orange" Loadout.bitGet+  pure Loadouts {blue, orange}
+ src/lib/Rattletrap/Type/Attribute/LoadoutsOnline.hs view
@@ -0,0 +1,62 @@+module Rattletrap.Type.Attribute.LoadoutsOnline where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.LoadoutOnline as LoadoutOnline+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data LoadoutsOnline = LoadoutsOnline+  { blue :: LoadoutOnline.LoadoutOnline,+    orange :: LoadoutOnline.LoadoutOnline,+    unknown1 :: Bool,+    unknown2 :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON LoadoutsOnline where+  parseJSON = Json.withObject "LoadoutsOnline" $ \object -> do+    blue <- Json.required object "blue"+    orange <- Json.required object "orange"+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.required object "unknown2"+    pure LoadoutsOnline {blue, orange, unknown1, unknown2}++instance Json.ToJSON LoadoutsOnline where+  toJSON x =+    Json.object+      [ Json.pair "blue" $ blue x,+        Json.pair "orange" $ orange x,+        Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-loadouts-online" $+    Schema.object+      [ (Json.pair "blue" $ Schema.ref LoadoutOnline.schema, True),+        (Json.pair "orange" $ Schema.ref LoadoutOnline.schema, True),+        (Json.pair "unknown1" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown2" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: LoadoutsOnline -> BitPut.BitPut+bitPut loadoutsOnlineAttribute =+  LoadoutOnline.bitPut (blue loadoutsOnlineAttribute)+    <> LoadoutOnline.bitPut (orange loadoutsOnlineAttribute)+    <> BitPut.bool (unknown1 loadoutsOnlineAttribute)+    <> BitPut.bool (unknown2 loadoutsOnlineAttribute)++bitGet ::+  Version.Version -> Map.Map U32.U32 Str.Str -> BitGet.BitGet LoadoutsOnline+bitGet version objectMap = BitGet.label "LoadoutsOnline" $ do+  blue <- BitGet.label "blue" $ LoadoutOnline.bitGet version objectMap+  orange <- BitGet.label "orange" $ LoadoutOnline.bitGet version objectMap+  unknown1 <- BitGet.label "unknown1" BitGet.bool+  unknown2 <- BitGet.label "unknown2" BitGet.bool+  pure LoadoutsOnline {blue, orange, unknown1, unknown2}
+ src/lib/Rattletrap/Type/Attribute/Location.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.Location where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++newtype Location = Location+  { value :: Vector.Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON Location where+  parseJSON = fmap Location . Json.parseJSON++instance Json.ToJSON Location where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-location" $ Schema.ref Vector.schema++bitPut :: Location -> BitPut.BitPut+bitPut locationAttribute = Vector.bitPut (value locationAttribute)++bitGet :: Version.Version -> BitGet.BitGet Location+bitGet version = BitGet.label "Location" $ do+  value <- BitGet.label "value" $ Vector.bitGet version+  pure Location {value}
+ src/lib/Rattletrap/Type/Attribute/MusicStinger.hs view
@@ -0,0 +1,52 @@+module Rattletrap.Type.Attribute.MusicStinger where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data MusicStinger = MusicStinger+  { flag :: Bool,+    cue :: U32.U32,+    trigger :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON MusicStinger where+  parseJSON = Json.withObject "MusicStinger" $ \object -> do+    flag <- Json.required object "flag"+    cue <- Json.required object "cue"+    trigger <- Json.required object "trigger"+    pure MusicStinger {flag, cue, trigger}++instance Json.ToJSON MusicStinger where+  toJSON x =+    Json.object+      [ Json.pair "flag" $ flag x,+        Json.pair "cue" $ cue x,+        Json.pair "trigger" $ trigger x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-music-stinger" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "cue" $ Schema.ref U32.schema, True),+        (Json.pair "trigger" $ Schema.ref U8.schema, True)+      ]++bitPut :: MusicStinger -> BitPut.BitPut+bitPut musicStingerAttribute =+  BitPut.bool (flag musicStingerAttribute)+    <> U32.bitPut (cue musicStingerAttribute)+    <> U8.bitPut (trigger musicStingerAttribute)++bitGet :: BitGet.BitGet MusicStinger+bitGet = BitGet.label "MusicStinger" $ do+  flag <- BitGet.label "flag" BitGet.bool+  cue <- BitGet.label "cue" U32.bitGet+  trigger <- BitGet.label "trigger" U8.bitGet+  pure MusicStinger {flag, cue, trigger}
+ src/lib/Rattletrap/Type/Attribute/PartyLeader.hs view
@@ -0,0 +1,70 @@+module Rattletrap.Type.Attribute.PartyLeader where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.RemoteId as RemoteId+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data PartyLeader = PartyLeader+  { systemId :: U8.U8,+    remoteId :: Maybe RemoteId.RemoteId,+    localId :: Maybe U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON PartyLeader where+  parseJSON = Json.withObject "PartyLeader" $ \object -> do+    systemId <- Json.required object "system_id"+    maybeId <- Json.optional object "id"+    pure+      PartyLeader+        { systemId,+          remoteId = fmap fst maybeId,+          localId = fmap snd maybeId+        }++instance Json.ToJSON PartyLeader where+  toJSON x =+    Json.object+      [ Json.pair "system_id" $ systemId x,+        Json.pair "id" $ case (remoteId x, localId x) of+          (Just r, Just l) -> Just (r, l)+          _ -> Nothing+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-party-leader" $+    Schema.object+      [ (Json.pair "system_id" $ Schema.ref U8.schema, True),+        ( Json.pair "id" $+            Schema.oneOf+              [ Schema.tuple [Schema.ref RemoteId.schema, Schema.ref U8.schema],+                Schema.ref Schema.null+              ],+          False+        )+      ]++bitPut :: PartyLeader -> BitPut.BitPut+bitPut x =+  U8.bitPut (systemId x)+    <> foldMap RemoteId.bitPut (remoteId x)+    <> foldMap+      U8.bitPut+      (localId x)++bitGet :: Version.Version -> BitGet.BitGet PartyLeader+bitGet version = BitGet.label "PartyLeader" $ do+  systemId <- BitGet.label "systemId" U8.bitGet+  (remoteId, localId) <-+    if systemId == U8.fromWord8 0+      then pure (Nothing, Nothing)+      else do+        remoteId <- BitGet.label "remoteId" $ RemoteId.bitGet version systemId+        localId <- BitGet.label "localId" U8.bitGet+        pure (Just remoteId, Just localId)+  pure PartyLeader {systemId, remoteId, localId}
+ src/lib/Rattletrap/Type/Attribute/Pickup.hs view
@@ -0,0 +1,52 @@+module Rattletrap.Type.Attribute.Pickup where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Pickup = Pickup+  { instigatorId :: Maybe U32.U32,+    pickedUp :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON Pickup where+  parseJSON = Json.withObject "Pickup" $ \object -> do+    instigatorId <- Json.optional object "instigator_id"+    pickedUp <- Json.required object "picked_up"+    pure Pickup {instigatorId, pickedUp}++instance Json.ToJSON Pickup where+  toJSON x =+    Json.object+      [ Json.pair "instigator_id" $ instigatorId x,+        Json.pair "picked_up" $ pickedUp x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-pickup" $+    Schema.object+      [ (Json.pair "instigator_id" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "picked_up" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: Pickup -> BitPut.BitPut+bitPut x =+  maybe+    (BitPut.bool False)+    (\y -> BitPut.bool True <> U32.bitPut y)+    (instigatorId x)+    <> BitPut.bool (pickedUp x)++bitGet :: BitGet.BitGet Pickup+bitGet = BitGet.label "Pickup" $ do+  instigator <- BitGet.label "instigator" BitGet.bool+  instigatorId <-+    BitGet.label "instigatorId" $+      Monad.whenMaybe instigator U32.bitGet+  pickedUp <- BitGet.label "pickedUp" BitGet.bool+  pure Pickup {instigatorId, pickedUp}
+ src/lib/Rattletrap/Type/Attribute/PickupInfo.hs view
@@ -0,0 +1,94 @@+module Rattletrap.Type.Attribute.PickupInfo where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data PickupInfo = PickupInfo+  { unknown1 :: Bool,+    unknown2 :: Bool,+    unknown3 :: U32.U32,+    unknown4 :: I32.I32,+    unknown5 :: I32.I32,+    unknown6 :: Bool,+    unknown7 :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON PickupInfo where+  parseJSON = Json.withObject "PickupInfo" $ \object -> do+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.required object "unknown2"+    unknown3 <- Json.required object "unknown3"+    unknown4 <- Json.required object "unknown4"+    unknown5 <- Json.required object "unknown5"+    unknown6 <- Json.required object "unknown6"+    unknown7 <- Json.required object "unknown7"+    pure+      PickupInfo+        { unknown1,+          unknown2,+          unknown3,+          unknown4,+          unknown5,+          unknown6,+          unknown7+        }++instance Json.ToJSON PickupInfo where+  toJSON x =+    Json.object+      [ Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x,+        Json.pair "unknown3" $ unknown3 x,+        Json.pair "unknown4" $ unknown4 x,+        Json.pair "unknown5" $ unknown5 x,+        Json.pair "unknown6" $ unknown6 x,+        Json.pair "unknown7" $ unknown7 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "pickup-info" $+    Schema.object+      [ (Json.pair "unknown1" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown2" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown3" $ Schema.ref U32.schema, True),+        (Json.pair "unknown4" $ Schema.ref I32.schema, True),+        (Json.pair "unknown5" $ Schema.ref I32.schema, True),+        (Json.pair "unknown6" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown7" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: PickupInfo -> BitPut.BitPut+bitPut x =+  BitPut.bool (unknown1 x)+    <> BitPut.bool (unknown2 x)+    <> U32.bitPut (unknown3 x)+    <> I32.bitPut (unknown4 x)+    <> I32.bitPut (unknown5 x)+    <> BitPut.bool (unknown6 x)+    <> BitPut.bool (unknown7 x)++bitGet :: BitGet.BitGet PickupInfo+bitGet = BitGet.label "PickupInfo" $ do+  unknown1 <- BitGet.label "unknown1" BitGet.bool+  unknown2 <- BitGet.label "unknown2" BitGet.bool+  unknown3 <- BitGet.label "unknown3" U32.bitGet+  unknown4 <- BitGet.label "unknown4" I32.bitGet+  unknown5 <- BitGet.label "unknown5" I32.bitGet+  unknown6 <- BitGet.label "unknown6" BitGet.bool+  unknown7 <- BitGet.label "unknown7" BitGet.bool+  pure+    PickupInfo+      { unknown1,+        unknown2,+        unknown3,+        unknown4,+        unknown5,+        unknown6,+        unknown7+      }
+ src/lib/Rattletrap/Type/Attribute/PickupNew.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Attribute.PickupNew where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data PickupNew = PickupNew+  { instigatorId :: Maybe U32.U32,+    pickedUp :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON PickupNew where+  parseJSON = Json.withObject "PickupNew" $ \object -> do+    instigatorId <- Json.optional object "instigator_id"+    pickedUp <- Json.required object "picked_up"+    pure PickupNew {instigatorId, pickedUp}++instance Json.ToJSON PickupNew where+  toJSON x =+    Json.object+      [ Json.pair "instigator_id" $ instigatorId x,+        Json.pair "picked_up" $ pickedUp x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-pickup-new" $+    Schema.object+      [ (Json.pair "instigator_id" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "picked_up" $ Schema.ref U8.schema, True)+      ]++bitPut :: PickupNew -> BitPut.BitPut+bitPut x =+  maybe+    (BitPut.bool False)+    (\y -> BitPut.bool True <> U32.bitPut y)+    (instigatorId x)+    <> U8.bitPut (pickedUp x)++bitGet :: BitGet.BitGet PickupNew+bitGet = BitGet.label "PickupNew" $ do+  instigator <- BitGet.label "instigator" BitGet.bool+  instigatorId <-+    BitGet.label "instigatorId" $+      Monad.whenMaybe instigator U32.bitGet+  pickedUp <- BitGet.label "pickedUp" U8.bitGet+  pure PickupNew {instigatorId, pickedUp}
+ src/lib/Rattletrap/Type/Attribute/PlayerHistoryKey.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.PlayerHistoryKey where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype PlayerHistoryKey = PlayerHistoryKey+  { unknown :: Word.Word16+  }+  deriving (Eq, Show)++instance Json.FromJSON PlayerHistoryKey where+  parseJSON = fmap PlayerHistoryKey . Json.parseJSON++instance Json.ToJSON PlayerHistoryKey where+  toJSON = Json.toJSON . unknown++schema :: Schema.Schema+schema =+  Schema.named "attribute-player-history-key" $ Schema.ref Schema.number++bitPut :: PlayerHistoryKey -> BitPut.BitPut+bitPut = BitPut.bits 14 . unknown++bitGet :: BitGet.BitGet PlayerHistoryKey+bitGet = BitGet.label "PlayerHistoryKey" $ do+  unknown <- BitGet.label "unknown" $ BitGet.bits 14+  pure PlayerHistoryKey {unknown}
+ src/lib/Rattletrap/Type/Attribute/PrivateMatchSettings.hs view
@@ -0,0 +1,86 @@+module Rattletrap.Type.Attribute.PrivateMatchSettings where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data PrivateMatchSettings = PrivateMatchSettings+  { mutators :: Str.Str,+    joinableBy :: U32.U32,+    maxPlayers :: U32.U32,+    gameName :: Str.Str,+    password :: Str.Str,+    flag :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON PrivateMatchSettings where+  parseJSON = Json.withObject "PrivateMatchSettings" $ \object -> do+    mutators <- Json.required object "mutators"+    joinableBy <- Json.required object "joinable_by"+    maxPlayers <- Json.required object "max_players"+    gameName <- Json.required object "game_name"+    password <- Json.required object "password"+    flag <- Json.required object "flag"+    pure+      PrivateMatchSettings+        { mutators,+          joinableBy,+          maxPlayers,+          gameName,+          password,+          flag+        }++instance Json.ToJSON PrivateMatchSettings where+  toJSON x =+    Json.object+      [ Json.pair "mutators" $ mutators x,+        Json.pair "joinable_by" $ joinableBy x,+        Json.pair "max_players" $ maxPlayers x,+        Json.pair "game_name" $ gameName x,+        Json.pair "password" $ password x,+        Json.pair "flag" $ flag x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-private-match-settings" $+    Schema.object+      [ (Json.pair "mutators" $ Schema.ref Str.schema, True),+        (Json.pair "joinable_by" $ Schema.ref U32.schema, True),+        (Json.pair "max_players" $ Schema.ref U32.schema, True),+        (Json.pair "game_name" $ Schema.ref Str.schema, True),+        (Json.pair "password" $ Schema.ref Str.schema, True),+        (Json.pair "flag" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: PrivateMatchSettings -> BitPut.BitPut+bitPut privateMatchSettingsAttribute =+  Str.bitPut (mutators privateMatchSettingsAttribute)+    <> U32.bitPut (joinableBy privateMatchSettingsAttribute)+    <> U32.bitPut (maxPlayers privateMatchSettingsAttribute)+    <> Str.bitPut (gameName privateMatchSettingsAttribute)+    <> Str.bitPut (password privateMatchSettingsAttribute)+    <> BitPut.bool (flag privateMatchSettingsAttribute)++bitGet :: BitGet.BitGet PrivateMatchSettings+bitGet = BitGet.label "PrivateMatchSettings" $ do+  mutators <- BitGet.label "mutators" Str.bitGet+  joinableBy <- BitGet.label "joinableBy" U32.bitGet+  maxPlayers <- BitGet.label "maxPlayers" U32.bitGet+  gameName <- BitGet.label "gameName" Str.bitGet+  password <- BitGet.label "password" Str.bitGet+  flag <- BitGet.label "flag" BitGet.bool+  pure+    PrivateMatchSettings+      { mutators,+        joinableBy,+        maxPlayers,+        gameName,+        password,+        flag+      }
+ src/lib/Rattletrap/Type/Attribute/Product.hs view
@@ -0,0 +1,78 @@+module Rattletrap.Type.Attribute.Product where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.ProductValue as ProductValue+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Product = Product+  { unknown :: Bool,+    objectId :: U32.U32,+    -- | read-only+    objectName :: Maybe Str.Str,+    value :: ProductValue.ProductValue+  }+  deriving (Eq, Show)++instance Json.FromJSON Product where+  parseJSON = Json.withObject "Product" $ \object -> do+    unknown <- Json.required object "unknown"+    objectId <- Json.required object "object_id"+    objectName <- Json.optional object "object_name"+    value <- Json.required object "value"+    pure Product {unknown, objectId, objectName, value}++instance Json.ToJSON Product where+  toJSON x =+    Json.object+      [ Json.pair "unknown" $ unknown x,+        Json.pair "object_id" $ objectId x,+        Json.pair "object_name" $ objectName x,+        Json.pair "value" $ value x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-product" $+    Schema.object+      [ (Json.pair "unknown" $ Schema.ref Schema.boolean, True),+        (Json.pair "object_id" $ Schema.ref U32.schema, True),+        (Json.pair "object_name" . Schema.json $ Schema.maybe Str.schema, False),+        (Json.pair "value" $ Schema.ref ProductValue.schema, True)+      ]++putProductAttributes :: RList.List Product -> BitPut.BitPut+putProductAttributes attributes =+  let v = RList.toList attributes+   in (U8.bitPut . U8.fromWord8 . fromIntegral $ length v) <> foldMap bitPut v++bitPut :: Product -> BitPut.BitPut+bitPut attribute =+  BitPut.bool (unknown attribute)+    <> U32.bitPut (objectId attribute)+    <> ProductValue.bitPut (value attribute)++decodeProductAttributesBits ::+  Version.Version ->+  Map.Map U32.U32 Str.Str ->+  BitGet.BitGet (RList.List Product)+decodeProductAttributesBits version objectMap = do+  size <- U8.bitGet+  RList.replicateM (fromIntegral $ U8.toWord8 size) $ bitGet version objectMap++bitGet :: Version.Version -> Map.Map U32.U32 Str.Str -> BitGet.BitGet Product+bitGet version objectMap = BitGet.label "Product" $ do+  unknown <- BitGet.label "unknown" BitGet.bool+  objectId <- BitGet.label "objectId" U32.bitGet+  let objectName = Map.lookup objectId objectMap+  value <-+    BitGet.label "value" $+      ProductValue.bitGet version objectId objectName+  pure Product {unknown, objectId, objectName, value}
+ src/lib/Rattletrap/Type/Attribute/ProductValue.hs view
@@ -0,0 +1,126 @@+module Rattletrap.Type.Attribute.ProductValue where++import qualified Data.Foldable as Foldable+import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Exception.MissingProductName as MissingProductName+import qualified Rattletrap.Exception.UnknownProduct as UnknownProduct+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data ProductValue+  = PaintedOld CompressedWord.CompressedWord+  | PaintedNew Word.Word32+  | TeamEditionOld CompressedWord.CompressedWord+  | TeamEditionNew Word.Word32+  | SpecialEdition Word.Word32+  | UserColorOld (Maybe Word.Word32)+  | UserColorNew U32.U32+  | TitleId Str.Str+  deriving (Eq, Show)++instance Json.FromJSON ProductValue where+  parseJSON = Json.withObject "ProductValue" $ \object ->+    Foldable.asum+      [ fmap PaintedOld $ Json.required object "painted_old",+        fmap PaintedNew $ Json.required object "painted_new",+        fmap TeamEditionOld $ Json.required object "team_edition_old",+        fmap TeamEditionNew $ Json.required object "team_edition_new",+        fmap SpecialEdition $ Json.required object "special_edition",+        fmap UserColorOld $ Json.required object "user_color_old",+        fmap UserColorNew $ Json.required object "user_color_new",+        fmap TitleId $ Json.required object "title_id"+      ]++instance Json.ToJSON ProductValue where+  toJSON x = case x of+    PaintedOld y -> Json.object [Json.pair "painted_old" y]+    PaintedNew y -> Json.object [Json.pair "painted_new" y]+    TeamEditionOld y -> Json.object [Json.pair "team_edition_old" y]+    TeamEditionNew y -> Json.object [Json.pair "team_edition_new" y]+    SpecialEdition y -> Json.object [Json.pair "special_edition" y]+    UserColorOld y -> Json.object [Json.pair "user_color_old" y]+    UserColorNew y -> Json.object [Json.pair "user_color_new" y]+    TitleId y -> Json.object [Json.pair "title_id" y]++schema :: Schema.Schema+schema =+  Schema.named "attribute-product-value" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k v, True)])+      [ ("painted_old", Schema.ref CompressedWord.schema),+        ("painted_new", Schema.ref Schema.integer),+        ("team_edition_old", Schema.ref CompressedWord.schema),+        ("team_edition_new", Schema.ref Schema.integer),+        ("special_edition", Schema.ref Schema.integer),+        ("user_color_old", Schema.json $ Schema.maybe Schema.integer),+        ("user_color_new", Schema.ref U32.schema),+        ("title_id", Schema.ref Str.schema)+      ]++bitPut :: ProductValue -> BitPut.BitPut+bitPut val = case val of+  PaintedOld x -> CompressedWord.bitPut x+  PaintedNew x -> BitPut.bits 31 x+  TeamEditionOld x -> CompressedWord.bitPut x+  TeamEditionNew x -> BitPut.bits 31 x+  SpecialEdition x -> BitPut.bits 31 x+  UserColorOld x -> case x of+    Nothing -> BitPut.bool False+    Just y -> BitPut.bool True <> BitPut.bits 31 y+  UserColorNew x -> U32.bitPut x+  TitleId x -> Str.bitPut x++bitGet ::+  Version.Version -> U32.U32 -> Maybe Str.Str -> BitGet.BitGet ProductValue+bitGet version objectId maybeObjectName =+  BitGet.label "ProductValue" $ case fmap Str.toString maybeObjectName of+    Just "TAGame.ProductAttribute_Painted_TA" -> decodePainted version+    Just "TAGame.ProductAttribute_SpecialEdition_TA" -> decodeSpecialEdition+    Just "TAGame.ProductAttribute_TeamEdition_TA" -> decodeTeamEdition version+    Just "TAGame.ProductAttribute_TitleID_TA" -> decodeTitle+    Just "TAGame.ProductAttribute_UserColor_TA" -> decodeColor version+    Just x -> BitGet.throw $ UnknownProduct.UnknownProduct x+    Nothing ->+      BitGet.throw . MissingProductName.MissingProductName $+        U32.toWord32+          objectId++decodeSpecialEdition :: BitGet.BitGet ProductValue+decodeSpecialEdition =+  BitGet.label "SpecialEdition" . fmap SpecialEdition $ BitGet.bits 31++decodePainted :: Version.Version -> BitGet.BitGet ProductValue+decodePainted version =+  BitGet.label "Painted" $+    if hasNewPainted version+      then fmap PaintedNew $ BitGet.bits 31+      else fmap PaintedOld $ CompressedWord.bitGet 13++decodeTeamEdition :: Version.Version -> BitGet.BitGet ProductValue+decodeTeamEdition version =+  BitGet.label "TeamEdition" $+    if hasNewPainted version+      then fmap TeamEditionNew $ BitGet.bits 31+      else fmap TeamEditionOld $ CompressedWord.bitGet 13++decodeColor :: Version.Version -> BitGet.BitGet ProductValue+decodeColor version =+  BitGet.label "UserColor" $+    if Version.atLeast 868 23 8 version+      then fmap UserColorNew U32.bitGet+      else do+        hasValue <- BitGet.bool+        fmap UserColorOld $ Monad.whenMaybe hasValue (BitGet.bits 31)++hasNewPainted :: Version.Version -> Bool+hasNewPainted = Version.atLeast 868 18 0++decodeTitle :: BitGet.BitGet ProductValue+decodeTitle = BitGet.label "Title" $ fmap TitleId Str.bitGet
+ src/lib/Rattletrap/Type/Attribute/QWord.hs view
@@ -0,0 +1,29 @@+module Rattletrap.Type.Attribute.QWord where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++newtype QWord = QWord+  { value :: U64.U64+  }+  deriving (Eq, Show)++instance Json.FromJSON QWord where+  parseJSON = fmap QWord . Json.parseJSON++instance Json.ToJSON QWord where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-q-word" $ Schema.ref U64.schema++bitPut :: QWord -> BitPut.BitPut+bitPut qWordAttribute = U64.bitPut (value qWordAttribute)++bitGet :: BitGet.BitGet QWord+bitGet = BitGet.label "QWord" $ do+  value <- BitGet.label "value" U64.bitGet+  pure QWord {value}
+ src/lib/Rattletrap/Type/Attribute/RepStatTitle.hs view
@@ -0,0 +1,59 @@+module Rattletrap.Type.Attribute.RepStatTitle where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.FlaggedInt as FlaggedInt+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data RepStatTitle = RepStatTitle+  { unknown :: Bool,+    name :: Str.Str,+    target :: FlaggedInt.FlaggedInt,+    value :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON RepStatTitle where+  parseJSON = Json.withObject "RepStatTitle" $ \object -> do+    unknown <- Json.required object "unknown"+    name <- Json.required object "name"+    target <- Json.required object "target"+    value <- Json.required object "value"+    pure RepStatTitle {unknown, name, target, value}++instance Json.ToJSON RepStatTitle where+  toJSON x =+    Json.object+      [ Json.pair "unknown" $ unknown x,+        Json.pair "name" $ name x,+        Json.pair "target" $ target x,+        Json.pair "value" $ value x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-rep-stat-title" $+    Schema.object+      [ (Json.pair "unknown" $ Schema.ref Schema.boolean, True),+        (Json.pair "name" $ Schema.ref Str.schema, True),+        (Json.pair "target" $ Schema.ref FlaggedInt.schema, True),+        (Json.pair "value" $ Schema.ref U32.schema, True)+      ]++bitPut :: RepStatTitle -> BitPut.BitPut+bitPut x =+  BitPut.bool (unknown x)+    <> Str.bitPut (name x)+    <> FlaggedInt.bitPut (target x)+    <> U32.bitPut (value x)++bitGet :: BitGet.BitGet RepStatTitle+bitGet = BitGet.label "RepStatTitle" $ do+  unknown <- BitGet.label "unknown" BitGet.bool+  name <- BitGet.label "name" Str.bitGet+  target <- BitGet.label "target" FlaggedInt.bitGet+  value <- BitGet.label "value" U32.bitGet+  pure RepStatTitle {unknown, name, target, value}
+ src/lib/Rattletrap/Type/Attribute/Reservation.hs view
@@ -0,0 +1,82 @@+module Rattletrap.Type.Attribute.Reservation where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.UniqueId as UniqueId+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Reservation = Reservation+  { number :: CompressedWord.CompressedWord,+    uniqueId :: UniqueId.UniqueId,+    name :: Maybe Str.Str,+    unknown1 :: Bool,+    unknown2 :: Bool,+    unknown3 :: Maybe Word.Word8+  }+  deriving (Eq, Show)++instance Json.FromJSON Reservation where+  parseJSON = Json.withObject "Reservation" $ \object -> do+    number <- Json.required object "number"+    uniqueId <- Json.required object "unique_id"+    name <- Json.optional object "name"+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.required object "unknown2"+    unknown3 <- Json.optional object "unknown3"+    pure Reservation {number, uniqueId, name, unknown1, unknown2, unknown3}++instance Json.ToJSON Reservation where+  toJSON x =+    Json.object+      [ Json.pair "number" $ number x,+        Json.pair "unique_id" $ uniqueId x,+        Json.pair "name" $ name x,+        Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x,+        Json.pair "unknown3" $ unknown3 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-reservation" $+    Schema.object+      [ (Json.pair "number" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "unique_id" $ Schema.ref UniqueId.schema, True),+        (Json.pair "name" . Schema.json $ Schema.maybe Str.schema, False),+        (Json.pair "unknown1" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown2" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown3" . Schema.json $ Schema.maybe Schema.integer, False)+      ]++bitPut :: Reservation -> BitPut.BitPut+bitPut reservationAttribute =+  CompressedWord.bitPut (number reservationAttribute)+    <> UniqueId.bitPut (uniqueId reservationAttribute)+    <> foldMap Str.bitPut (name reservationAttribute)+    <> BitPut.bool (unknown1 reservationAttribute)+    <> BitPut.bool (unknown2 reservationAttribute)+    <> foldMap (BitPut.bits 6) (unknown3 reservationAttribute)++bitGet :: Version.Version -> BitGet.BitGet Reservation+bitGet version = BitGet.label "Reservation" $ do+  number <- BitGet.label "number" $ CompressedWord.bitGet 7+  uniqueId <- BitGet.label "uniqueId" $ UniqueId.bitGet version+  name <-+    BitGet.label "name" $+      Monad.whenMaybe+        (UniqueId.systemId uniqueId /= U8.fromWord8 0)+        Str.bitGet+  unknown1 <- BitGet.label "unknown1" BitGet.bool+  unknown2 <- BitGet.label "unknown2" BitGet.bool+  unknown3 <-+    BitGet.label "unknown3"+      . Monad.whenMaybe (Version.atLeast 868 12 0 version)+      $ BitGet.bits 6+  pure Reservation {number, uniqueId, name, unknown1, unknown2, unknown3}
+ src/lib/Rattletrap/Type/Attribute/RigidBodyState.hs view
@@ -0,0 +1,88 @@+module Rattletrap.Type.Attribute.RigidBodyState where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Rotation as Rotation+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data RigidBodyState = RigidBodyState+  { sleeping :: Bool,+    location :: Vector.Vector,+    rotation :: Rotation.Rotation,+    linearVelocity :: Maybe Vector.Vector,+    angularVelocity :: Maybe Vector.Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON RigidBodyState where+  parseJSON = Json.withObject "RigidBodyState" $ \object -> do+    sleeping <- Json.required object "sleeping"+    location <- Json.required object "location"+    rotation <- Json.required object "rotation"+    linearVelocity <- Json.optional object "linear_velocity"+    angularVelocity <- Json.optional object "angular_velocity"+    pure+      RigidBodyState+        { sleeping,+          location,+          rotation,+          linearVelocity,+          angularVelocity+        }++instance Json.ToJSON RigidBodyState where+  toJSON x =+    Json.object+      [ Json.pair "sleeping" $ sleeping x,+        Json.pair "location" $ location x,+        Json.pair "rotation" $ rotation x,+        Json.pair "linear_velocity" $ linearVelocity x,+        Json.pair "angular_velocity" $ angularVelocity x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-rigid-body-state" $+    Schema.object+      [ (Json.pair "sleeping" $ Schema.ref Schema.boolean, True),+        (Json.pair "location" $ Schema.ref Vector.schema, True),+        (Json.pair "rotation" $ Schema.ref Rotation.schema, True),+        ( Json.pair "linear_velocity" . Schema.json $ Schema.maybe Vector.schema,+          False+        ),+        ( Json.pair "angular_velocity" . Schema.json $ Schema.maybe Vector.schema,+          False+        )+      ]++bitPut :: RigidBodyState -> BitPut.BitPut+bitPut rigidBodyStateAttribute =+  BitPut.bool (sleeping rigidBodyStateAttribute)+    <> Vector.bitPut (location rigidBodyStateAttribute)+    <> Rotation.bitPut (rotation rigidBodyStateAttribute)+    <> foldMap Vector.bitPut (linearVelocity rigidBodyStateAttribute)+    <> foldMap Vector.bitPut (angularVelocity rigidBodyStateAttribute)++bitGet :: Version.Version -> BitGet.BitGet RigidBodyState+bitGet version = BitGet.label "RigidBodyState" $ do+  sleeping <- BitGet.label "sleeping" BitGet.bool+  location <- BitGet.label "location" $ Vector.bitGet version+  rotation <- BitGet.label "rotation" $ Rotation.bitGet version+  linearVelocity <-+    BitGet.label "linearVelocity" $+      Monad.whenMaybe (not sleeping) (Vector.bitGet version)+  angularVelocity <-+    BitGet.label "angularVelocity" $+      Monad.whenMaybe (not sleeping) (Vector.bitGet version)+  pure+    RigidBodyState+      { sleeping,+        location,+        rotation,+        linearVelocity,+        angularVelocity+      }
+ src/lib/Rattletrap/Type/Attribute/Rotation.hs view
@@ -0,0 +1,29 @@+module Rattletrap.Type.Attribute.Rotation where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Int8Vector as Int8Vector+import qualified Rattletrap.Utility.Json as Json++newtype Rotation = Rotation+  { value :: Int8Vector.Int8Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON Rotation where+  parseJSON = fmap Rotation . Json.parseJSON++instance Json.ToJSON Rotation where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-rotation" $ Schema.ref Int8Vector.schema++bitPut :: Rotation -> BitPut.BitPut+bitPut = Int8Vector.bitPut . value++bitGet :: BitGet.BitGet Rotation+bitGet = BitGet.label "Rotation" $ do+  value <- BitGet.label "value" Int8Vector.bitGet+  pure Rotation {value}
+ src/lib/Rattletrap/Type/Attribute/StatEvent.hs view
@@ -0,0 +1,43 @@+module Rattletrap.Type.Attribute.StatEvent where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Json as Json++data StatEvent = StatEvent+  { unknown :: Bool,+    objectId :: I32.I32+  }+  deriving (Eq, Show)++instance Json.FromJSON StatEvent where+  parseJSON = Json.withObject "StatEvent" $ \object -> do+    unknown <- Json.required object "unknown"+    objectId <- Json.required object "object_id"+    pure StatEvent {unknown, objectId}++instance Json.ToJSON StatEvent where+  toJSON x =+    Json.object+      [Json.pair "unknown" $ unknown x, Json.pair "object_id" $ objectId x]++schema :: Schema.Schema+schema =+  Schema.named "attribute-stat-event" $+    Schema.object+      [ (Json.pair "unknown" $ Schema.ref Schema.boolean, True),+        (Json.pair "object_id" $ Schema.ref I32.schema, True)+      ]++bitPut :: StatEvent -> BitPut.BitPut+bitPut statEventAttribute =+  BitPut.bool (unknown statEventAttribute)+    <> I32.bitPut (objectId statEventAttribute)++bitGet :: BitGet.BitGet StatEvent+bitGet = BitGet.label "StatEvent" $ do+  unknown <- BitGet.label "unknown" BitGet.bool+  objectId <- BitGet.label "objectId" I32.bitGet+  pure StatEvent {unknown, objectId}
+ src/lib/Rattletrap/Type/Attribute/String.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Attribute.String where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (String)++newtype String = String+  { value :: Str.Str+  }+  deriving (Eq, Show)++instance Json.FromJSON String where+  parseJSON = fmap String . Json.parseJSON++instance Json.ToJSON String where+  toJSON = Json.toJSON . value++schema :: Schema.Schema+schema = Schema.named "attribute-string" $ Schema.ref Str.schema++bitPut :: String -> BitPut.BitPut+bitPut stringAttribute = Str.bitPut (value stringAttribute)++bitGet :: BitGet.BitGet String+bitGet = BitGet.label "String" $ do+  value <- BitGet.label "value" Str.bitGet+  pure String {value}
+ src/lib/Rattletrap/Type/Attribute/TeamPaint.hs view
@@ -0,0 +1,78 @@+module Rattletrap.Type.Attribute.TeamPaint where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data TeamPaint = TeamPaint+  { team :: U8.U8,+    primaryColor :: U8.U8,+    accentColor :: U8.U8,+    primaryFinish :: U32.U32,+    accentFinish :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON TeamPaint where+  parseJSON = Json.withObject "TeamPaint" $ \object -> do+    team <- Json.required object "team"+    primaryColor <- Json.required object "primary_color"+    accentColor <- Json.required object "accent_color"+    primaryFinish <- Json.required object "primary_finish"+    accentFinish <- Json.required object "accent_finish"+    pure+      TeamPaint+        { team,+          primaryColor,+          accentColor,+          primaryFinish,+          accentFinish+        }++instance Json.ToJSON TeamPaint where+  toJSON x =+    Json.object+      [ Json.pair "team" $ team x,+        Json.pair "primary_color" $ primaryColor x,+        Json.pair "accent_color" $ accentColor x,+        Json.pair "primary_finish" $ primaryFinish x,+        Json.pair "accent_finish" $ accentFinish x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-team-paint" $+    Schema.object+      [ (Json.pair "team" $ Schema.ref U8.schema, True),+        (Json.pair "primary_color" $ Schema.ref U8.schema, True),+        (Json.pair "accent_color" $ Schema.ref U8.schema, True),+        (Json.pair "primary_finish" $ Schema.ref U32.schema, True),+        (Json.pair "accent_finish" $ Schema.ref U32.schema, True)+      ]++bitPut :: TeamPaint -> BitPut.BitPut+bitPut teamPaintAttribute =+  U8.bitPut (team teamPaintAttribute)+    <> U8.bitPut (primaryColor teamPaintAttribute)+    <> U8.bitPut (accentColor teamPaintAttribute)+    <> U32.bitPut (primaryFinish teamPaintAttribute)+    <> U32.bitPut (accentFinish teamPaintAttribute)++bitGet :: BitGet.BitGet TeamPaint+bitGet = BitGet.label "TeamPaint" $ do+  team <- BitGet.label "team" U8.bitGet+  primaryColor <- BitGet.label "primaryColor" U8.bitGet+  accentColor <- BitGet.label "accentColor" U8.bitGet+  primaryFinish <- BitGet.label "primaryFinish" U32.bitGet+  accentFinish <- BitGet.label "accentFinish" U32.bitGet+  pure+    TeamPaint+      { team,+        primaryColor,+        accentColor,+        primaryFinish,+        accentFinish+      }
+ src/lib/Rattletrap/Type/Attribute/Title.hs view
@@ -0,0 +1,101 @@+module Rattletrap.Type.Attribute.Title where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Title = Title+  { unknown1 :: Bool,+    unknown2 :: Bool,+    unknown3 :: U32.U32,+    unknown4 :: U32.U32,+    unknown5 :: U32.U32,+    unknown6 :: U32.U32,+    unknown7 :: U32.U32,+    unknown8 :: Bool+  }+  deriving (Eq, Show)++instance Json.FromJSON Title where+  parseJSON = Json.withObject "Title" $ \object -> do+    unknown1 <- Json.required object "unknown1"+    unknown2 <- Json.required object "unknown2"+    unknown3 <- Json.required object "unknown3"+    unknown4 <- Json.required object "unknown4"+    unknown5 <- Json.required object "unknown5"+    unknown6 <- Json.required object "unknown6"+    unknown7 <- Json.required object "unknown7"+    unknown8 <- Json.required object "unknown8"+    pure+      Title+        { unknown1,+          unknown2,+          unknown3,+          unknown4,+          unknown5,+          unknown6,+          unknown7,+          unknown8+        }++instance Json.ToJSON Title where+  toJSON x =+    Json.object+      [ Json.pair "unknown1" $ unknown1 x,+        Json.pair "unknown2" $ unknown2 x,+        Json.pair "unknown3" $ unknown3 x,+        Json.pair "unknown4" $ unknown4 x,+        Json.pair "unknown5" $ unknown5 x,+        Json.pair "unknown6" $ unknown6 x,+        Json.pair "unknown7" $ unknown7 x,+        Json.pair "unknown8" $ unknown8 x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-title" $+    Schema.object+      [ (Json.pair "unknown1" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown2" $ Schema.ref Schema.boolean, True),+        (Json.pair "unknown3" $ Schema.ref U32.schema, True),+        (Json.pair "unknown4" $ Schema.ref U32.schema, True),+        (Json.pair "unknown5" $ Schema.ref U32.schema, True),+        (Json.pair "unknown6" $ Schema.ref U32.schema, True),+        (Json.pair "unknown7" $ Schema.ref U32.schema, True),+        (Json.pair "unknown8" $ Schema.ref Schema.boolean, True)+      ]++bitPut :: Title -> BitPut.BitPut+bitPut titleAttribute =+  BitPut.bool (unknown1 titleAttribute)+    <> BitPut.bool (unknown2 titleAttribute)+    <> U32.bitPut (unknown3 titleAttribute)+    <> U32.bitPut (unknown4 titleAttribute)+    <> U32.bitPut (unknown5 titleAttribute)+    <> U32.bitPut (unknown6 titleAttribute)+    <> U32.bitPut (unknown7 titleAttribute)+    <> BitPut.bool (unknown8 titleAttribute)++bitGet :: BitGet.BitGet Title+bitGet = BitGet.label "Title" $ do+  unknown1 <- BitGet.label "unknown1" BitGet.bool+  unknown2 <- BitGet.label "unknown2" BitGet.bool+  unknown3 <- BitGet.label "unknown3" U32.bitGet+  unknown4 <- BitGet.label "unknown4" U32.bitGet+  unknown5 <- BitGet.label "unknown5" U32.bitGet+  unknown6 <- BitGet.label "unknown6" U32.bitGet+  unknown7 <- BitGet.label "unknown7" U32.bitGet+  unknown8 <- BitGet.label "unknown8" BitGet.bool+  pure+    Title+      { unknown1,+        unknown2,+        unknown3,+        unknown4,+        unknown5,+        unknown6,+        unknown7,+        unknown8+      }
+ src/lib/Rattletrap/Type/Attribute/UniqueId.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Attribute.UniqueId where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.RemoteId as RemoteId+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data UniqueId = UniqueId+  { systemId :: U8.U8,+    remoteId :: RemoteId.RemoteId,+    localId :: U8.U8+  }+  deriving (Eq, Show)++instance Json.FromJSON UniqueId where+  parseJSON = Json.withObject "UniqueId" $ \object -> do+    systemId <- Json.required object "system_id"+    remoteId <- Json.required object "remote_id"+    localId <- Json.required object "local_id"+    pure UniqueId {systemId, remoteId, localId}++instance Json.ToJSON UniqueId where+  toJSON x =+    Json.object+      [ Json.pair "system_id" $ systemId x,+        Json.pair "remote_id" $ remoteId x,+        Json.pair "local_id" $ localId x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-unique-id" $+    Schema.object+      [ (Json.pair "system_id" $ Schema.ref U8.schema, True),+        (Json.pair "remote_id" $ Schema.ref RemoteId.schema, True),+        (Json.pair "local_id" $ Schema.ref U8.schema, True)+      ]++bitPut :: UniqueId -> BitPut.BitPut+bitPut uniqueIdAttribute =+  U8.bitPut (systemId uniqueIdAttribute)+    <> RemoteId.bitPut (remoteId uniqueIdAttribute)+    <> U8.bitPut (localId uniqueIdAttribute)++bitGet :: Version.Version -> BitGet.BitGet UniqueId+bitGet version = BitGet.label "UniqueId" $ do+  systemId <- BitGet.label "systemId" U8.bitGet+  remoteId <- BitGet.label "remoteId" $ RemoteId.bitGet version systemId+  localId <- BitGet.label "localId" U8.bitGet+  pure UniqueId {systemId, remoteId, localId}
+ src/lib/Rattletrap/Type/Attribute/WeldedInfo.hs view
@@ -0,0 +1,67 @@+module Rattletrap.Type.Attribute.WeldedInfo where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.Int8Vector as Int8Vector+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data WeldedInfo = WeldedInfo+  { active :: Bool,+    actorId :: I32.I32,+    offset :: Vector.Vector,+    mass :: F32.F32,+    rotation :: Int8Vector.Int8Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON WeldedInfo where+  parseJSON = Json.withObject "WeldedInfo" $ \object -> do+    active <- Json.required object "active"+    actorId <- Json.required object "actor_id"+    offset <- Json.required object "offset"+    mass <- Json.required object "mass"+    rotation <- Json.required object "rotation"+    pure WeldedInfo {active, actorId, offset, mass, rotation}++instance Json.ToJSON WeldedInfo where+  toJSON x =+    Json.object+      [ Json.pair "active" $ active x,+        Json.pair "actor_id" $ actorId x,+        Json.pair "offset" $ offset x,+        Json.pair "mass" $ mass x,+        Json.pair "rotation" $ rotation x+      ]++schema :: Schema.Schema+schema =+  Schema.named "attribute-welded-info" $+    Schema.object+      [ (Json.pair "active" $ Schema.ref Schema.boolean, True),+        (Json.pair "actor_id" $ Schema.ref I32.schema, True),+        (Json.pair "offset" $ Schema.ref Vector.schema, True),+        (Json.pair "mass" $ Schema.ref F32.schema, True),+        (Json.pair "rotation" $ Schema.ref Int8Vector.schema, True)+      ]++bitPut :: WeldedInfo -> BitPut.BitPut+bitPut weldedInfoAttribute =+  BitPut.bool (active weldedInfoAttribute)+    <> I32.bitPut (actorId weldedInfoAttribute)+    <> Vector.bitPut (offset weldedInfoAttribute)+    <> F32.bitPut (mass weldedInfoAttribute)+    <> Int8Vector.bitPut (rotation weldedInfoAttribute)++bitGet :: Version.Version -> BitGet.BitGet WeldedInfo+bitGet version = BitGet.label "WeldedInfo" $ do+  active <- BitGet.label "active" BitGet.bool+  actorId <- BitGet.label "actorId" I32.bitGet+  offset <- BitGet.label "offset" $ Vector.bitGet version+  mass <- BitGet.label "mass" F32.bitGet+  rotation <- BitGet.label "rotation" Int8Vector.bitGet+  pure WeldedInfo {active, actorId, offset, mass, rotation}
+ src/lib/Rattletrap/Type/AttributeMapping.hs view
@@ -0,0 +1,41 @@+module Rattletrap.Type.AttributeMapping where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data AttributeMapping = AttributeMapping+  { objectId :: U32.U32,+    streamId :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON AttributeMapping where+  parseJSON = Json.withObject "AttributeMapping" $ \object -> do+    objectId <- Json.required object "object_id"+    streamId <- Json.required object "stream_id"+    pure AttributeMapping {objectId, streamId}++instance Json.ToJSON AttributeMapping where+  toJSON x =+    Json.object+      [Json.pair "object_id" $ objectId x, Json.pair "stream_id" $ streamId x]++schema :: Schema.Schema+schema =+  Schema.named "attributeMapping" $+    Schema.object+      [ (Json.pair "object_id" $ Schema.ref U32.schema, True),+        (Json.pair "stream_id" $ Schema.ref U32.schema, True)+      ]++bytePut :: AttributeMapping -> BytePut.BytePut+bytePut x = U32.bytePut (objectId x) <> U32.bytePut (streamId x)++byteGet :: ByteGet.ByteGet AttributeMapping+byteGet = ByteGet.label "AttributeMapping" $ do+  objectId <- ByteGet.label "objectId" U32.byteGet+  streamId <- ByteGet.label "streamId" U32.byteGet+  pure AttributeMapping {objectId, streamId}
+ src/lib/Rattletrap/Type/AttributeType.hs view
@@ -0,0 +1,47 @@+module Rattletrap.Type.AttributeType where++data AttributeType+  = AppliedDamage+  | Boolean+  | Boost+  | Byte+  | CamSettings+  | ClubColors+  | CustomDemolish+  | CustomDemolishExtended+  | DamageState+  | Demolish+  | Enum+  | Explosion+  | ExtendedExplosion+  | FlaggedInt+  | FlaggedByte+  | Float+  | GameMode+  | GameServer+  | Int+  | Int64+  | Loadout+  | LoadoutOnline+  | Loadouts+  | LoadoutsOnline+  | Location+  | MusicStinger+  | PartyLeader+  | Pickup+  | PickupInfo+  | PickupNew+  | PlayerHistoryKey+  | PrivateMatchSettings+  | QWord+  | RepStatTitle+  | Reservation+  | RigidBodyState+  | Rotation+  | StatEvent+  | String+  | TeamPaint+  | Title+  | UniqueId+  | WeldedInfo+  deriving (Eq, Show)
+ src/lib/Rattletrap/Type/AttributeValue.hs view
@@ -0,0 +1,364 @@+module Rattletrap.Type.AttributeValue where++import qualified Data.Foldable as Foldable+import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Data as Data+import qualified Rattletrap.Exception.UnknownAttribute as UnknownAttribute+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute.AppliedDamage as AppliedDamage+import qualified Rattletrap.Type.Attribute.Boolean as Boolean+import qualified Rattletrap.Type.Attribute.Boost as Boost+import qualified Rattletrap.Type.Attribute.Byte as Byte+import qualified Rattletrap.Type.Attribute.CamSettings as CamSettings+import qualified Rattletrap.Type.Attribute.ClubColors as ClubColors+import qualified Rattletrap.Type.Attribute.CustomDemolish as CustomDemolish+import qualified Rattletrap.Type.Attribute.CustomDemolishExtended as CustomDemolishExtended+import qualified Rattletrap.Type.Attribute.DamageState as DamageState+import qualified Rattletrap.Type.Attribute.Demolish as Demolish+import qualified Rattletrap.Type.Attribute.Enum as Enum+import qualified Rattletrap.Type.Attribute.Explosion as Explosion+import qualified Rattletrap.Type.Attribute.ExtendedExplosion as ExtendedExplosion+import qualified Rattletrap.Type.Attribute.FlaggedByte as FlaggedByte+import qualified Rattletrap.Type.Attribute.FlaggedInt as FlaggedInt+import qualified Rattletrap.Type.Attribute.Float as Float+import qualified Rattletrap.Type.Attribute.GameMode as GameMode+import qualified Rattletrap.Type.Attribute.GameServer as GameServer+import qualified Rattletrap.Type.Attribute.Int as Int+import qualified Rattletrap.Type.Attribute.Int64 as Int64+import qualified Rattletrap.Type.Attribute.Loadout as Loadout+import qualified Rattletrap.Type.Attribute.LoadoutOnline as LoadoutOnline+import qualified Rattletrap.Type.Attribute.Loadouts as Loadouts+import qualified Rattletrap.Type.Attribute.LoadoutsOnline as LoadoutsOnline+import qualified Rattletrap.Type.Attribute.Location as Location+import qualified Rattletrap.Type.Attribute.MusicStinger as MusicStinger+import qualified Rattletrap.Type.Attribute.PartyLeader as PartyLeader+import qualified Rattletrap.Type.Attribute.Pickup as Pickup+import qualified Rattletrap.Type.Attribute.PickupInfo as PickupInfo+import qualified Rattletrap.Type.Attribute.PickupNew as PickupNew+import qualified Rattletrap.Type.Attribute.PlayerHistoryKey as PlayerHistoryKey+import qualified Rattletrap.Type.Attribute.PrivateMatchSettings as PrivateMatchSettings+import qualified Rattletrap.Type.Attribute.QWord as QWord+import qualified Rattletrap.Type.Attribute.RepStatTitle as RepStatTitle+import qualified Rattletrap.Type.Attribute.Reservation as Reservation+import qualified Rattletrap.Type.Attribute.RigidBodyState as RigidBodyState+import qualified Rattletrap.Type.Attribute.Rotation as Rotation+import qualified Rattletrap.Type.Attribute.StatEvent as StatEvent+import qualified Rattletrap.Type.Attribute.String as String+import qualified Rattletrap.Type.Attribute.TeamPaint as TeamPaint+import qualified Rattletrap.Type.Attribute.Title as Title+import qualified Rattletrap.Type.Attribute.UniqueId as UniqueId+import qualified Rattletrap.Type.Attribute.WeldedInfo as WeldedInfo+import qualified Rattletrap.Type.AttributeType as AttributeType+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data AttributeValue+  = AppliedDamage AppliedDamage.AppliedDamage+  | Boolean Boolean.Boolean+  | Boost Boost.Boost+  | Byte Byte.Byte+  | CamSettings CamSettings.CamSettings+  | ClubColors ClubColors.ClubColors+  | CustomDemolish CustomDemolish.CustomDemolish+  | CustomDemolishExtended CustomDemolishExtended.CustomDemolishExtended+  | DamageState DamageState.DamageState+  | Demolish Demolish.Demolish+  | Enum Enum.Enum+  | Explosion Explosion.Explosion+  | ExtendedExplosion ExtendedExplosion.ExtendedExplosion+  | FlaggedInt FlaggedInt.FlaggedInt+  | FlaggedByte FlaggedByte.FlaggedByte+  | Float Float.Float+  | GameMode GameMode.GameMode+  | GameServer GameServer.GameServer+  | Int Int.Int+  | Int64 Int64.Int64+  | Loadout Loadout.Loadout+  | LoadoutOnline LoadoutOnline.LoadoutOnline+  | Loadouts Loadouts.Loadouts+  | LoadoutsOnline LoadoutsOnline.LoadoutsOnline+  | Location Location.Location+  | MusicStinger MusicStinger.MusicStinger+  | PartyLeader PartyLeader.PartyLeader+  | Pickup Pickup.Pickup+  | PickupInfo PickupInfo.PickupInfo+  | PickupNew PickupNew.PickupNew+  | PlayerHistoryKey PlayerHistoryKey.PlayerHistoryKey+  | PrivateMatchSettings PrivateMatchSettings.PrivateMatchSettings+  | QWord QWord.QWord+  | RepStatTitle RepStatTitle.RepStatTitle+  | Reservation Reservation.Reservation+  | RigidBodyState RigidBodyState.RigidBodyState+  | Rotation Rotation.Rotation+  | StatEvent StatEvent.StatEvent+  | String String.String+  | TeamPaint TeamPaint.TeamPaint+  | Title Title.Title+  | UniqueId UniqueId.UniqueId+  | WeldedInfo WeldedInfo.WeldedInfo+  deriving (Eq, Show)++instance Json.FromJSON AttributeValue where+  parseJSON = Json.withObject "AttributeValue" $ \object ->+    Foldable.asum+      [ fmap AppliedDamage $ Json.required object "applied_damage",+        fmap Boolean $ Json.required object "boolean",+        fmap Boost $ Json.required object "boost",+        fmap Byte $ Json.required object "byte",+        fmap CamSettings $ Json.required object "cam_settings",+        fmap ClubColors $ Json.required object "club_colors",+        fmap CustomDemolish $ Json.required object "custom_demolish",+        fmap CustomDemolishExtended $ Json.required object "custom_demolish_extended",+        fmap DamageState $ Json.required object "damage_state",+        fmap Demolish $ Json.required object "demolish",+        fmap Enum $ Json.required object "enum",+        fmap Explosion $ Json.required object "explosion",+        fmap ExtendedExplosion $ Json.required object "extended_explosion",+        fmap FlaggedByte $ Json.required object "flagged_byte",+        fmap FlaggedInt $ Json.required object "flagged_int",+        fmap Float $ Json.required object "float",+        fmap GameMode $ Json.required object "game_mode",+        fmap GameServer $ Json.required object "game_server",+        fmap Int $ Json.required object "int",+        fmap Int64 $ Json.required object "int64",+        fmap Loadout $ Json.required object "loadout",+        fmap LoadoutOnline $ Json.required object "loadout_online",+        fmap Loadouts $ Json.required object "loadouts",+        fmap LoadoutsOnline $ Json.required object "loadouts_online",+        fmap Location $ Json.required object "location",+        fmap MusicStinger $ Json.required object "music_stinger",+        fmap PartyLeader $ Json.required object "party_leader",+        fmap Pickup $ Json.required object "pickup",+        fmap PickupInfo $ Json.required object "pickup_info",+        fmap PickupNew $ Json.required object "pickup_new",+        fmap PlayerHistoryKey $ Json.required object "player_history_key",+        fmap PrivateMatchSettings $ Json.required object "private_match_settings",+        fmap QWord $ Json.required object "q_word",+        fmap RepStatTitle $ Json.required object "rep_stat_title",+        fmap Reservation $ Json.required object "reservation",+        fmap RigidBodyState $ Json.required object "rigid_body_state",+        fmap Rotation $ Json.required object "rotation",+        fmap StatEvent $ Json.required object "stat_event",+        fmap String $ Json.required object "string",+        fmap TeamPaint $ Json.required object "team_paint",+        fmap Title $ Json.required object "title",+        fmap UniqueId $ Json.required object "unique_id",+        fmap WeldedInfo $ Json.required object "welded_info"+      ]++instance Json.ToJSON AttributeValue where+  toJSON x = case x of+    AppliedDamage y -> Json.object [Json.pair "applied_damage" y]+    Boolean y -> Json.object [Json.pair "boolean" y]+    Boost y -> Json.object [Json.pair "boost" y]+    Byte y -> Json.object [Json.pair "byte" y]+    CamSettings y -> Json.object [Json.pair "cam_settings" y]+    ClubColors y -> Json.object [Json.pair "club_colors" y]+    CustomDemolish y -> Json.object [Json.pair "custom_demolish" y]+    CustomDemolishExtended y -> Json.object [Json.pair "custom_demolish_extended" y]+    DamageState y -> Json.object [Json.pair "damage_state" y]+    Demolish y -> Json.object [Json.pair "demolish" y]+    Enum y -> Json.object [Json.pair "enum" y]+    Explosion y -> Json.object [Json.pair "explosion" y]+    ExtendedExplosion y -> Json.object [Json.pair "extended_explosion" y]+    FlaggedByte y -> Json.object [Json.pair "flagged_byte" y]+    FlaggedInt y -> Json.object [Json.pair "flagged_int" y]+    Float y -> Json.object [Json.pair "float" y]+    GameMode y -> Json.object [Json.pair "game_mode" y]+    GameServer y -> Json.object [Json.pair "game_server" y]+    Int y -> Json.object [Json.pair "int" y]+    Int64 y -> Json.object [Json.pair "int64" y]+    Loadout y -> Json.object [Json.pair "loadout" y]+    LoadoutOnline y -> Json.object [Json.pair "loadout_online" y]+    Loadouts y -> Json.object [Json.pair "loadouts" y]+    LoadoutsOnline y -> Json.object [Json.pair "loadouts_online" y]+    Location y -> Json.object [Json.pair "location" y]+    MusicStinger y -> Json.object [Json.pair "music_stinger" y]+    PartyLeader y -> Json.object [Json.pair "party_leader" y]+    Pickup y -> Json.object [Json.pair "pickup" y]+    PickupInfo y -> Json.object [Json.pair "pickup_info" y]+    PickupNew y -> Json.object [Json.pair "pickup_new" y]+    PlayerHistoryKey y -> Json.object [Json.pair "player_history_key" y]+    PrivateMatchSettings y ->+      Json.object [Json.pair "private_match_settings" y]+    QWord y -> Json.object [Json.pair "q_word" y]+    RepStatTitle y -> Json.object [Json.pair "rep_stat_title" y]+    Reservation y -> Json.object [Json.pair "reservation" y]+    RigidBodyState y -> Json.object [Json.pair "rigid_body_state" y]+    Rotation y -> Json.object [Json.pair "rotation" y]+    StatEvent y -> Json.object [Json.pair "stat_event" y]+    String y -> Json.object [Json.pair "string" y]+    TeamPaint y -> Json.object [Json.pair "team_paint" y]+    Title y -> Json.object [Json.pair "title" y]+    UniqueId y -> Json.object [Json.pair "unique_id" y]+    WeldedInfo y -> Json.object [Json.pair "welded_info" y]++schema :: Schema.Schema+schema =+  Schema.named "attribute-value" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k $ Schema.ref v, True)])+      [ ("applied_damage", AppliedDamage.schema),+        ("boolean", Boolean.schema),+        ("boost", Boost.schema),+        ("byte", Byte.schema),+        ("cam_settings", CamSettings.schema),+        ("club_colors", ClubColors.schema),+        ("custom_demolish", CustomDemolish.schema),+        ("custom_demolish_extended", CustomDemolishExtended.schema),+        ("damage_state", DamageState.schema),+        ("demolish", Demolish.schema),+        ("enum", Enum.schema),+        ("explosion", Explosion.schema),+        ("extended_explosion", ExtendedExplosion.schema),+        ("flagged_byte", FlaggedByte.schema),+        ("flagged_int", FlaggedInt.schema),+        ("float", Float.schema),+        ("game_mode", GameMode.schema),+        ("game_server", GameServer.schema),+        ("int", Int.schema),+        ("int64", Int64.schema),+        ("loadout_online", LoadoutOnline.schema),+        ("loadout", Loadout.schema),+        ("loadouts_online", LoadoutsOnline.schema),+        ("loadouts", Loadouts.schema),+        ("location", Location.schema),+        ("music_stinger", MusicStinger.schema),+        ("party_leader", PartyLeader.schema),+        ("pickup_info", PickupInfo.schema),+        ("pickup_new", PickupNew.schema),+        ("pickup", Pickup.schema),+        ("player_history_key", PlayerHistoryKey.schema),+        ("private_match_settings", PrivateMatchSettings.schema),+        ("q_word", QWord.schema),+        ("rep_stat_title", RepStatTitle.schema),+        ("reservation", Reservation.schema),+        ("rigid_body_state", RigidBodyState.schema),+        ("rotation", Rotation.schema),+        ("stat_event", StatEvent.schema),+        ("string", String.schema),+        ("team_paint", TeamPaint.schema),+        ("title", Title.schema),+        ("unique_id", UniqueId.schema),+        ("welded_info", WeldedInfo.schema)+      ]++bitPut :: AttributeValue -> BitPut.BitPut+bitPut value = case value of+  AppliedDamage x -> AppliedDamage.bitPut x+  Boolean x -> Boolean.bitPut x+  Boost x -> Boost.bitPut x+  Byte x -> Byte.bitPut x+  CamSettings x -> CamSettings.bitPut x+  ClubColors x -> ClubColors.bitPut x+  CustomDemolish x -> CustomDemolish.bitPut x+  CustomDemolishExtended x -> CustomDemolishExtended.bitPut x+  DamageState x -> DamageState.bitPut x+  Demolish x -> Demolish.bitPut x+  Enum x -> Enum.bitPut x+  Explosion x -> Explosion.bitPut x+  ExtendedExplosion x -> ExtendedExplosion.bitPut x+  FlaggedInt x -> FlaggedInt.bitPut x+  FlaggedByte x -> FlaggedByte.bitPut x+  Float x -> Float.bitPut x+  GameMode x -> GameMode.bitPut x+  GameServer x -> GameServer.bitPut x+  Int x -> Int.bitPut x+  Int64 x -> Int64.putInt64Attribute x+  Loadout x -> Loadout.bitPut x+  LoadoutOnline x -> LoadoutOnline.bitPut x+  Loadouts x -> Loadouts.bitPut x+  LoadoutsOnline x -> LoadoutsOnline.bitPut x+  Location x -> Location.bitPut x+  MusicStinger x -> MusicStinger.bitPut x+  PartyLeader x -> PartyLeader.bitPut x+  Pickup x -> Pickup.bitPut x+  PickupInfo x -> PickupInfo.bitPut x+  PickupNew x -> PickupNew.bitPut x+  PlayerHistoryKey x -> PlayerHistoryKey.bitPut x+  PrivateMatchSettings x -> PrivateMatchSettings.bitPut x+  QWord x -> QWord.bitPut x+  RepStatTitle x -> RepStatTitle.bitPut x+  Reservation x -> Reservation.bitPut x+  RigidBodyState x -> RigidBodyState.bitPut x+  Rotation x -> Rotation.bitPut x+  StatEvent x -> StatEvent.bitPut x+  String x -> String.bitPut x+  TeamPaint x -> TeamPaint.bitPut x+  Title x -> Title.bitPut x+  UniqueId x -> UniqueId.bitPut x+  WeldedInfo x -> WeldedInfo.bitPut x++bitGet ::+  Version.Version ->+  Maybe Str.Str ->+  Map.Map U32.U32 Str.Str ->+  Str.Str ->+  BitGet.BitGet AttributeValue+bitGet version buildVersion objectMap name =+  BitGet.label "AttributeValue" $ do+    constructor <- case Map.lookup (Str.toText name) Data.attributeTypes of+      Nothing ->+        BitGet.throw . UnknownAttribute.UnknownAttribute $ Str.toString name+      Just x -> pure x+    case constructor of+      AttributeType.AppliedDamage ->+        fmap AppliedDamage $ AppliedDamage.bitGet version+      AttributeType.Boolean -> fmap Boolean Boolean.bitGet+      AttributeType.Boost -> fmap Boost Boost.bitGet+      AttributeType.Byte -> fmap Byte Byte.bitGet+      AttributeType.CamSettings ->+        fmap CamSettings $ CamSettings.bitGet version+      AttributeType.ClubColors -> fmap ClubColors ClubColors.bitGet+      AttributeType.CustomDemolish ->+        fmap CustomDemolish $ CustomDemolish.bitGet version+      AttributeType.CustomDemolishExtended -> fmap CustomDemolishExtended $ CustomDemolishExtended.bitGet version+      AttributeType.DamageState ->+        fmap DamageState $ DamageState.bitGet version+      AttributeType.Demolish -> fmap Demolish $ Demolish.bitGet version+      AttributeType.Enum -> fmap Enum Enum.bitGet+      AttributeType.Explosion -> fmap Explosion $ Explosion.bitGet version+      AttributeType.ExtendedExplosion ->+        fmap ExtendedExplosion $ ExtendedExplosion.bitGet version+      AttributeType.FlaggedInt -> fmap FlaggedInt FlaggedInt.bitGet+      AttributeType.FlaggedByte -> fmap FlaggedByte FlaggedByte.bitGet+      AttributeType.Float -> fmap Float Float.bitGet+      AttributeType.GameMode -> fmap GameMode $ GameMode.bitGet version+      AttributeType.GameServer ->+        fmap GameServer $ GameServer.bitGet buildVersion+      AttributeType.Int -> fmap Int Int.bitGet+      AttributeType.Int64 -> fmap Int64 Int64.bitGet+      AttributeType.Loadout -> fmap Loadout Loadout.bitGet+      AttributeType.LoadoutOnline ->+        fmap LoadoutOnline $ LoadoutOnline.bitGet version objectMap+      AttributeType.Loadouts -> fmap Loadouts Loadouts.bitGet+      AttributeType.LoadoutsOnline ->+        fmap LoadoutsOnline $ LoadoutsOnline.bitGet version objectMap+      AttributeType.Location -> fmap Location $ Location.bitGet version+      AttributeType.MusicStinger -> fmap MusicStinger MusicStinger.bitGet+      AttributeType.PartyLeader ->+        fmap PartyLeader $ PartyLeader.bitGet version+      AttributeType.Pickup -> fmap Pickup Pickup.bitGet+      AttributeType.PickupInfo -> fmap PickupInfo PickupInfo.bitGet+      AttributeType.PickupNew -> fmap PickupNew PickupNew.bitGet+      AttributeType.PlayerHistoryKey ->+        fmap PlayerHistoryKey PlayerHistoryKey.bitGet+      AttributeType.PrivateMatchSettings ->+        fmap PrivateMatchSettings PrivateMatchSettings.bitGet+      AttributeType.QWord -> fmap QWord QWord.bitGet+      AttributeType.RepStatTitle -> fmap RepStatTitle RepStatTitle.bitGet+      AttributeType.Reservation ->+        fmap Reservation $ Reservation.bitGet version+      AttributeType.RigidBodyState ->+        fmap RigidBodyState $ RigidBodyState.bitGet version+      AttributeType.Rotation -> fmap Rotation Rotation.bitGet+      AttributeType.StatEvent -> fmap StatEvent StatEvent.bitGet+      AttributeType.String -> fmap String String.bitGet+      AttributeType.TeamPaint -> fmap TeamPaint TeamPaint.bitGet+      AttributeType.Title -> fmap Title Title.bitGet+      AttributeType.UniqueId -> fmap UniqueId $ UniqueId.bitGet version+      AttributeType.WeldedInfo -> fmap WeldedInfo $ WeldedInfo.bitGet version
+ src/lib/Rattletrap/Type/Cache.hs view
@@ -0,0 +1,65 @@+module Rattletrap.Type.Cache where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.AttributeMapping as AttributeMapping+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Cache = Cache+  { classId :: U32.U32,+    parentCacheId :: U32.U32,+    cacheId :: U32.U32,+    attributeMappings :: RList.List AttributeMapping.AttributeMapping+  }+  deriving (Eq, Show)++instance Json.FromJSON Cache where+  parseJSON = Json.withObject "Cache" $ \object -> do+    classId <- Json.required object "class_id"+    parentCacheId <- Json.required object "parent_cache_id"+    cacheId <- Json.required object "cache_id"+    attributeMappings <- Json.required object "attribute_mappings"+    pure Cache {classId, parentCacheId, cacheId, attributeMappings}++instance Json.ToJSON Cache where+  toJSON x =+    Json.object+      [ Json.pair "class_id" $ classId x,+        Json.pair "parent_cache_id" $ parentCacheId x,+        Json.pair "cache_id" $ cacheId x,+        Json.pair "attribute_mappings" $ attributeMappings x+      ]++schema :: Schema.Schema+schema =+  Schema.named "cache" $+    Schema.object+      [ (Json.pair "class_id" $ Schema.ref U32.schema, True),+        (Json.pair "parent_cache_id" $ Schema.ref U32.schema, True),+        (Json.pair "cache_id" $ Schema.ref U32.schema, True),+        ( Json.pair "attribute_mappings" . Schema.json $+            RList.schema+              AttributeMapping.schema,+          True+        )+      ]++bytePut :: Cache -> BytePut.BytePut+bytePut x =+  U32.bytePut (classId x)+    <> U32.bytePut (parentCacheId x)+    <> U32.bytePut (cacheId x)+    <> RList.bytePut AttributeMapping.bytePut (attributeMappings x)++byteGet :: ByteGet.ByteGet Cache+byteGet = ByteGet.label "Cache" $ do+  classId <- ByteGet.label "classId" U32.byteGet+  parentCacheId <- ByteGet.label "parentCacheId" U32.byteGet+  cacheId <- ByteGet.label "cacheId" U32.byteGet+  attributeMappings <-+    ByteGet.label "attributeMappings" $+      RList.byteGet AttributeMapping.byteGet+  pure Cache {classId, parentCacheId, cacheId, attributeMappings}
+ src/lib/Rattletrap/Type/ClassAttributeMap.hs view
@@ -0,0 +1,323 @@+module Rattletrap.Type.ClassAttributeMap where++import qualified Data.IntMap as IntMap+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set+import qualified Data.Text as Text+import qualified Data.Tuple as Tuple+import qualified Rattletrap.Data as Data+import qualified Rattletrap.Type.AttributeMapping as AttributeMapping+import qualified Rattletrap.Type.Cache as Cache+import qualified Rattletrap.Type.ClassMapping as ClassMapping+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32++-- | This data structure holds all the information about classes, objects, and+-- attributes in the replay. The class hierarchy is not fixed; it is encoded+-- in the 'Rattletrap.Content.Content'. Similarly, the attributes that belong+-- to each class are not fixed either. Converting the raw data into a usable+-- structure is tedious; see 'make'.+data ClassAttributeMap = ClassAttributeMap+  { -- | A map from object IDs to their names.+    objectMap :: Map.Map U32.U32 Str.Str,+    -- | A map from object IDs to their class IDs.+    objectClassMap :: Map.Map U32.U32 U32.U32,+    -- | A map from class IDs to a map from attribute stream IDs to attribute+    -- IDs.+    value :: Map.Map U32.U32 (Map.Map U32.U32 U32.U32),+    nameMap :: IntMap.IntMap Str.Str+  }+  deriving (Eq, Show)++type Bimap l r = (Map.Map l r, Map.Map r l)++bimap :: (Ord l, Ord r) => [(l, r)] -> Bimap l r+bimap xs = (Map.fromList xs, Map.fromList (fmap Tuple.swap xs))++lookupL :: (Ord l) => l -> Bimap l r -> Maybe r+lookupL k = Map.lookup k . fst++lookupR :: (Ord r) => r -> Bimap l r -> Maybe l+lookupR k = Map.lookup k . snd++-- | Makes a 'ClassAttributeMap' given the necessary fields from the+-- 'Rattletrap.Content.Content'.+make ::+  -- | From 'Rattletrap.Content.objects'.+  RList.List Str.Str ->+  -- | From 'Rattletrap.Content.classMappings'.+  RList.List ClassMapping.ClassMapping ->+  -- | From 'Rattletrap.Content.caches'.+  RList.List Cache.Cache ->+  -- | From 'Rattletrap.Content.names'.+  RList.List Str.Str ->+  ClassAttributeMap+make objects classMappings caches names =+  let objectMap_ = makeObjectMap objects+      classMap = makeClassMap classMappings+      objectClassMap_ = makeObjectClassMap objectMap_ classMap+      classCache = makeClassCache classMap caches+      attributeMap = makeAttributeMap caches+      classIds = fmap (\(_, classId, _, _) -> classId) classCache+      parentMap = makeParentMap classCache+      value_ =+        Map.fromList+          ( fmap+              ( \classId ->+                  let ownAttributes =+                        Maybe.fromMaybe Map.empty (Map.lookup classId attributeMap)+                      parentsAttributes = case Map.lookup classId parentMap of+                        Nothing -> []+                        Just parentClassIds ->+                          fmap+                            ( \parentClassId ->+                                Maybe.fromMaybe+                                  Map.empty+                                  (Map.lookup parentClassId attributeMap)+                            )+                            parentClassIds+                      attributes = ownAttributes : parentsAttributes+                   in (classId, Map.fromList (concatMap Map.toList attributes))+              )+              classIds+          )+      nameMap_ = makeNameMap names+   in ClassAttributeMap objectMap_ objectClassMap_ value_ nameMap_++makeNameMap :: RList.List Str.Str -> IntMap.IntMap Str.Str+makeNameMap names =+  IntMap.fromDistinctAscList (zip [0 ..] (RList.toList names))++getName :: IntMap.IntMap Str.Str -> U32.U32 -> Maybe Str.Str+getName nameMap_ nameIndex =+  IntMap.lookup (fromIntegral (U32.toWord32 nameIndex)) nameMap_++makeObjectClassMap ::+  Map.Map U32.U32 Str.Str ->+  Bimap U32.U32 Str.Str ->+  Map.Map U32.U32 U32.U32+makeObjectClassMap objectMap_ classMap = do+  let objectIds = Map.keys objectMap_+  let classIds = fmap (getClassId objectMap_ classMap) objectIds+  let rawPairs = zip objectIds classIds+  let pairs =+        Maybe.mapMaybe+          ( \(objectId, maybeClassId) -> case maybeClassId of+              Nothing -> Nothing+              Just classId -> Just (objectId, classId)+          )+          rawPairs+  Map.fromList pairs++getClassId ::+  Map.Map U32.U32 Str.Str ->+  Bimap U32.U32 Str.Str ->+  U32.U32 ->+  Maybe U32.U32+getClassId objectMap_ classMap objectId = do+  objectName <- getObjectName objectMap_ objectId+  className <- getClassName objectName+  lookupR className classMap++makeClassCache ::+  Bimap U32.U32 Str.Str ->+  RList.List Cache.Cache ->+  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)]+makeClassCache classMap caches =+  fmap+    ( \cache ->+        let classId = Cache.classId cache+         in ( lookupL classId classMap,+              classId,+              Cache.cacheId cache,+              Cache.parentCacheId cache+            )+    )+    (RList.toList caches)++makeClassMap :: RList.List ClassMapping.ClassMapping -> Bimap U32.U32 Str.Str+makeClassMap classMappings =+  bimap+    ( fmap+        ( \classMapping ->+            (ClassMapping.streamId classMapping, ClassMapping.name classMapping)+        )+        (RList.toList classMappings)+    )++makeAttributeMap ::+  RList.List Cache.Cache -> Map.Map U32.U32 (Map.Map U32.U32 U32.U32)+makeAttributeMap caches =+  Map.fromList+    ( fmap+        ( \cache ->+            ( Cache.classId cache,+              Map.fromList+                ( fmap+                    ( \attributeMapping ->+                        ( AttributeMapping.streamId attributeMapping,+                          AttributeMapping.objectId attributeMapping+                        )+                    )+                    (RList.toList (Cache.attributeMappings cache))+                )+            )+        )+        (RList.toList caches)+    )++makeShallowParentMap ::+  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Map.Map U32.U32 U32.U32+makeShallowParentMap classCache =+  Map.fromList+    ( Maybe.mapMaybe+        ( \xs -> case xs of+            [] -> Nothing+            (maybeClassName, classId, _, parentCacheId) : rest -> do+              parentClassId <- getParentClass maybeClassName parentCacheId rest+              pure (classId, parentClassId)+        )+        (List.tails (reverse classCache))+    )++makeParentMap ::+  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Map.Map U32.U32 [U32.U32]+makeParentMap classCache =+  let shallowParentMap = makeShallowParentMap classCache+   in Map.mapWithKey+        (\classId _ -> getParentClasses shallowParentMap classId)+        shallowParentMap++getParentClasses :: Map.Map U32.U32 U32.U32 -> U32.U32 -> [U32.U32]+getParentClasses shallowParentMap classId =+  case Map.lookup classId shallowParentMap of+    Nothing -> []+    Just parentClassId ->+      parentClassId : getParentClasses shallowParentMap parentClassId++getParentClass ::+  Maybe Str.Str ->+  U32.U32 ->+  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] ->+  Maybe U32.U32+getParentClass maybeClassName parentCacheId xs = case maybeClassName of+  Nothing -> getParentClassById parentCacheId xs+  Just className -> getParentClassByName className parentCacheId xs++getParentClassById ::+  U32.U32 -> [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] -> Maybe U32.U32+getParentClassById parentCacheId xs =+  case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of+    [] ->+      if parentCacheId == U32.fromWord32 0+        then Nothing+        else+          getParentClassById+            (U32.fromWord32 (U32.toWord32 parentCacheId - 1))+            xs+    (_, parentClassId, _, _) : _ -> Just parentClassId++getParentClassByName ::+  Str.Str ->+  U32.U32 ->+  [(Maybe Str.Str, U32.U32, U32.U32, U32.U32)] ->+  Maybe U32.U32+getParentClassByName className parentCacheId xs =+  case Map.lookup (Str.toText className) Data.parentClasses of+    Nothing -> getParentClassById parentCacheId xs+    Just parentClassName ->+      Maybe.maybe+        (getParentClassById parentCacheId xs)+        Just+        ( Maybe.listToMaybe+            ( fmap+                (\(_, parentClassId, _, _) -> parentClassId)+                ( filter+                    (\(_, _, cacheId, _) -> cacheId <= parentCacheId)+                    ( filter+                        ( \(maybeClassName, _, _, _) ->+                            fmap Str.toText maybeClassName == Just parentClassName+                        )+                        xs+                    )+                )+            )+        )++makeObjectMap :: RList.List Str.Str -> Map.Map U32.U32 Str.Str+makeObjectMap objects =+  Map.fromAscList (zip (fmap U32.fromWord32 [0 ..]) (RList.toList objects))++getObjectName :: Map.Map U32.U32 Str.Str -> U32.U32 -> Maybe Str.Str+getObjectName objectMap_ objectId = Map.lookup objectId objectMap_++getClassName :: Str.Str -> Maybe Str.Str+getClassName rawObjectName =+  fmap Str.fromText $+    Map.lookup+      (Str.toText $ normalizeObjectName rawObjectName)+      Data.objectClasses++normalizeObjectName :: Str.Str -> Str.Str+normalizeObjectName objectName =+  let name = Str.toText objectName+      crowdActor = Text.pack "TheWorld:PersistentLevel.CrowdActor_TA"+      crowdManager = Text.pack "TheWorld:PersistentLevel.CrowdManager_TA"+      boostPickup = Text.pack "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"+      mapScoreboard = Text.pack "TheWorld:PersistentLevel.InMapScoreboard_TA"+      breakout = Text.pack "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"+   in if Text.isInfixOf crowdActor name+        then Str.fromText crowdActor+        else+          if Text.isInfixOf crowdManager name+            then Str.fromText crowdManager+            else+              if Text.isInfixOf boostPickup name+                then Str.fromText boostPickup+                else+                  if Text.isInfixOf mapScoreboard name+                    then Str.fromText mapScoreboard+                    else+                      if Text.isInfixOf breakout name+                        then Str.fromText breakout+                        else objectName++classHasLocation :: Str.Str -> Bool+classHasLocation className =+  Set.member (Str.toText className) Data.classesWithLocation++classHasRotation :: Str.Str -> Bool+classHasRotation className =+  Set.member (Str.toText className) Data.classesWithRotation++getAttributeIdLimit :: Map.Map U32.U32 U32.U32 -> Maybe Word+getAttributeIdLimit attributeMap = do+  ((streamId, _), _) <- Map.maxViewWithKey attributeMap+  pure (fromIntegral (U32.toWord32 streamId))++getAttributeName ::+  ClassAttributeMap ->+  Map.Map U32.U32 U32.U32 ->+  CompressedWord.CompressedWord ->+  Maybe Str.Str+getAttributeName classAttributeMap attributeMap streamId = do+  let key = U32.fromWord32 (fromIntegral (CompressedWord.value streamId))+  attributeId <- Map.lookup key attributeMap+  let objectMap_ = objectMap classAttributeMap+  Map.lookup attributeId objectMap_++getAttributeMap ::+  ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  CompressedWord.CompressedWord ->+  Maybe (Map.Map U32.U32 U32.U32)+getAttributeMap classAttributeMap actorMap actorId = do+  objectId <- Map.lookup actorId actorMap+  let objectClassMap_ = objectClassMap classAttributeMap+  classId <- Map.lookup objectId objectClassMap_+  let value_ = value classAttributeMap+  Map.lookup classId value_
+ src/lib/Rattletrap/Type/ClassMapping.hs view
@@ -0,0 +1,41 @@+module Rattletrap.Type.ClassMapping where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data ClassMapping = ClassMapping+  { name :: Str.Str,+    streamId :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON ClassMapping where+  parseJSON = Json.withObject "ClassMapping" $ \object -> do+    name <- Json.required object "name"+    streamId <- Json.required object "stream_id"+    pure ClassMapping {name, streamId}++instance Json.ToJSON ClassMapping where+  toJSON x =+    Json.object [Json.pair "name" $ name x, Json.pair "stream_id" $ streamId x]++schema :: Schema.Schema+schema =+  Schema.named "classMapping" $+    Schema.object+      [ (Json.pair "name" $ Schema.ref Str.schema, True),+        (Json.pair "stream_id" $ Schema.ref U32.schema, True)+      ]++bytePut :: ClassMapping -> BytePut.BytePut+bytePut x = Str.bytePut (name x) <> U32.bytePut (streamId x)++byteGet :: ByteGet.ByteGet ClassMapping+byteGet = ByteGet.label "ClassMapping" $ do+  name <- ByteGet.label "name" Str.byteGet+  streamId <- ByteGet.label "streamId" U32.byteGet+  pure ClassMapping {name, streamId}
+ src/lib/Rattletrap/Type/CompressedWord.hs view
@@ -0,0 +1,89 @@+module Rattletrap.Type.CompressedWord where++import qualified Data.Bits as Bits+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++-- | Although there's no guarantee that these values will not overflow, it's+-- exceptionally unlikely. Most 'CompressedWord's are very small.+data CompressedWord = CompressedWord+  { limit :: Word,+    value :: Word+  }+  deriving (Eq, Ord, Show)++instance Json.FromJSON CompressedWord where+  parseJSON = Json.withObject "CompressedWord" $ \object -> do+    limit <- Json.required object "limit"+    value <- Json.required object "value"+    pure CompressedWord {limit, value}++instance Json.ToJSON CompressedWord where+  toJSON x =+    Json.object [Json.pair "limit" $ limit x, Json.pair "value" $ value x]++schema :: Schema.Schema+schema =+  Schema.named "compressedWord" $+    Schema.object+      [ (Json.pair "limit" $ Json.object [Json.pair "type" "integer"], True),+        (Json.pair "value" $ Json.object [Json.pair "type" "integer"], True)+      ]++bitPut :: CompressedWord -> BitPut.BitPut+bitPut compressedWord =+  let limit_ = limit compressedWord+      value_ = value compressedWord+      maxBits = getMaxBits limit_+   in putCompressedWordStep limit_ value_ maxBits 0 0++putCompressedWordStep :: Word -> Word -> Int -> Int -> Word -> BitPut.BitPut+putCompressedWordStep limit_ value_ maxBits position soFar =+  if position < maxBits+    then do+      let x = Bits.shiftL 1 position :: Word+      if maxBits > 1 && position == maxBits - 1 && soFar + x > limit_+        then mempty+        else+          let bit = Bits.testBit value_ position+              delta = if bit then x else 0+           in BitPut.bool bit+                <> putCompressedWordStep+                  limit_+                  value_+                  maxBits+                  (position + 1)+                  (soFar + delta)+    else mempty++getMaxBits :: Word -> Int+getMaxBits x =+  let n :: Int+      n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))+   in if x < 1024 && x == 2 ^ n then n + 1 else n++bitGet :: Word -> BitGet.BitGet CompressedWord+bitGet = bitGetNew++bitGetNew :: Word -> BitGet.BitGet CompressedWord+bitGetNew limit = do+  value <-+    if limit < 1+      then pure 0+      else do+        let numBits =+              max (0 :: Int)+                . subtract 1+                . ceiling+                . logBase (2 :: Double)+                $ fromIntegral limit+        partial <- BitGet.bits numBits+        let next = partial + Bits.shiftL 1 numBits+        if next > limit+          then pure partial+          else do+            x <- BitGet.bool+            pure $ if x then next else partial+  pure CompressedWord {limit, value}
+ src/lib/Rattletrap/Type/CompressedWordVector.hs view
@@ -0,0 +1,50 @@+module Rattletrap.Type.CompressedWordVector where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Utility.Json as Json++data CompressedWordVector = CompressedWordVector+  { x :: CompressedWord.CompressedWord,+    y :: CompressedWord.CompressedWord,+    z :: CompressedWord.CompressedWord+  }+  deriving (Eq, Show)++instance Json.FromJSON CompressedWordVector where+  parseJSON = Json.withObject "CompressedWordVector" $ \object -> do+    x <- Json.required object "x"+    y <- Json.required object "y"+    z <- Json.required object "z"+    pure CompressedWordVector {x, y, z}++instance Json.ToJSON CompressedWordVector where+  toJSON a =+    Json.object [Json.pair "x" $ x a, Json.pair "y" $ y a, Json.pair "z" $ z a]++schema :: Schema.Schema+schema =+  Schema.named "compressed-word-vector" $+    Schema.object+      [ (Json.pair "x" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "y" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "z" $ Schema.ref CompressedWord.schema, True)+      ]++bitPut :: CompressedWordVector -> BitPut.BitPut+bitPut compressedWordVector =+  CompressedWord.bitPut (x compressedWordVector)+    <> CompressedWord.bitPut (y compressedWordVector)+    <> CompressedWord.bitPut (z compressedWordVector)++bitGet :: BitGet.BitGet CompressedWordVector+bitGet = do+  x <- CompressedWord.bitGet limit+  y <- CompressedWord.bitGet limit+  z <- CompressedWord.bitGet limit+  pure CompressedWordVector {x, y, z}++limit :: Word+limit = 65536
+ src/lib/Rattletrap/Type/Content.hs view
@@ -0,0 +1,247 @@+module Rattletrap.Type.Content where++import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Cache as Cache+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.ClassMapping as ClassMapping+import qualified Rattletrap.Type.Frame as Frame+import qualified Rattletrap.Type.Keyframe as Keyframe+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Mark as Mark+import qualified Rattletrap.Type.Message as Message+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Bytes as Bytes+import qualified Rattletrap.Utility.Json as Json++type Content = ContentWith (RList.List Frame.Frame)++-- | Contains low-level game data about a 'Rattletrap.Replay.Replay'.+data ContentWith frames = Content+  { -- | This typically only has one element, like @stadium_oob_audio_map@.+    levels :: RList.List Str.Str,+    -- | A list of which frames are key frames. Although they aren't necessary+    -- for replay, key frames are frames that replicate every actor. They+    -- typically happen once every 10 seconds.+    keyframes :: RList.List Keyframe.Keyframe,+    -- | The size of the stream in bytes. This is only really necessary because+    -- the stream has some arbitrary amount of padding at the end.+    streamSize :: U32.U32,+    -- | The actual game data. This is where all the interesting information is.+    frames :: frames,+    -- | Debugging messages. In newer replays, this is always empty.+    messages :: RList.List Message.Message,+    -- | Tick marks shown on the scrubber when watching a replay.+    marks :: RList.List Mark.Mark,+    -- | A list of @.upk@ files to load, like+    -- @..\\..\\TAGame\\CookedPCConsole\\Stadium_P.upk@.+    packages :: RList.List Str.Str,+    -- | Objects in the stream. Used for the+    -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.+    objects :: RList.List Str.Str,+    -- | It's not clear what these are used for. This list is usually not empty,+    -- but appears unused otherwise.+    names :: RList.List Str.Str,+    -- | A mapping between classes and their ID in the stream. Used for the+    -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.+    classMappings :: RList.List ClassMapping.ClassMapping,+    -- | A list of classes along with their parent classes and attributes. Used+    -- for the 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.+    caches :: RList.List Cache.Cache,+    unknown :: [Word.Word8]+  }+  deriving (Eq, Show)++instance (Json.FromJSON frames) => Json.FromJSON (ContentWith frames) where+  parseJSON = Json.withObject "Content" $ \object -> do+    levels <- Json.required object "levels"+    keyframes <- Json.required object "key_frames"+    streamSize <- Json.required object "stream_size"+    frames <- Json.required object "frames"+    messages <- Json.required object "messages"+    marks <- Json.required object "marks"+    packages <- Json.required object "packages"+    objects <- Json.required object "objects"+    names <- Json.required object "names"+    classMappings <- Json.required object "class_mappings"+    caches <- Json.required object "caches"+    unknown <- Json.required object "unknown"+    pure+      Content+        { levels,+          keyframes,+          streamSize,+          frames,+          messages,+          marks,+          packages,+          objects,+          names,+          classMappings,+          caches,+          unknown+        }++instance (Json.ToJSON frames) => Json.ToJSON (ContentWith frames) where+  toJSON x =+    Json.object+      [ Json.pair "levels" $ levels x,+        Json.pair "key_frames" $ keyframes x,+        Json.pair "stream_size" $ streamSize x,+        Json.pair "frames" $ frames x,+        Json.pair "messages" $ messages x,+        Json.pair "marks" $ marks x,+        Json.pair "packages" $ packages x,+        Json.pair "objects" $ objects x,+        Json.pair "names" $ names x,+        Json.pair "class_mappings" $ classMappings x,+        Json.pair "caches" $ caches x,+        Json.pair "unknown" $ unknown x+      ]++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named "content" $+    Schema.object+      [ (Json.pair "levels" . Schema.json $ RList.schema Str.schema, True),+        (Json.pair "key_frames" . Schema.json $ RList.schema Keyframe.schema, True),+        (Json.pair "stream_size" $ Schema.ref U32.schema, True),+        (Json.pair "frames" $ Schema.json s, True),+        (Json.pair "messages" . Schema.json $ RList.schema Message.schema, True),+        (Json.pair "marks" . Schema.json $ RList.schema Mark.schema, True),+        (Json.pair "packages" . Schema.json $ RList.schema Str.schema, True),+        (Json.pair "objects" . Schema.json $ RList.schema Str.schema, True),+        (Json.pair "names" . Schema.json $ RList.schema Str.schema, True),+        ( Json.pair "class_mappings" . Schema.json $+            RList.schema+              ClassMapping.schema,+          True+        ),+        (Json.pair "caches" . Schema.json $ RList.schema Cache.schema, True),+        (Json.pair "unknown" . Schema.json $ Schema.array U8.schema, True)+      ]++empty :: Content+empty =+  Content+    { levels = RList.empty,+      keyframes = RList.empty,+      streamSize = U32.fromWord32 0,+      frames = RList.empty,+      messages = RList.empty,+      marks = RList.empty,+      packages = RList.empty,+      objects = RList.empty,+      names = RList.empty,+      classMappings = RList.empty,+      caches = RList.empty,+      unknown = []+    }++bytePut :: Content -> BytePut.BytePut+bytePut x =+  RList.bytePut Str.bytePut (levels x)+    <> RList.bytePut Keyframe.bytePut (keyframes x)+    <> putFrames x+    <> RList.bytePut Message.bytePut (messages x)+    <> RList.bytePut Mark.bytePut (marks x)+    <> RList.bytePut Str.bytePut (packages x)+    <> RList.bytePut Str.bytePut (objects x)+    <> RList.bytePut Str.bytePut (names x)+    <> RList.bytePut ClassMapping.bytePut (classMappings x)+    <> RList.bytePut Cache.bytePut (caches x)+    <> foldMap BytePut.word8 (unknown x)++putFrames :: Content -> BytePut.BytePut+putFrames x =+  let stream =+        BytePut.toByteString . BitPut.toBytePut . Frame.putFrames $ frames x+      -- This is a little strange. When parsing a binary replay, the stream size+      -- is given before the stream itself. When generating the JSON, the stream+      -- size is included. That allows a bit-for-bit identical binary replay to+      -- be generated from the JSON. However if you modify the JSON before+      -- converting it back into binary, the stream size might be different.+      --+      -- If it was possible to know how much padding the stream required without+      -- carrying it along as extra data on the side, this logic could go away.+      -- Unforunately that isn't currently known. See this issue for details:+      -- <https://github.com/tfausak/rattletrap/issues/171>.+      expectedStreamSize = streamSize x+      actualStreamSize =+        U32.fromWord32 . fromIntegral $ ByteString.length stream+      streamSize_ =+        U32.fromWord32 $+          max (U32.toWord32 expectedStreamSize) (U32.toWord32 actualStreamSize)+   in U32.bytePut streamSize_+        <> BytePut.byteString (Bytes.padBytes (U32.toWord32 streamSize_) stream)++byteGet ::+  Maybe Str.Str ->+  -- | Version numbers, usually from 'Rattletrap.Header.getVersion'.+  Version.Version ->+  -- | The number of frames in the stream, usually from+  -- 'Rattletrap.Header.getNumFrames'.+  Int ->+  -- | The maximum number of channels in the stream, usually from+  -- 'Rattletrap.Header.getMaxChannels'.+  Word ->+  -- | 'Rattletrap.Header.getBuildVersion'+  Maybe Str.Str ->+  ByteGet.ByteGet Content+byteGet matchType version numFrames maxChannels buildVersion =+  ByteGet.label "Content" $ do+    levels <- ByteGet.label "levels" $ RList.byteGet Str.byteGet+    keyframes <- ByteGet.label "keyframes" $ RList.byteGet Keyframe.byteGet+    streamSize <- ByteGet.label "streamSize" U32.byteGet+    stream <-+      ByteGet.label "stream" . ByteGet.byteString . fromIntegral $+        U32.toWord32+          streamSize+    messages <- ByteGet.label "messages" $ RList.byteGet Message.byteGet+    marks <- ByteGet.label "marks" $ RList.byteGet Mark.byteGet+    packages <- ByteGet.label "packages" $ RList.byteGet Str.byteGet+    objects <- ByteGet.label "objects" $ RList.byteGet Str.byteGet+    names <- ByteGet.label "names" $ RList.byteGet Str.byteGet+    classMappings <-+      ByteGet.label "classMappings" $+        RList.byteGet ClassMapping.byteGet+    caches <- ByteGet.label "caches" $ RList.byteGet Cache.byteGet+    let classAttributeMap =+          ClassAttributeMap.make objects classMappings caches names+        getFrames =+          BitGet.toByteGet $+            Frame.decodeFramesBits+              matchType+              version+              buildVersion+              numFrames+              maxChannels+              classAttributeMap+    frames <- ByteGet.label "frames" $ ByteGet.embed getFrames stream+    unknown <-+      ByteGet.label "unknown" $+        fmap LazyByteString.unpack ByteGet.remaining+    pure+      Content+        { levels,+          keyframes,+          streamSize,+          frames,+          messages,+          marks,+          packages,+          objects,+          names,+          classMappings,+          caches,+          unknown+        }
+ src/lib/Rattletrap/Type/Dictionary.hs view
@@ -0,0 +1,76 @@+module Rattletrap.Type.Dictionary where++import qualified Data.Text as Text+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++data Dictionary a = Dictionary+  { elements :: RList.List (Str.Str, a),+    lastKey :: Str.Str+  }+  deriving (Eq, Show)++instance (Json.FromJSON a) => Json.FromJSON (Dictionary a) where+  parseJSON = Json.withObject "Dictionary" $ \o -> do+    elements <- Json.required o "elements"+    lastKey <- Json.required o "last_key"+    pure Dictionary {elements = elements, lastKey = lastKey}++instance (Json.ToJSON a) => Json.ToJSON (Dictionary a) where+  toJSON x =+    Json.object+      [ Json.pair "elements" . RList.toList $ elements x,+        Json.pair "last_key" $ lastKey x+      ]++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named ("dictionary-" <> Text.unpack (Schema.name s)) $+    Schema.object+      [ (Json.pair "elements" . Schema.json . Schema.array $ elementSchema s, True),+        (Json.pair "last_key" $ Schema.ref Str.schema, True)+      ]++elementSchema :: Schema.Schema -> Schema.Schema+elementSchema s =+  Schema.named ("dictionary-element-" <> Text.unpack (Schema.name s)) $+    Schema.tuple+      [ Schema.ref Str.schema,+        Schema.ref s+      ]++lookup :: Str.Str -> Dictionary a -> Maybe a+lookup k = Prelude.lookup k . RList.toList . elements++bytePut :: (a -> BytePut.BytePut) -> Dictionary a -> BytePut.BytePut+bytePut f x =+  foldMap (\(k, v) -> Str.bytePut k <> f v) (RList.toList $ elements x)+    <> Str.bytePut (lastKey x)++byteGet :: ByteGet.ByteGet a -> ByteGet.ByteGet (Dictionary a)+byteGet = ByteGet.label "Dictionary" . byteGetWith 0 []++byteGetWith ::+  Int ->+  [(Int, (Str.Str, a))] ->+  ByteGet.ByteGet a ->+  ByteGet.ByteGet (Dictionary a)+byteGetWith i xs f = do+  k <- ByteGet.label ("key (" <> show i <> ")") Str.byteGet+  if isNone k+    then+      pure+        Dictionary+          { elements = RList.fromList . reverse $ fmap snd xs,+            lastKey = k+          }+    else do+      v <- ByteGet.label ("value (" <> Str.toString k <> ")") f+      byteGetWith (i + 1) ((i, (k, v)) : xs) f++isNone :: Str.Str -> Bool+isNone = (== Text.pack "None") . Text.filter (/= '\x00') . Str.toText
+ src/lib/Rattletrap/Type/F32.hs view
@@ -0,0 +1,39 @@+module Rattletrap.Type.F32 where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype F32+  = F32 Float+  deriving (Eq, Show)++instance Json.FromJSON F32 where+  parseJSON = fmap fromFloat . Json.parseJSON++instance Json.ToJSON F32 where+  toJSON = Json.toJSON . toFloat++schema :: Schema.Schema+schema = Schema.named "f32" $ Json.object [Json.pair "type" "number"]++fromFloat :: Float -> F32+fromFloat = F32++toFloat :: F32 -> Float+toFloat (F32 x) = x++bytePut :: F32 -> BytePut.BytePut+bytePut = BytePut.float . toFloat++bitPut :: F32 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet F32+byteGet = ByteGet.label "F32" $ fmap fromFloat ByteGet.float++bitGet :: BitGet.BitGet F32+bitGet = BitGet.fromByteGet byteGet 4
+ src/lib/Rattletrap/Type/Frame.hs view
@@ -0,0 +1,140 @@+module Rattletrap.Type.Frame where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Replication as Replication+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Frame = Frame+  { -- | Time in seconds since the beginning of the match.+    time :: F32.F32,+    -- | Time in seconds since the last frame. Usually about 0.03 since there+    -- are 30 frames per second.+    delta :: F32.F32,+    replications :: RList.List Replication.Replication+  }+  deriving (Eq, Show)++instance Json.FromJSON Frame where+  parseJSON = Json.withObject "Frame" $ \object -> do+    time <- Json.required object "time"+    delta <- Json.required object "delta"+    replications <- Json.required object "replications"+    pure Frame {time, delta, replications}++instance Json.ToJSON Frame where+  toJSON x =+    Json.object+      [ Json.pair "time" $ time x,+        Json.pair "delta" $ delta x,+        Json.pair "replications" $ replications x+      ]++schema :: Schema.Schema+schema =+  Schema.named "frame" $+    Schema.object+      [ (Json.pair "time" $ Schema.ref F32.schema, True),+        (Json.pair "delta" $ Schema.ref F32.schema, True),+        ( Json.pair "replications" . Schema.json $ RList.schema Replication.schema,+          True+        )+      ]++putFrames :: RList.List Frame -> BitPut.BitPut+putFrames = foldMap bitPut . RList.toList++bitPut :: Frame -> BitPut.BitPut+bitPut frame =+  F32.bitPut (time frame)+    <> F32.bitPut (delta frame)+    <> Replication.putReplications (replications frame)++decodeFramesBits ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Int ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  BitGet.BitGet (RList.List Frame)+decodeFramesBits matchType version buildVersion count limit classes =+  fmap snd $+    decodeFramesBitsWith+      matchType+      version+      buildVersion+      count+      limit+      classes+      Map.empty+      0+      []++decodeFramesBitsWith ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Int ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  Int ->+  [Frame] ->+  BitGet.BitGet+    ( Map.Map+        CompressedWord.CompressedWord+        U32.U32,+      RList.List Frame+    )+decodeFramesBitsWith matchType version buildVersion count limit classes actorMap index frames =+  if index >= count+    then pure (actorMap, RList.fromList $ reverse frames)+    else do+      (newActorMap, frame) <-+        BitGet.label ("element (" <> show index <> ")") $+          bitGet matchType version buildVersion limit classes actorMap+      decodeFramesBitsWith+        matchType+        version+        buildVersion+        count+        limit+        classes+        newActorMap+        (index + 1)+        $ frame+          : frames++bitGet ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  BitGet.BitGet+    (Map.Map CompressedWord.CompressedWord U32.U32, Frame)+bitGet matchType version buildVersion limit classes actorMap =+  BitGet.label "Frame" $ do+    time <- BitGet.label "time" F32.bitGet+    delta <- BitGet.label "delta" F32.bitGet+    (newActorMap, replications) <-+      BitGet.label "replications" $+        Replication.decodeReplicationsBits+          matchType+          version+          buildVersion+          limit+          classes+          actorMap+    pure (newActorMap, Frame {time, delta, replications})
+ src/lib/Rattletrap/Type/Header.hs view
@@ -0,0 +1,113 @@+module Rattletrap.Type.Header where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Dictionary as Dictionary+import qualified Rattletrap.Type.Property as Property+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++-- | Contains high-level metadata about a 'Rattletrap.Replay.Replay'.+data Header = Header+  { version :: Version.Version,+    -- | Always @TAGame.Replay_Soccar_TA@.+    label :: Str.Str,+    -- | These properties determine how a replay will look in the list of+    -- replays in-game. One element is required for the replay to show up:+    --+    -- - MapName: This is a 'Rattletrap.PropertyValue.NameProperty' with a+    --   case-insensitive map identifier, like @Stadium_P@.+    --+    -- There are many other properties that affect how the replay looks in the+    -- list of replays.+    --+    -- - Date: A 'Rattletrap.PropertyValue.StrProperty' with the format+    --   @YYYY-mm-dd:HH-MM@. Dates are not validated, but the month must be+    --   between 1 and 12 to show up. The hour is shown modulo 12 with AM or PM.+    -- - MatchType: A 'Rattletrap.PropertyValue.NameProperty'. If this is not+    --   one of the expected values, nothing will be shown next to the replay's+    --   map. The expected values are: @Online@, @Offline@, @Private@, and+    --   @Season@.+    -- - NumFrames: This 'Rattletrap.PropertyValue.IntProperty' is used to+    --   calculate the length of the match. There are 30 frames per second,+    --   a typical 5-minute match has about 9,000 frames.+    -- - PrimaryPlayerTeam: This is an 'Rattletrap.PropertyValue.IntProperty'.+    --   It is either 0 (blue) or 1 (orange). Any other value is ignored. If+    --   this would be 0, you don't have to set it at all.+    -- - ReplayName: An optional 'Rattletrap.PropertyValue.StrProperty' with a+    --   user-supplied name for the replay.+    -- - Team0Score: The blue team's score as an+    --   'Rattletrap.PropertyValue.IntProperty'. Can be omitted if the score is+    --   0.+    -- - Team1Score: The orange team's score as an+    --   'Rattletrap.PropertyValue.IntProperty'. Can also be omitted if the+    --   score is 0.+    -- - TeamSize: An 'Rattletrap.PropertyValue.IntProperty' with the number of+    --   players per team. This value is not validated, so you can put absurd+    --   values like 99. To get an "unfair" team size like 1v4, you must set the+    --   bUnfairBots 'Rattletrap.PropertyValue.BoolProperty' to @True@.+    properties :: Dictionary.Dictionary Property.Property+  }+  deriving (Eq, Show)++instance Json.FromJSON Header where+  parseJSON = Json.withObject "Header" $ \object -> do+    major <- Json.required object "engine_version"+    minor <- Json.required object "licensee_version"+    patch <- Json.optional object "patch_version"+    label <- Json.required object "label"+    properties <- Json.required object "properties"+    pure+      Header+        { version =+            Version.Version+              { Version.major,+                Version.minor,+                Version.patch+              },+          label,+          properties+        }++instance Json.ToJSON Header where+  toJSON x =+    Json.object+      [ Json.pair "engine_version" . Version.major $ version x,+        Json.pair "licensee_version" . Version.minor $ version x,+        Json.pair "patch_version" . Version.patch $ version x,+        Json.pair "label" $ label x,+        Json.pair "properties" $ properties x+      ]++schema :: Schema.Schema+schema =+  Schema.named "header" $+    Schema.object+      [ (Json.pair "engine_version" $ Schema.ref U32.schema, True),+        (Json.pair "licensee_version" $ Schema.ref U32.schema, True),+        (Json.pair "patch_version" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "label" $ Schema.ref Str.schema, True),+        ( Json.pair "properties" . Schema.json $ Dictionary.schema Property.schema,+          True+        )+      ]++bytePut :: Header -> BytePut.BytePut+bytePut x =+  Version.bytePut (version x)+    <> Str.bytePut (label x)+    <> Dictionary.bytePut+      Property.bytePut+      (properties x)++byteGet :: ByteGet.ByteGet Header+byteGet = ByteGet.label "Header" $ do+  version <- ByteGet.label "version" Version.byteGet+  label <- ByteGet.label "label" Str.byteGet+  properties <-+    ByteGet.label "properties" $+      Dictionary.byteGet Property.byteGet+  pure Header {version, label, properties}
+ src/lib/Rattletrap/Type/I32.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.I32 where++import qualified Data.Int as Int+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype I32+  = I32 Int.Int32+  deriving (Eq, Show)++instance Json.FromJSON I32 where+  parseJSON = fmap fromInt32 . Json.parseJSON++instance Json.ToJSON I32 where+  toJSON = Json.toJSON . toInt32++schema :: Schema.Schema+schema =+  Schema.named "i32" $+    Json.object+      [ Json.pair "type" "integer",+        Json.pair "minimum" (minBound :: Int.Int32),+        Json.pair "maximum" (maxBound :: Int.Int32)+      ]++fromInt32 :: Int.Int32 -> I32+fromInt32 = I32++toInt32 :: I32 -> Int.Int32+toInt32 (I32 x) = x++bytePut :: I32 -> BytePut.BytePut+bytePut = BytePut.int32 . toInt32++bitPut :: I32 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet I32+byteGet = ByteGet.label "I32" $ fmap fromInt32 ByteGet.int32++bitGet :: BitGet.BitGet I32+bitGet = BitGet.fromByteGet byteGet 4
+ src/lib/Rattletrap/Type/I64.hs view
@@ -0,0 +1,48 @@+module Rattletrap.Type.I64 where++import qualified Data.Int as Int+import qualified Data.Text as Text+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json+import qualified Text.Read as Read++newtype I64+  = I64 Int.Int64+  deriving (Eq, Show)++instance Json.FromJSON I64 where+  parseJSON =+    Json.withText "I64" $+      either fail (pure . fromInt64)+        . Read.readEither+        . Text.unpack++instance Json.ToJSON I64 where+  toJSON = Json.toJSON . show . toInt64++schema :: Schema.Schema+schema =+  Schema.named "i64" $+    Json.object [Json.pair "type" "string", Json.pair "pattern" "^-?[0-9]+$"]++fromInt64 :: Int.Int64 -> I64+fromInt64 = I64++toInt64 :: I64 -> Int.Int64+toInt64 (I64 x) = x++bytePut :: I64 -> BytePut.BytePut+bytePut = BytePut.int64 . toInt64++bitPut :: I64 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet I64+byteGet = fmap fromInt64 ByteGet.int64++bitGet :: BitGet.BitGet I64+bitGet = BitGet.fromByteGet byteGet 8
+ src/lib/Rattletrap/Type/I8.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.I8 where++import qualified Data.Int as Int+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype I8+  = I8 Int.Int8+  deriving (Eq, Show)++instance Json.FromJSON I8 where+  parseJSON = fmap fromInt8 . Json.parseJSON++instance Json.ToJSON I8 where+  toJSON = Json.toJSON . toInt8++schema :: Schema.Schema+schema =+  Schema.named "i8" $+    Json.object+      [ Json.pair "type" "integer",+        Json.pair "minimum" (minBound :: Int.Int8),+        Json.pair "maximum" (maxBound :: Int.Int8)+      ]++fromInt8 :: Int.Int8 -> I8+fromInt8 = I8++toInt8 :: I8 -> Int.Int8+toInt8 (I8 x) = x++bytePut :: I8 -> BytePut.BytePut+bytePut = BytePut.int8 . toInt8++bitPut :: I8 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet I8+byteGet = ByteGet.label "I8" $ fmap fromInt8 ByteGet.int8++bitGet :: BitGet.BitGet I8+bitGet = BitGet.fromByteGet byteGet 1
+ src/lib/Rattletrap/Type/Initialization.hs view
@@ -0,0 +1,56 @@+module Rattletrap.Type.Initialization where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Int8Vector as Int8Vector+import qualified Rattletrap.Type.Vector as Vector+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Initialization = Initialization+  { -- | Not every class has an initial location. See+    -- 'Rattletrap.Data.classesWithLocation'.+    location :: Maybe Vector.Vector,+    -- | Only classes with location can have rotation, but not every one does.+    -- See 'Rattletrap.Data.classesWithRotation'.+    rotation :: Maybe Int8Vector.Int8Vector+  }+  deriving (Eq, Show)++instance Json.FromJSON Initialization where+  parseJSON = Json.withObject "Initialization" $ \object -> do+    location <- Json.optional object "location"+    rotation <- Json.optional object "rotation"+    pure Initialization {location, rotation}++instance Json.ToJSON Initialization where+  toJSON x =+    Json.object+      [Json.pair "location" $ location x, Json.pair "rotation" $ rotation x]++schema :: Schema.Schema+schema =+  Schema.named "initialization" $+    Schema.object+      [ (Json.pair "location" . Schema.json $ Schema.maybe Vector.schema, False),+        ( Json.pair "rotation" . Schema.json $ Schema.maybe Int8Vector.schema,+          False+        )+      ]++bitPut :: Initialization -> BitPut.BitPut+bitPut initialization =+  foldMap Vector.bitPut (location initialization)+    <> foldMap Int8Vector.bitPut (rotation initialization)++bitGet :: Version.Version -> Bool -> Bool -> BitGet.BitGet Initialization+bitGet version hasLocation hasRotation = BitGet.label "Initialization" $ do+  location <-+    BitGet.label "location" $+      Monad.whenMaybe hasLocation (Vector.bitGet version)+  rotation <-+    BitGet.label "rotation" $+      Monad.whenMaybe hasRotation Int8Vector.bitGet+  pure Initialization {location, rotation}
+ src/lib/Rattletrap/Type/Int8Vector.hs view
@@ -0,0 +1,58 @@+module Rattletrap.Type.Int8Vector where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I8 as I8+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Int8Vector = Int8Vector+  { x :: Maybe I8.I8,+    y :: Maybe I8.I8,+    z :: Maybe I8.I8+  }+  deriving (Eq, Show)++instance Json.FromJSON Int8Vector where+  parseJSON = Json.withObject "Int8Vector" $ \object -> do+    x <- Json.optional object "x"+    y <- Json.optional object "y"+    z <- Json.optional object "z"+    pure Int8Vector {x, y, z}++instance Json.ToJSON Int8Vector where+  toJSON a =+    Json.object [Json.pair "x" $ x a, Json.pair "y" $ y a, Json.pair "z" $ z a]++schema :: Schema.Schema+schema =+  Schema.named "int8Vector" $+    Schema.object+      [ (Json.pair "x" . Schema.json $ Schema.maybe I8.schema, False),+        (Json.pair "y" . Schema.json $ Schema.maybe I8.schema, False),+        (Json.pair "z" . Schema.json $ Schema.maybe I8.schema, False)+      ]++bitPut :: Int8Vector -> BitPut.BitPut+bitPut int8Vector =+  putInt8VectorField (x int8Vector)+    <> putInt8VectorField (y int8Vector)+    <> putInt8VectorField (z int8Vector)++putInt8VectorField :: Maybe I8.I8 -> BitPut.BitPut+putInt8VectorField maybeField = case maybeField of+  Nothing -> BitPut.bool False+  Just field -> BitPut.bool True <> I8.bitPut field++bitGet :: BitGet.BitGet Int8Vector+bitGet = BitGet.label "Int8Vector" $ do+  x <- BitGet.label "x" decodeFieldBits+  y <- BitGet.label "y" decodeFieldBits+  z <- BitGet.label "z" decodeFieldBits+  pure Int8Vector {x, y, z}++decodeFieldBits :: BitGet.BitGet (Maybe I8.I8)+decodeFieldBits = do+  hasField <- BitGet.bool+  Monad.whenMaybe hasField I8.bitGet
+ src/lib/Rattletrap/Type/Keyframe.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Keyframe where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Keyframe = Keyframe+  { -- | When this key frame occurs, in seconds.+    time :: F32.F32,+    -- | The frame number of this key frame, starting from 0.+    frame :: U32.U32,+    -- | The bit position of this key frame in the stream.+    position :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON Keyframe where+  parseJSON = Json.withObject "Keyframe" $ \object -> do+    time <- Json.required object "time"+    frame <- Json.required object "frame"+    position <- Json.required object "position"+    pure Keyframe {time, frame, position}++instance Json.ToJSON Keyframe where+  toJSON x =+    Json.object+      [ Json.pair "time" $ time x,+        Json.pair "frame" $ frame x,+        Json.pair "position" $ position x+      ]++schema :: Schema.Schema+schema =+  Schema.named "keyframe" $+    Schema.object+      [ (Json.pair "time" $ Schema.ref F32.schema, True),+        (Json.pair "frame" $ Schema.ref U32.schema, True),+        (Json.pair "position" $ Schema.ref U32.schema, True)+      ]++bytePut :: Keyframe -> BytePut.BytePut+bytePut x =+  F32.bytePut (time x) <> U32.bytePut (frame x) <> U32.bytePut (position x)++byteGet :: ByteGet.ByteGet Keyframe+byteGet = ByteGet.label "Keyframe" $ do+  time <- ByteGet.label "time" F32.byteGet+  frame <- ByteGet.label "frame" U32.byteGet+  position <- ByteGet.label "position" U32.byteGet+  pure Keyframe {time, frame, position}
+ src/lib/Rattletrap/Type/List.hs view
@@ -0,0 +1,57 @@+module Rattletrap.Type.List where++import qualified Control.Monad as Monad+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++newtype List a+  = List [a]+  deriving (Eq, Show)++instance (Json.FromJSON a) => Json.FromJSON (List a) where+  parseJSON = fmap fromList . Json.parseJSON++instance (Json.ToJSON a) => Json.ToJSON (List a) where+  toJSON = Json.toJSON . toList++schema :: Schema.Schema -> Schema.Schema+schema = Schema.array++fromList :: [a] -> List a+fromList = List++empty :: List a+empty = fromList []++toList :: List a -> [a]+toList (List x) = x++bytePut :: (a -> BytePut.BytePut) -> List a -> BytePut.BytePut+bytePut f x =+  let v = toList x+   in (U32.bytePut . U32.fromWord32 . fromIntegral $ length v) <> foldMap f v++byteGet :: ByteGet.ByteGet a -> ByteGet.ByteGet (List a)+byteGet f = ByteGet.label "List" $ do+  size <- ByteGet.label "size" U32.byteGet+  generateM (fromIntegral $ U32.toWord32 size) $+    \i -> ByteGet.label ("element (" <> show i <> ")") f++generateM :: (Monad m) => Int -> (Int -> m a) -> m (List a)+generateM n f = fmap fromList $ mapM f [0 .. n - 1]++replicateM :: (Monad m) => Int -> m a -> m (List a)+replicateM n = fmap fromList . Monad.replicateM n++untilM :: (Monad m) => m (Maybe a) -> m (List a)+untilM f = untilMWith f 0 []++untilMWith :: (Monad m) => m (Maybe a) -> Int -> [(Int, a)] -> m (List a)+untilMWith f i xs = do+  m <- f+  case m of+    Nothing -> pure . fromList . reverse $ fmap snd xs+    Just x -> untilMWith f (i + 1) ((i, x) : xs)
+ src/lib/Rattletrap/Type/Mark.hs view
@@ -0,0 +1,43 @@+module Rattletrap.Type.Mark where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Mark = Mark+  { -- | Which type of mark this is, like @Team0Goal@.+    value :: Str.Str,+    -- | Which frame this mark belongs to, starting from 0.+    frame :: U32.U32+  }+  deriving (Eq, Show)++instance Json.FromJSON Mark where+  parseJSON = Json.withObject "Mark" $ \object -> do+    value <- Json.required object "value"+    frame <- Json.required object "frame"+    pure Mark {value, frame}++instance Json.ToJSON Mark where+  toJSON x =+    Json.object [Json.pair "value" $ value x, Json.pair "frame" $ frame x]++schema :: Schema.Schema+schema =+  Schema.named "mark" $+    Schema.object+      [ (Json.pair "value" $ Schema.ref Str.schema, True),+        (Json.pair "frame" $ Schema.ref U32.schema, True)+      ]++bytePut :: Mark -> BytePut.BytePut+bytePut x = Str.bytePut (value x) <> U32.bytePut (frame x)++byteGet :: ByteGet.ByteGet Mark+byteGet = ByteGet.label "Mark" $ do+  value <- ByteGet.label "value" Str.byteGet+  frame <- ByteGet.label "frame" U32.byteGet+  pure Mark {value, frame}
+ src/lib/Rattletrap/Type/Message.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Message where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Message = Message+  { -- | Which frame this message belongs to, starting from 0.+    frame :: U32.U32,+    -- | The primary player's name.+    name :: Str.Str,+    -- | The content of the message.+    value :: Str.Str+  }+  deriving (Eq, Show)++instance Json.FromJSON Message where+  parseJSON = Json.withObject "Message" $ \object -> do+    frame <- Json.required object "frame"+    name <- Json.required object "name"+    value <- Json.required object "value"+    pure Message {frame, name, value}++instance Json.ToJSON Message where+  toJSON x =+    Json.object+      [ Json.pair "frame" $ frame x,+        Json.pair "name" $ name x,+        Json.pair "value" $ value x+      ]++schema :: Schema.Schema+schema =+  Schema.named "message" $+    Schema.object+      [ (Json.pair "frame" $ Schema.ref U32.schema, True),+        (Json.pair "name" $ Schema.ref Str.schema, True),+        (Json.pair "value" $ Schema.ref Str.schema, True)+      ]++bytePut :: Message -> BytePut.BytePut+bytePut x =+  U32.bytePut (frame x) <> Str.bytePut (name x) <> Str.bytePut (value x)++byteGet :: ByteGet.ByteGet Message+byteGet = ByteGet.label "Message" $ do+  frame <- ByteGet.label "frame" U32.byteGet+  name <- ByteGet.label "name" Str.byteGet+  value <- ByteGet.label "value" Str.byteGet+  pure Message {frame, name, value}
+ src/lib/Rattletrap/Type/ObjectTarget.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.ObjectTarget where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Json as Json++data ObjectTarget = ObjectTarget+  { isActor :: Bool,+    targetIndex :: I32.I32+  }+  deriving (Eq, Show)++instance Json.FromJSON ObjectTarget where+  parseJSON = Json.withObject "ObjectTarget" $ \object -> do+    isActor <- Json.required object "is_actor"+    targetIndex <- Json.required object "target_index"+    pure+      ObjectTarget+        { isActor,+          targetIndex+        }++instance Json.ToJSON ObjectTarget where+  toJSON x =+    Json.object+      [ Json.pair "is_actor" $ isActor x,+        Json.pair "target_index" $ targetIndex x+      ]++schema :: Schema.Schema+schema =+  Schema.named "object-target" $+    Schema.object+      [ (Json.pair "is_actor" $ Schema.ref Schema.boolean, True),+        (Json.pair "target_index" $ Schema.ref I32.schema, True)+      ]++bitPut :: ObjectTarget -> BitPut.BitPut+bitPut demolishAttribute =+  BitPut.bool (isActor demolishAttribute)+    <> I32.bitPut (targetIndex demolishAttribute)++bitGet :: BitGet.BitGet ObjectTarget+bitGet = BitGet.label "ObjectTarget" $ do+  isActor <- BitGet.label "isActor" BitGet.bool+  targetIndex <- BitGet.label "targetIndex" I32.bitGet+  pure+    ObjectTarget+      { isActor,+        targetIndex+      }
+ src/lib/Rattletrap/Type/Property.hs view
@@ -0,0 +1,62 @@+module Rattletrap.Type.Property where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.PropertyValue as PropertyValue+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json++data Property = Property+  { kind :: Str.Str,+    -- | Not used.+    size :: U32.U32,+    index :: U32.U32,+    value :: PropertyValue.PropertyValue Property+  }+  deriving (Eq, Show)++instance Json.FromJSON Property where+  parseJSON = Json.withObject "Property" $ \object -> do+    kind <- Json.required object "kind"+    size <- Json.required object "size"+    index <- Json.required object "index"+    value <- Json.required object "value"+    pure Property {kind, size, index, value}++instance Json.ToJSON Property where+  toJSON x =+    Json.object+      [ Json.pair "kind" $ kind x,+        Json.pair "size" $ size x,+        Json.pair "index" $ index x,+        Json.pair "value" $ value x+      ]++schema :: Schema.Schema+schema =+  Schema.named "property" $+    Schema.object+      [ (Json.pair "kind" $ Schema.ref Str.schema, True),+        (Json.pair "size" $ Schema.ref U32.schema, True),+        (Json.pair "index" $ Schema.ref U32.schema, True),+        (Json.pair "value" . Schema.ref $ PropertyValue.schema schema, True)+      ]++bytePut :: Property -> BytePut.BytePut+bytePut x =+  Str.bytePut (kind x)+    <> U32.bytePut (size x)+    <> U32.bytePut (index x)+    <> PropertyValue.bytePut+      bytePut+      (value x)++byteGet :: ByteGet.ByteGet Property+byteGet = ByteGet.label "Property" $ do+  kind <- ByteGet.label "kind" Str.byteGet+  size <- ByteGet.label "size" U32.byteGet+  index <- ByteGet.label "index" U32.byteGet+  value <- ByteGet.label "value" $ PropertyValue.byteGet byteGet kind+  pure Property {kind, size, index, value}
+ src/lib/Rattletrap/Type/Property/Array.hs view
@@ -0,0 +1,37 @@+module Rattletrap.Type.Property.Array where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Dictionary as Dictionary+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Utility.Json as Json++newtype Array a+  = Array (RList.List (Dictionary.Dictionary a))+  deriving (Eq, Show)++fromList :: RList.List (Dictionary.Dictionary a) -> Array a+fromList = Array++toList :: Array a -> RList.List (Dictionary.Dictionary a)+toList (Array x) = x++instance (Json.FromJSON a) => Json.FromJSON (Array a) where+  parseJSON = fmap fromList . Json.parseJSON++instance (Json.ToJSON a) => Json.ToJSON (Array a) where+  toJSON = Json.toJSON . toList++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named "property-array" . Schema.json . RList.schema $+    Dictionary.schema+      s++bytePut :: (a -> BytePut.BytePut) -> Array a -> BytePut.BytePut+bytePut f = RList.bytePut (Dictionary.bytePut f) . toList++byteGet :: ByteGet.ByteGet a -> ByteGet.ByteGet (Array a)+byteGet =+  ByteGet.label "Array" . fmap fromList . RList.byteGet . Dictionary.byteGet
+ src/lib/Rattletrap/Type/Property/Bool.hs view
@@ -0,0 +1,33 @@+module Rattletrap.Type.Property.Bool where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Bool)++newtype Bool+  = Bool U8.U8+  deriving (Eq, Show)++fromU8 :: U8.U8 -> Bool+fromU8 = Bool++toU8 :: Bool -> U8.U8+toU8 (Bool x) = x++instance Json.FromJSON Bool where+  parseJSON = fmap fromU8 . Json.parseJSON++instance Json.ToJSON Bool where+  toJSON = Json.toJSON . toU8++schema :: Schema.Schema+schema = U8.schema++bytePut :: Bool -> BytePut.BytePut+bytePut = U8.bytePut . toU8++byteGet :: ByteGet.ByteGet Bool+byteGet = ByteGet.label "Bool" $ fmap fromU8 U8.byteGet
+ src/lib/Rattletrap/Type/Property/Byte.hs view
@@ -0,0 +1,53 @@+module Rattletrap.Type.Property.Byte where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Utility.Json as Json++data Byte = Byte+  { key :: Str.Str,+    value :: Maybe (Either U8.U8 Str.Str)+  }+  deriving (Eq, Show)++instance Json.FromJSON Byte where+  parseJSON json = do+    (key, value) <- Json.parseJSON json+    pure Byte {key, value}++instance Json.ToJSON Byte where+  toJSON byte = Json.toJSON (key byte, value byte)++schema :: Schema.Schema+schema =+  Schema.named "property-byte" $+    Schema.tuple+      [ Schema.ref Str.schema,+        Schema.oneOf+          [ Schema.ref Schema.null,+            Schema.object [(Json.pair "Left" $ Schema.ref U8.schema, True)],+            Schema.object [(Json.pair "Right" $ Schema.ref Str.schema, True)]+          ]+      ]++bytePut :: Byte -> BytePut.BytePut+bytePut byte = Str.bytePut (key byte) <> foldMap (either U8.bytePut Str.bytePut) (value byte)++byteGet :: ByteGet.ByteGet Byte+byteGet = ByteGet.label "Byte" $ do+  key <- ByteGet.label "key" Str.byteGet+  let isSteam = key == Str.fromString "OnlinePlatform_Steam"+      isPlayStation = key == Str.fromString "OnlinePlatform_PS4"+      isNone = key == Str.fromString "None"+  value <-+    ByteGet.label "value" $+      if isSteam || isPlayStation+        then pure Nothing+        else+          if isNone+            then Just . Left <$> U8.byteGet+            else Just . Right <$> Str.byteGet+  pure Byte {key, value}
+ src/lib/Rattletrap/Type/Property/Float.hs view
@@ -0,0 +1,33 @@+module Rattletrap.Type.Property.Float where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.F32 as F32+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Float)++newtype Float+  = Float F32.F32+  deriving (Eq, Show)++fromF32 :: F32.F32 -> Float+fromF32 = Float++toF32 :: Float -> F32.F32+toF32 (Float x) = x++instance Json.FromJSON Float where+  parseJSON = fmap fromF32 . Json.parseJSON++instance Json.ToJSON Float where+  toJSON = Json.toJSON . toF32++schema :: Schema.Schema+schema = F32.schema++bytePut :: Float -> BytePut.BytePut+bytePut = F32.bytePut . toF32++byteGet :: ByteGet.ByteGet Float+byteGet = ByteGet.label "Float" $ fmap fromF32 F32.byteGet
+ src/lib/Rattletrap/Type/Property/Int.hs view
@@ -0,0 +1,33 @@+module Rattletrap.Type.Property.Int where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Json as Json+import Prelude hiding (Int)++newtype Int+  = Int I32.I32+  deriving (Eq, Show)++fromI32 :: I32.I32 -> Int+fromI32 = Int++toI32 :: Int -> I32.I32+toI32 (Int x) = x++instance Json.FromJSON Int where+  parseJSON = fmap fromI32 . Json.parseJSON++instance Json.ToJSON Int where+  toJSON = Json.toJSON . toI32++schema :: Schema.Schema+schema = I32.schema++bytePut :: Int -> BytePut.BytePut+bytePut = I32.bytePut . toI32++byteGet :: ByteGet.ByteGet Int+byteGet = ByteGet.label "I32" $ fmap fromI32 I32.byteGet
+ src/lib/Rattletrap/Type/Property/Name.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.Property.Name where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++newtype Name+  = Name Str.Str+  deriving (Eq, Show)++fromStr :: Str.Str -> Name+fromStr = Name++toStr :: Name -> Str.Str+toStr (Name x) = x++instance Json.FromJSON Name where+  parseJSON = fmap fromStr . Json.parseJSON++instance Json.ToJSON Name where+  toJSON = Json.toJSON . toStr++schema :: Schema.Schema+schema = Str.schema++bytePut :: Name -> BytePut.BytePut+bytePut = Str.bytePut . toStr++byteGet :: ByteGet.ByteGet Name+byteGet = ByteGet.label "Name" $ fmap fromStr Str.byteGet
+ src/lib/Rattletrap/Type/Property/QWord.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.Property.QWord where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++newtype QWord+  = QWord U64.U64+  deriving (Eq, Show)++fromU64 :: U64.U64 -> QWord+fromU64 = QWord++toU64 :: QWord -> U64.U64+toU64 (QWord x) = x++instance Json.FromJSON QWord where+  parseJSON = fmap fromU64 . Json.parseJSON++instance Json.ToJSON QWord where+  toJSON = Json.toJSON . toU64++schema :: Schema.Schema+schema = U64.schema++bytePut :: QWord -> BytePut.BytePut+bytePut = U64.bytePut . toU64++byteGet :: ByteGet.ByteGet QWord+byteGet = ByteGet.label "QWord" $ fmap fromU64 U64.byteGet
+ src/lib/Rattletrap/Type/Property/Str.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.Property.Str where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++newtype Str+  = Str Str.Str+  deriving (Eq, Show)++fromStr :: Str.Str -> Str+fromStr = Str++toStr :: Str -> Str.Str+toStr (Str x) = x++instance Json.FromJSON Str where+  parseJSON = fmap fromStr . Json.parseJSON++instance Json.ToJSON Str where+  toJSON = Json.toJSON . toStr++schema :: Schema.Schema+schema = Str.schema++bytePut :: Str -> BytePut.BytePut+bytePut = Str.bytePut . toStr++byteGet :: ByteGet.ByteGet Str+byteGet = ByteGet.label "Str" $ fmap fromStr Str.byteGet
+ src/lib/Rattletrap/Type/Property/Struct.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.Property.Struct where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Dictionary as Dictionary+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++data Struct a = Struct+  { name :: Str.Str,+    fields :: Dictionary.Dictionary a+  }+  deriving (Eq, Show)++instance (Json.FromJSON a) => Json.FromJSON (Struct a) where+  parseJSON = Json.withObject "Struct" $ \o -> do+    name <- Json.required o "name"+    fields <- Json.required o "fields"+    pure Struct {name, fields}++instance (Json.ToJSON a) => Json.ToJSON (Struct a) where+  toJSON x =+    Json.object+      [ Json.pair "name" $ name x,+        Json.pair "fields" $ fields x+      ]++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named "property-struct" $+    Schema.object+      [ (Json.pair "name" $ Schema.ref Str.schema, True),+        (Json.pair "fields" $ Schema.ref (Dictionary.schema s), True)+      ]++bytePut :: (a -> BytePut.BytePut) -> Struct a -> BytePut.BytePut+bytePut p x =+  Str.bytePut (name x)+    <> Dictionary.bytePut p (fields x)++byteGet :: ByteGet.ByteGet a -> ByteGet.ByteGet (Struct a)+byteGet g = ByteGet.label "Struct" $ do+  name <- ByteGet.label "name" Str.byteGet+  fields <- ByteGet.label "fields" $ Dictionary.byteGet g+  pure Struct {name, fields}
+ src/lib/Rattletrap/Type/PropertyValue.hs view
@@ -0,0 +1,102 @@+module Rattletrap.Type.PropertyValue where++import qualified Data.Foldable as Foldable+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Exception.UnknownProperty as UnknownProperty+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Property.Array as Property.Array+import qualified Rattletrap.Type.Property.Bool as Property.Bool+import qualified Rattletrap.Type.Property.Byte as Property.Byte+import qualified Rattletrap.Type.Property.Float as Property.Float+import qualified Rattletrap.Type.Property.Int as Property.Int+import qualified Rattletrap.Type.Property.Name as Property.Name+import qualified Rattletrap.Type.Property.QWord as Property.QWord+import qualified Rattletrap.Type.Property.Str as Property.Str+import qualified Rattletrap.Type.Property.Struct as Property.Struct+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++data PropertyValue a+  = -- | Yes, a list of dictionaries. No, it doesn't make sense. These usually+    -- only have one element.+    Array (Property.Array.Array a)+  | Bool Property.Bool.Bool+  | -- | This is a strange name for essentially a key-value pair.+    Byte Property.Byte.Byte+  | Float Property.Float.Float+  | Int Property.Int.Int+  | -- | It's unclear how exactly this is different than a 'StrProperty'.+    Name Property.Name.Name+  | QWord Property.QWord.QWord+  | Str Property.Str.Str+  | Struct (Property.Struct.Struct a)+  deriving (Eq, Show)++instance (Json.FromJSON a) => Json.FromJSON (PropertyValue a) where+  parseJSON = Json.withObject "PropertyValue" $ \object ->+    Foldable.asum+      [ fmap Array $ Json.required object "array",+        fmap Bool $ Json.required object "bool",+        fmap Byte $ Json.required object "byte",+        fmap Float $ Json.required object "float",+        fmap Int $ Json.required object "int",+        fmap Name $ Json.required object "name",+        fmap QWord $ Json.required object "q_word",+        fmap Str $ Json.required object "str",+        fmap Struct $ Json.required object "struct"+      ]++instance (Json.ToJSON a) => Json.ToJSON (PropertyValue a) where+  toJSON x = case x of+    Array y -> Json.object [Json.pair "array" y]+    Bool y -> Json.object [Json.pair "bool" y]+    Byte y -> Json.object [Json.pair "byte" y]+    Float y -> Json.object [Json.pair "float" y]+    Int y -> Json.object [Json.pair "int" y]+    Name y -> Json.object [Json.pair "name" y]+    QWord y -> Json.object [Json.pair "q_word" y]+    Str y -> Json.object [Json.pair "str" y]+    Struct y -> Json.object [Json.pair "struct" y]++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named "property-value" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k v, True)])+      [ ("array", Schema.ref $ Property.Array.schema s),+        ("bool", Schema.ref Property.Bool.schema),+        ("byte", Schema.ref Property.Byte.schema),+        ("float", Schema.ref Property.Float.schema),+        ("int", Schema.ref Property.Int.schema),+        ("name", Schema.ref Property.Name.schema),+        ("q_word", Schema.ref Property.QWord.schema),+        ("str", Schema.ref Property.Str.schema),+        ("struct", Schema.ref $ Property.Struct.schema s)+      ]++bytePut :: (a -> BytePut.BytePut) -> PropertyValue a -> BytePut.BytePut+bytePut putProperty value = case value of+  Array x -> Property.Array.bytePut putProperty x+  Bool x -> Property.Bool.bytePut x+  Byte x -> Property.Byte.bytePut x+  Float x -> Property.Float.bytePut x+  Int x -> Property.Int.bytePut x+  Name x -> Property.Name.bytePut x+  QWord x -> Property.QWord.bytePut x+  Str x -> Property.Str.bytePut x+  Struct x -> Property.Struct.bytePut putProperty x++byteGet :: ByteGet.ByteGet a -> Str.Str -> ByteGet.ByteGet (PropertyValue a)+byteGet getProperty kind =+  ByteGet.label "PropertyValue" $ case Str.toString kind of+    "ArrayProperty" -> fmap Array $ Property.Array.byteGet getProperty+    "BoolProperty" -> fmap Bool Property.Bool.byteGet+    "ByteProperty" -> fmap Byte Property.Byte.byteGet+    "FloatProperty" -> fmap Float Property.Float.byteGet+    "IntProperty" -> fmap Int Property.Int.byteGet+    "NameProperty" -> fmap Name Property.Name.byteGet+    "QWordProperty" -> fmap QWord Property.QWord.byteGet+    "StrProperty" -> fmap Str Property.Str.byteGet+    "StructProperty" -> fmap Struct $ Property.Struct.byteGet getProperty+    x -> ByteGet.throw $ UnknownProperty.UnknownProperty x
+ src/lib/Rattletrap/Type/Quaternion.hs view
@@ -0,0 +1,167 @@+module Rattletrap.Type.Quaternion where++import qualified Data.List as List+import qualified Data.Maybe as Maybe+import qualified Data.Ord as Ord+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Exception.InvalidComponent as InvalidComponent+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Utility.Json as Json++data Quaternion = Quaternion+  { x :: Double,+    y :: Double,+    z :: Double,+    w :: Double+  }+  deriving (Eq, Show)++instance Json.FromJSON Quaternion where+  parseJSON = Json.withObject "Quaternion" $ \object -> do+    x <- Json.required object "x"+    y <- Json.required object "y"+    z <- Json.required object "z"+    w <- Json.required object "w"+    pure Quaternion {x, y, z, w}++instance Json.ToJSON Quaternion where+  toJSON a =+    Json.object+      [ Json.pair "x" $ x a,+        Json.pair "y" $ y a,+        Json.pair "z" $ z a,+        Json.pair "w" $ w a+      ]++schema :: Schema.Schema+schema =+  Schema.named "quaternion" $+    Schema.object+      [ (Json.pair "x" $ Schema.ref Schema.number, True),+        (Json.pair "y" $ Schema.ref Schema.number, True),+        (Json.pair "z" $ Schema.ref Schema.number, True),+        (Json.pair "w" $ Schema.ref Schema.number, True)+      ]++data Component+  = X+  | Y+  | Z+  | W+  deriving (Eq, Show)++toQuaternion :: Component -> Double -> Double -> Double -> Quaternion+toQuaternion component a b c =+  let d = toPart a b c+   in case component of+        X -> Quaternion d a b c+        Y -> Quaternion a d b c+        Z -> Quaternion a b d c+        W -> Quaternion a b c d++toPart :: Double -> Double -> Double -> Double+toPart a b c = sqrt (1 - (a * a) - (b * b) - (c * c))++compressPart :: Double -> CompressedWord.CompressedWord+compressPart =+  CompressedWord.CompressedWord maxCompressedValue+    . round+    . (* wordToDouble maxCompressedValue)+    . (+ 0.5)+    . (/ 2.0)+    . (/ maxValue)++decompressPart :: CompressedWord.CompressedWord -> Double+decompressPart x_ =+  (* maxValue)+    . (* 2.0)+    . subtract 0.5+    . (/ wordToDouble (CompressedWord.limit x_))+    . wordToDouble+    $ CompressedWord.value x_++maxComponent :: Quaternion -> Component+maxComponent quaternion =+  let x_ = x quaternion+      y_ = y quaternion+      z_ = z quaternion+      w_ = w quaternion+      parts = [(x_, X), (y_, Y), (z_, Z), (w_, W)]+      biggestPart = maximumOn fst parts+      roundTrip = decompressPart . compressPart+      computedPart =+        Maybe.fromMaybe+          biggestPart+          (List.find (\(value, _) -> value /= roundTrip value) parts)+   in snd+        ( if (biggestPart == computedPart)+            || (abs (fst biggestPart - fst computedPart) > 0.00001)+            then biggestPart+            else computedPart+        )++maximumOn :: (Foldable t, Ord b) => (a -> b) -> t a -> a+maximumOn f = List.maximumBy (Ord.comparing f)++numBits :: Word+numBits = 18++wordToDouble :: Word -> Double+wordToDouble = fromIntegral++maxCompressedValue :: Word+maxCompressedValue = (2 ^ numBits) - 1++maxValue :: Double+maxValue = 1.0 / sqrt 2.0++bitPut :: Quaternion -> BitPut.BitPut+bitPut q =+  let c = maxComponent q+   in putComponent c <> case c of+        X -> putParts (y q) (z q) (w q)+        Y -> putParts (x q) (z q) (w q)+        Z -> putParts (x q) (y q) (w q)+        W -> putParts (x q) (y q) (z q)++putComponent :: Component -> BitPut.BitPut+putComponent component =+  CompressedWord.bitPut+    ( CompressedWord.CompressedWord+        3+        ( case component of+            X -> 0+            Y -> 1+            Z -> 2+            W -> 3+        )+    )++putParts :: Double -> Double -> Double -> BitPut.BitPut+putParts a b c = putPart a <> putPart b <> putPart c++putPart :: Double -> BitPut.BitPut+putPart = CompressedWord.bitPut . compressPart++bitGet :: BitGet.BitGet Quaternion+bitGet = do+  component <- decodeComponent+  a <- decodePart+  b <- decodePart+  c <- decodePart+  pure $ toQuaternion component a b c++decodeComponent :: BitGet.BitGet Component+decodeComponent = do+  x_ <- CompressedWord.bitGet 3+  case CompressedWord.value x_ of+    0 -> pure X+    1 -> pure Y+    2 -> pure Z+    3 -> pure W+    y_ -> BitGet.throw $ InvalidComponent.InvalidComponent y_++decodePart :: BitGet.BitGet Double+decodePart = fmap decompressPart $ CompressedWord.bitGet maxCompressedValue
+ src/lib/Rattletrap/Type/RemoteId.hs view
@@ -0,0 +1,92 @@+module Rattletrap.Type.RemoteId where++import qualified Data.Foldable as Foldable+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Exception.UnknownSystemId as UnknownSystemId+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.RemoteId.Epic as Epic+import qualified Rattletrap.Type.RemoteId.PlayStation as PlayStation+import qualified Rattletrap.Type.RemoteId.PsyNet as PsyNet+import qualified Rattletrap.Type.RemoteId.QQ as QQ+import qualified Rattletrap.Type.RemoteId.Splitscreen as Splitscreen+import qualified Rattletrap.Type.RemoteId.Steam as Steam+import qualified Rattletrap.Type.RemoteId.Switch as Switch+import qualified Rattletrap.Type.RemoteId.Xbox as Xbox+import qualified Rattletrap.Type.U8 as U8+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data RemoteId+  = PlayStation PlayStation.PlayStation+  | PsyNet PsyNet.PsyNet+  | QQ QQ.QQ+  | -- | Really only 24 bits.+    Splitscreen Splitscreen.Splitscreen+  | Steam Steam.Steam+  | Switch Switch.Switch+  | Xbox Xbox.Xbox+  | Epic Epic.Epic+  deriving (Eq, Show)++instance Json.FromJSON RemoteId where+  parseJSON = Json.withObject "RemoteId" $ \object ->+    Foldable.asum+      [ fmap PlayStation $ Json.required object "play_station",+        fmap PsyNet $ Json.required object "psy_net",+        fmap QQ $ Json.required object "qq",+        fmap Splitscreen $ Json.required object "splitscreen",+        fmap Steam $ Json.required object "steam",+        fmap Switch $ Json.required object "switch",+        fmap Xbox $ Json.required object "xbox",+        fmap Epic $ Json.required object "epic"+      ]++instance Json.ToJSON RemoteId where+  toJSON x = case x of+    PlayStation y -> Json.object [Json.pair "play_station" y]+    PsyNet y -> Json.object [Json.pair "psy_net" y]+    QQ y -> Json.object [Json.pair "qq" y]+    Splitscreen y -> Json.object [Json.pair "splitscreen" y]+    Steam y -> Json.object [Json.pair "steam" y]+    Switch y -> Json.object [Json.pair "switch" y]+    Xbox y -> Json.object [Json.pair "xbox" y]+    Epic y -> Json.object [Json.pair "epic" y]++schema :: Schema.Schema+schema =+  Schema.named "remote-id" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k v, True)])+      [ ("play_station", Schema.ref PlayStation.schema),+        ("psy_net", Schema.ref PsyNet.schema),+        ("qq", Schema.ref QQ.schema),+        ("splitscreen", Schema.ref Splitscreen.schema),+        ("steam", Schema.ref Steam.schema),+        ("switch", Schema.ref Switch.schema),+        ("xbox", Schema.ref Xbox.schema),+        ("epic", Schema.ref Epic.schema)+      ]++bitPut :: RemoteId -> BitPut.BitPut+bitPut remoteId = case remoteId of+  PlayStation x -> PlayStation.bitPut x+  PsyNet x -> PsyNet.bitPut x+  QQ x -> QQ.bitPut x+  Splitscreen x -> Splitscreen.bitPut x+  Steam x -> Steam.bitPut x+  Switch x -> Switch.bitPut x+  Xbox x -> Xbox.bitPut x+  Epic x -> Epic.bitPut x++bitGet :: Version.Version -> U8.U8 -> BitGet.BitGet RemoteId+bitGet version systemId = case U8.toWord8 systemId of+  0 -> fmap Splitscreen Splitscreen.bitGet+  1 -> fmap Steam Steam.bitGet+  2 -> fmap PlayStation $ PlayStation.bitGet version+  4 -> fmap Xbox Xbox.bitGet+  5 -> fmap QQ QQ.bitGet+  6 -> fmap Switch Switch.bitGet+  7 -> fmap PsyNet $ PsyNet.bitGet version+  11 -> fmap Epic Epic.bitGet+  x -> BitGet.throw $ UnknownSystemId.UnknownSystemId x
+ src/lib/Rattletrap/Type/RemoteId/Epic.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.RemoteId.Epic where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Utility.Json as Json++newtype Epic+  = Epic Str.Str+  deriving (Eq, Show)++instance Json.FromJSON Epic where+  parseJSON = fmap fromStr . Json.parseJSON++instance Json.ToJSON Epic where+  toJSON = Json.toJSON . toStr++fromStr :: Str.Str -> Epic+fromStr = Epic++toStr :: Epic -> Str.Str+toStr (Epic x) = x++schema :: Schema.Schema+schema = Str.schema++bitPut :: Epic -> BitPut.BitPut+bitPut = Str.bitPut . toStr++bitGet :: BitGet.BitGet Epic+bitGet = BitGet.label "Epic" $ fmap fromStr Str.bitGet
+ src/lib/Rattletrap/Type/RemoteId/PlayStation.hs view
@@ -0,0 +1,55 @@+module Rattletrap.Type.RemoteId.PlayStation where++import qualified Data.ByteString as ByteString+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Bytes as Bytes+import qualified Rattletrap.Utility.Json as Json++data PlayStation = PlayStation+  { name :: Text.Text,+    code :: [Word.Word8]+  }+  deriving (Eq, Show)++instance Json.FromJSON PlayStation where+  parseJSON json = do+    (name, code) <- Json.parseJSON json+    pure PlayStation {name, code}++instance Json.ToJSON PlayStation where+  toJSON x = Json.toJSON (name x, code x)++schema :: Schema.Schema+schema =+  Schema.named "remote-id-play-station" $+    Schema.tuple+      [Schema.ref Schema.string, Schema.json $ Schema.array Schema.number]++bitPut :: PlayStation -> BitPut.BitPut+bitPut x =+  let nameBytes = Bytes.padBytes (16 :: Int) . Bytes.encodeLatin1 $ name x+      codeBytes = ByteString.pack $ code x+   in BitPut.byteString nameBytes <> BitPut.byteString codeBytes++bitGet :: Version.Version -> BitGet.BitGet PlayStation+bitGet version = BitGet.label "PlayStation" $ do+  name <- BitGet.label "name" getCode+  code <- BitGet.label "code" $ getName version+  pure PlayStation {name, code}++getCode :: BitGet.BitGet Text.Text+getCode =+  fmap (Text.dropWhileEnd (== '\x00') . Text.decodeLatin1) $+    BitGet.byteString 16++getName :: Version.Version -> BitGet.BitGet [Word.Word8]+getName version =+  fmap ByteString.unpack+    . BitGet.byteString+    $ if Version.atLeast 868 20 1 version then 24 else 16
+ src/lib/Rattletrap/Type/RemoteId/PsyNet.hs view
@@ -0,0 +1,56 @@+module Rattletrap.Type.RemoteId.PsyNet where++import qualified Control.Applicative as Applicative+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data PsyNet+  = New U64.U64+  | Old U64.U64 U64.U64 U64.U64 U64.U64+  deriving (Eq, Show)++instance Json.FromJSON PsyNet where+  parseJSON = Json.withObject "PsyNet" $ \object -> do+    let new = fmap New $ Json.required object "Left"+        old = do+          (a, b, c, d) <- Json.required object "Right"+          pure $ Old a b c d+    new Applicative.<|> old++instance Json.ToJSON PsyNet where+  toJSON x = case x of+    New a -> Json.object [Json.pair "Left" a]+    Old a b c d -> Json.object [Json.pair "Right" (a, b, c, d)]++schema :: Schema.Schema+schema =+  Schema.named "remote-id-psy-net" $+    Schema.oneOf+      [ Schema.object [(Json.pair "Left" $ Schema.ref U64.schema, True)],+        Schema.object+          [ ( Json.pair "Right" . Schema.tuple . replicate 4 $ Schema.ref U64.schema,+              True+            )+          ]+      ]++bitPut :: PsyNet -> BitPut.BitPut+bitPut x = case x of+  New l -> U64.bitPut l+  Old a b c d -> U64.bitPut a <> U64.bitPut b <> U64.bitPut c <> U64.bitPut d++bitGet :: Version.Version -> BitGet.BitGet PsyNet+bitGet version =+  BitGet.label "PsyNet" $+    if Version.atLeast 868 24 10 version+      then BitGet.label "New" $ fmap New U64.bitGet+      else BitGet.label "Old" $ do+        a <- U64.bitGet+        b <- U64.bitGet+        c <- U64.bitGet+        d <- U64.bitGet+        pure $ Old a b c d
+ src/lib/Rattletrap/Type/RemoteId/QQ.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.RemoteId.QQ where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++newtype QQ+  = QQ U64.U64+  deriving (Eq, Show)++instance Json.FromJSON QQ where+  parseJSON = fmap fromU64 . Json.parseJSON++instance Json.ToJSON QQ where+  toJSON = Json.toJSON . toU64++fromU64 :: U64.U64 -> QQ+fromU64 = QQ++toU64 :: QQ -> U64.U64+toU64 (QQ x) = x++schema :: Schema.Schema+schema = U64.schema++bitPut :: QQ -> BitPut.BitPut+bitPut = U64.bitPut . toU64++bitGet :: BitGet.BitGet QQ+bitGet = BitGet.label "QQ" $ fmap fromU64 U64.bitGet
+ src/lib/Rattletrap/Type/RemoteId/Splitscreen.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.RemoteId.Splitscreen where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype Splitscreen+  = Splitscreen Word.Word32+  deriving (Eq, Show)++instance Json.FromJSON Splitscreen where+  parseJSON = fmap fromWord32 . Json.parseJSON++instance Json.ToJSON Splitscreen where+  toJSON = Json.toJSON . toWord32++fromWord32 :: Word.Word32 -> Splitscreen+fromWord32 = Splitscreen++toWord32 :: Splitscreen -> Word.Word32+toWord32 (Splitscreen x) = x++schema :: Schema.Schema+schema = Schema.integer++bitPut :: Splitscreen -> BitPut.BitPut+bitPut = BitPut.bits 24 . toWord32++bitGet :: BitGet.BitGet Splitscreen+bitGet = BitGet.label "Splitscreen" . fmap fromWord32 $ BitGet.bits 24
+ src/lib/Rattletrap/Type/RemoteId/Steam.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.RemoteId.Steam where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++newtype Steam+  = Steam U64.U64+  deriving (Eq, Show)++instance Json.FromJSON Steam where+  parseJSON = fmap fromU64 . Json.parseJSON++instance Json.ToJSON Steam where+  toJSON = Json.toJSON . toU64++fromU64 :: U64.U64 -> Steam+fromU64 = Steam++toU64 :: Steam -> U64.U64+toU64 (Steam x) = x++schema :: Schema.Schema+schema = U64.schema++bitPut :: Steam -> BitPut.BitPut+bitPut = U64.bitPut . toU64++bitGet :: BitGet.BitGet Steam+bitGet = BitGet.label "Steam" $ fmap fromU64 U64.bitGet
+ src/lib/Rattletrap/Type/RemoteId/Switch.hs view
@@ -0,0 +1,41 @@+module Rattletrap.Type.RemoteId.Switch where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++data Switch = Switch+  { a :: U64.U64,+    b :: U64.U64,+    c :: U64.U64,+    d :: U64.U64+  }+  deriving (Eq, Show)++instance Json.FromJSON Switch where+  parseJSON json = do+    (a, b, c, d) <- Json.parseJSON json+    pure Switch {a, b, c, d}++instance Json.ToJSON Switch where+  toJSON x = Json.toJSON (a x, b x, c x, d x)++schema :: Schema.Schema+schema =+  Schema.named "remote-id-switch" . Schema.tuple . replicate 4 $+    Schema.ref+      U64.schema++bitPut :: Switch -> BitPut.BitPut+bitPut x =+  U64.bitPut (a x) <> U64.bitPut (b x) <> U64.bitPut (c x) <> U64.bitPut (d x)++bitGet :: BitGet.BitGet Switch+bitGet = BitGet.label "Switch" $ do+  a <- U64.bitGet+  b <- U64.bitGet+  c <- U64.bitGet+  d <- U64.bitGet+  pure Switch {a, b, c, d}
+ src/lib/Rattletrap/Type/RemoteId/Xbox.hs view
@@ -0,0 +1,32 @@+module Rattletrap.Type.RemoteId.Xbox where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U64 as U64+import qualified Rattletrap.Utility.Json as Json++newtype Xbox+  = Xbox U64.U64+  deriving (Eq, Show)++instance Json.FromJSON Xbox where+  parseJSON = fmap fromU64 . Json.parseJSON++instance Json.ToJSON Xbox where+  toJSON = Json.toJSON . toU64++fromU64 :: U64.U64 -> Xbox+fromU64 = Xbox++toU64 :: Xbox -> U64.U64+toU64 (Xbox x) = x++schema :: Schema.Schema+schema = U64.schema++bitPut :: Xbox -> BitPut.BitPut+bitPut = U64.bitPut . toU64++bitGet :: BitGet.BitGet Xbox+bitGet = BitGet.label "Xbox" $ fmap fromU64 U64.bitGet
+ src/lib/Rattletrap/Type/Replay.hs view
@@ -0,0 +1,134 @@+module Rattletrap.Type.Replay where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Content as Content+import qualified Rattletrap.Type.Dictionary as Dictionary+import qualified Rattletrap.Type.Header as Header+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Type.Property as Property+import qualified Rattletrap.Type.Property.Int as Property.Int+import qualified Rattletrap.Type.Property.Name as Property.Name+import qualified Rattletrap.Type.Property.Str as Property.Str+import qualified Rattletrap.Type.PropertyValue as PropertyValue+import qualified Rattletrap.Type.Section as Section+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Version as Version++type Replay =+  ReplayWith+    (Section.Section Header.Header)+    (Section.Section Content.Content)++-- | A Rocket League replay.+data ReplayWith header content = Replay+  { -- | This has most of the high-level metadata.+    header :: header,+    -- | This has most of the low-level game data.+    content :: content+  }+  deriving (Eq, Show)++instance (Json.FromJSON h, Json.FromJSON c) => Json.FromJSON (ReplayWith h c) where+  parseJSON = Json.withObject "Replay" $ \object -> do+    header <- Json.required object "header"+    content <- Json.required object "content"+    pure Replay {header, content}++instance (Json.ToJSON h, Json.ToJSON c) => Json.ToJSON (ReplayWith h c) where+  toJSON x =+    Json.object+      [ Json.pair "$schema" schemaUrl,+        Json.pair "header" $ header x,+        Json.pair "content" $ content x+      ]++schema :: Schema.Schema -> Schema.Schema -> Schema.Schema+schema h c =+  Schema.named "replay" $+    Schema.object+      [ (Json.pair "header" $ Schema.ref h, True),+        (Json.pair "content" $ Schema.ref c, True)+      ]++schemaUrl :: String+schemaUrl =+  mconcat+    [ "https://github.com/tfausak/rattletrap/releases/download/",+      Version.string,+      "/rattletrap-",+      Version.string,+      "-schema.json"+    ]++bytePut :: Replay -> BytePut.BytePut+bytePut x =+  Section.bytePut Header.bytePut (header x)+    <> Section.bytePut Content.bytePut (content x)++byteGet :: Bool -> Bool -> ByteGet.ByteGet Replay+byteGet fast skip = ByteGet.label "Replay" $ do+  header <- ByteGet.label "header" $ do+    section <-+      Section.byteGet skip $ ByteGet.byteString . fromIntegral . U32.toWord32+    body <- ByteGet.embed Header.byteGet $ Section.body section+    pure section {Section.body}+  content <- ByteGet.label "content" $ do+    section <-+      Section.byteGet skip $ ByteGet.byteString . fromIntegral . U32.toWord32+    body <-+      if fast+        then pure Content.empty+        else+          ByteGet.embed (getContent $ Section.body header) $+            Section.body section+    pure section {Section.body}+  pure Replay {header, content}++getContent :: Header.Header -> ByteGet.ByteGet Content.Content+getContent h =+  Content.byteGet+    (getMatchType h)+    (Header.version h)+    (getNumFrames h)+    (getMaxChannels h)+    (getBuildVersion h)++getMatchType :: Header.Header -> Maybe Str.Str+getMatchType header = do+  Property.Property {Property.value} <-+    Dictionary.lookup (Str.fromString "MatchType") $ Header.properties header+  case value of+    PropertyValue.Name x -> Just $ Property.Name.toStr x+    _ -> Nothing++getNumFrames :: Header.Header -> Int+getNumFrames header_ =+  case Dictionary.lookup+    (Str.fromString "NumFrames")+    (Header.properties header_) of+    Just (Property.Property _ _ _ (PropertyValue.Int numFrames)) ->+      fromIntegral (I32.toInt32 (Property.Int.toI32 numFrames))+    _ -> 0++getMaxChannels :: Header.Header -> Word+getMaxChannels header_ =+  subtract 1 $+    case Dictionary.lookup+      (Str.fromString "MaxChannels")+      (Header.properties header_) of+      Just (Property.Property _ _ _ (PropertyValue.Int maxChannels)) ->+        fromIntegral (I32.toInt32 (Property.Int.toI32 maxChannels))+      _ -> 1023++getBuildVersion :: Header.Header -> Maybe Str.Str+getBuildVersion header = do+  property <-+    Dictionary.lookup (Str.fromString "BuildVersion") $+      Header.properties header+  case Property.value property of+    PropertyValue.Str x -> Just $ Property.Str.toStr x+    _ -> Nothing
+ src/lib/Rattletrap/Type/Replication.hs view
@@ -0,0 +1,128 @@+module Rattletrap.Type.Replication where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.ReplicationValue as ReplicationValue+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Replication = Replication+  { actorId :: CompressedWord.CompressedWord,+    value :: ReplicationValue.ReplicationValue+  }+  deriving (Eq, Show)++instance Json.FromJSON Replication where+  parseJSON = Json.withObject "Replication" $ \object -> do+    actorId <- Json.required object "actor_id"+    value <- Json.required object "value"+    pure Replication {actorId, value}++instance Json.ToJSON Replication where+  toJSON x =+    Json.object [Json.pair "actor_id" $ actorId x, Json.pair "value" $ value x]++schema :: Schema.Schema+schema =+  Schema.named "replication" $+    Schema.object+      [ (Json.pair "actor_id" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "value" $ Schema.ref ReplicationValue.schema, True)+      ]++putReplications :: RList.List Replication -> BitPut.BitPut+putReplications xs =+  foldMap (\x -> BitPut.bool True <> bitPut x) (RList.toList xs)+    <> BitPut.bool False++bitPut :: Replication -> BitPut.BitPut+bitPut replication =+  CompressedWord.bitPut (actorId replication)+    <> ReplicationValue.bitPut (value replication)++decodeReplicationsBits ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  BitGet.BitGet+    ( Map.Map CompressedWord.CompressedWord U32.U32,+      RList.List Replication+    )+decodeReplicationsBits matchType version buildVersion limit classes actorMap =+  decodeReplicationsBitsWith+    matchType+    version+    buildVersion+    limit+    classes+    actorMap+    0+    []++decodeReplicationsBitsWith ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  Int ->+  [Replication] ->+  BitGet.BitGet+    ( Map.Map CompressedWord.CompressedWord U32.U32,+      RList.List Replication+    )+decodeReplicationsBitsWith matchType version buildVersion limit classes actorMap index replications =+  do+    hasReplication <- BitGet.bool+    if hasReplication+      then do+        (newActorMap, replication) <-+          BitGet.label ("element (" <> show index <> ")") $+            bitGet matchType version buildVersion limit classes actorMap+        decodeReplicationsBitsWith+          matchType+          version+          buildVersion+          limit+          classes+          newActorMap+          (index + 1)+          $ replication+            : replications+      else pure (actorMap, RList.fromList $ reverse replications)++bitGet ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  Word ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  BitGet.BitGet+    ( Map.Map CompressedWord.CompressedWord U32.U32,+      Replication+    )+bitGet matchType version buildVersion limit classes actorMap =+  BitGet.label "Replication" $ do+    actorId <- BitGet.label "actorId" $ CompressedWord.bitGet limit+    (newActorMap, value) <-+      BitGet.label "value" $+        ReplicationValue.bitGet+          matchType+          version+          buildVersion+          classes+          actorId+          actorMap+    pure (newActorMap, Replication {actorId, value})
+ src/lib/Rattletrap/Type/Replication/Destroyed.hs view
@@ -0,0 +1,30 @@+module Rattletrap.Type.Replication.Destroyed where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++-- | Destroyed replications don't actually contain any extra information. All+-- you need to know is the actor's ID, which is given by the+-- 'Rattletrap.Replication.Replication'.+data Destroyed = Destroyed+  deriving (Eq, Show)++instance Json.FromJSON Destroyed where+  parseJSON json = do+    () <- Json.parseJSON json+    pure Destroyed++instance Json.ToJSON Destroyed where+  toJSON = const $ Json.toJSON ()++schema :: Schema.Schema+schema =+  Schema.named "replication-destroyed" $ Json.object [Json.pair "type" "array"]++bitPut :: Destroyed -> BitPut.BitPut+bitPut _ = mempty++bitGet :: BitGet.BitGet Destroyed+bitGet = BitGet.label "Destroyed" $ pure Destroyed
+ src/lib/Rattletrap/Type/Replication/Spawned.hs view
@@ -0,0 +1,164 @@+module Rattletrap.Type.Replication.Spawned where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Exception.MissingClassName as MissingClassName+import qualified Rattletrap.Exception.MissingObjectName as MissingObjectName+import qualified Rattletrap.Exception.UnknownName as UnknownName+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Initialization as Initialization+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++data Spawned = Spawned+  { -- | Unclear what this is.+    flag :: Bool,+    nameIndex :: Maybe U32.U32,+    -- | Read-only! Changing a replication's name requires editing the+    -- 'nameIndex' and maybe the class attribute map.+    name :: Maybe Str.Str,+    objectId :: U32.U32,+    -- | Read-only! Changing a replication's object requires editing the class+    -- attribute map.+    objectName :: Str.Str,+    -- | Read-only! Changing a replication's class requires editing the class+    -- attribute map.+    className :: Str.Str,+    initialization :: Initialization.Initialization+  }+  deriving (Eq, Show)++instance Json.FromJSON Spawned where+  parseJSON = Json.withObject "Spawned" $ \object -> do+    flag <- Json.required object "flag"+    nameIndex <- Json.optional object "name_index"+    name <- Json.optional object "name"+    objectId <- Json.required object "object_id"+    objectName <- Json.required object "object_name"+    className <- Json.required object "class_name"+    initialization <- Json.required object "initialization"+    pure+      Spawned+        { flag,+          nameIndex,+          name,+          objectId,+          objectName,+          className,+          initialization+        }++instance Json.ToJSON Spawned where+  toJSON x =+    Json.object+      [ Json.pair "flag" $ flag x,+        Json.pair "name_index" $ nameIndex x,+        Json.pair "name" $ name x,+        Json.pair "object_id" $ objectId x,+        Json.pair "object_name" $ objectName x,+        Json.pair "class_name" $ className x,+        Json.pair "initialization" $ initialization x+      ]++schema :: Schema.Schema+schema =+  Schema.named "replication-spawned" $+    Schema.object+      [ (Json.pair "flag" $ Schema.ref Schema.boolean, True),+        (Json.pair "name_index" . Schema.json $ Schema.maybe U32.schema, False),+        (Json.pair "name" . Schema.json $ Schema.maybe Str.schema, False),+        (Json.pair "object_id" $ Schema.ref U32.schema, True),+        (Json.pair "object_name" $ Schema.ref Str.schema, True),+        (Json.pair "class_name" $ Schema.ref Str.schema, True),+        (Json.pair "initialization" $ Schema.ref Initialization.schema, True)+      ]++bitPut :: Spawned -> BitPut.BitPut+bitPut spawnedReplication =+  BitPut.bool (flag spawnedReplication)+    <> foldMap U32.bitPut (nameIndex spawnedReplication)+    <> U32.bitPut (objectId spawnedReplication)+    <> Initialization.bitPut (initialization spawnedReplication)++bitGet ::+  Maybe Str.Str ->+  Version.Version ->+  ClassAttributeMap.ClassAttributeMap ->+  CompressedWord.CompressedWord ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  BitGet.BitGet+    (Map.Map CompressedWord.CompressedWord U32.U32, Spawned)+bitGet matchType version classAttributeMap actorId actorMap =+  BitGet.label "Spawned" $ do+    flag <- BitGet.label "flag" BitGet.bool+    nameIndex <-+      BitGet.label "nameIndex" $+        Monad.whenMaybe (hasNameIndex matchType version) U32.bitGet+    name <- lookupName classAttributeMap nameIndex+    objectId <- BitGet.label "objectId" U32.bitGet+    objectName <- lookupObjectName classAttributeMap objectId+    className <- lookupClassName objectName+    let hasLocation = ClassAttributeMap.classHasLocation className+    let hasRotation = ClassAttributeMap.classHasRotation className+    initialization <-+      BitGet.label "initialization" $+        Initialization.bitGet version hasLocation hasRotation+    pure+      ( Map.insert actorId objectId actorMap,+        Spawned+          { flag,+            nameIndex,+            name,+            objectId,+            objectName,+            className,+            initialization+          }+      )++hasNameIndex :: Maybe Str.Str -> Version.Version -> Bool+hasNameIndex matchType version =+  Version.atLeast 868 20 0 version+    || Version.atLeast 868 14 0 version+      && (matchType /= Just (Str.fromString "Lan"))++lookupName ::+  ClassAttributeMap.ClassAttributeMap ->+  Maybe U32.U32 ->+  BitGet.BitGet (Maybe Str.Str)+lookupName classAttributeMap maybeNameIndex = case maybeNameIndex of+  Nothing -> pure Nothing+  Just nameIndex_ ->+    case ClassAttributeMap.getName+      (ClassAttributeMap.nameMap classAttributeMap)+      nameIndex_ of+      Nothing ->+        BitGet.throw . UnknownName.UnknownName $ U32.toWord32 nameIndex_+      Just name_ -> pure (Just name_)++lookupObjectName ::+  ClassAttributeMap.ClassAttributeMap -> U32.U32 -> BitGet.BitGet Str.Str+lookupObjectName classAttributeMap objectId_ =+  case ClassAttributeMap.getObjectName+    (ClassAttributeMap.objectMap classAttributeMap)+    objectId_ of+    Nothing ->+      BitGet.throw . MissingObjectName.MissingObjectName $+        U32.toWord32+          objectId_+    Just objectName_ -> pure objectName_++lookupClassName :: Str.Str -> BitGet.BitGet Str.Str+lookupClassName objectName_ =+  case ClassAttributeMap.getClassName objectName_ of+    Nothing ->+      BitGet.throw . MissingClassName.MissingClassName $+        Str.toString+          objectName_+    Just className_ -> pure className_
+ src/lib/Rattletrap/Type/Replication/Updated.hs view
@@ -0,0 +1,52 @@+module Rattletrap.Type.Replication.Updated where++import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.Attribute as Attribute+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.List as RList+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json+import qualified Rattletrap.Utility.Monad as Monad++newtype Updated = Updated+  { attributes :: RList.List Attribute.Attribute+  }+  deriving (Eq, Show)++instance Json.FromJSON Updated where+  parseJSON = fmap Updated . Json.parseJSON++instance Json.ToJSON Updated where+  toJSON = Json.toJSON . attributes++schema :: Schema.Schema+schema =+  Schema.named "replication-updated" . Schema.json $+    RList.schema+      Attribute.schema++bitPut :: Updated -> BitPut.BitPut+bitPut x =+  foldMap+    (\y -> BitPut.bool True <> Attribute.bitPut y)+    (RList.toList $ attributes x)+    <> BitPut.bool False++bitGet ::+  Version.Version ->+  Maybe Str.Str ->+  ClassAttributeMap.ClassAttributeMap ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  CompressedWord.CompressedWord ->+  BitGet.BitGet Updated+bitGet version buildVersion classes actors actor =+  BitGet.label "Updated" . fmap Updated . RList.untilM $ do+    p <- BitGet.bool+    Monad.whenMaybe p $+      Attribute.bitGet version buildVersion classes actors actor
+ src/lib/Rattletrap/Type/ReplicationValue.hs view
@@ -0,0 +1,95 @@+module Rattletrap.Type.ReplicationValue where++import qualified Data.Foldable as Foldable+import qualified Data.Map as Map+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Replication.Destroyed as Destroyed+import qualified Rattletrap.Type.Replication.Spawned as Spawned+import qualified Rattletrap.Type.Replication.Updated as Updated+import qualified Rattletrap.Type.Str as Str+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data ReplicationValue+  = -- | Creates a new actor.+    Spawned Spawned.Spawned+  | -- | Updates an existing actor.+    Updated Updated.Updated+  | -- | Destroys an existing actor.+    Destroyed Destroyed.Destroyed+  deriving (Eq, Show)++instance Json.FromJSON ReplicationValue where+  parseJSON = Json.withObject "ReplicationValue" $ \object ->+    Foldable.asum+      [ fmap Spawned $ Json.required object "spawned",+        fmap Updated $ Json.required object "updated",+        fmap Destroyed $ Json.required object "destroyed"+      ]++instance Json.ToJSON ReplicationValue where+  toJSON x = case x of+    Spawned y -> Json.object [Json.pair "spawned" y]+    Updated y -> Json.object [Json.pair "updated" y]+    Destroyed y -> Json.object [Json.pair "destroyed" y]++schema :: Schema.Schema+schema =+  Schema.named "replicationValue" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k $ Schema.ref v, True)])+      [ ("spawned", Spawned.schema),+        ("updated", Updated.schema),+        ("destroyed", Destroyed.schema)+      ]++bitPut :: ReplicationValue -> BitPut.BitPut+bitPut value = case value of+  Spawned x -> BitPut.bool True <> BitPut.bool True <> Spawned.bitPut x+  Updated x -> BitPut.bool True <> BitPut.bool False <> Updated.bitPut x+  Destroyed x -> BitPut.bool False <> Destroyed.bitPut x++bitGet ::+  Maybe Str.Str ->+  Version.Version ->+  Maybe Str.Str ->+  ClassAttributeMap.ClassAttributeMap ->+  CompressedWord.CompressedWord ->+  Map.Map CompressedWord.CompressedWord U32.U32 ->+  BitGet.BitGet+    ( Map.Map CompressedWord.CompressedWord U32.U32,+      ReplicationValue+    )+bitGet matchType version buildVersion classAttributeMap actorId actorMap =+  BitGet.label "ReplicationValue" $ do+    isOpen <- BitGet.bool+    if isOpen+      then do+        isNew <- BitGet.bool+        if isNew+          then do+            (newActorMap, spawned) <-+              Spawned.bitGet+                matchType+                version+                classAttributeMap+                actorId+                actorMap+            pure (newActorMap, Spawned spawned)+          else do+            updated <-+              Updated.bitGet+                version+                buildVersion+                classAttributeMap+                actorMap+                actorId+            pure (actorMap, Updated updated)+      else do+        destroyed <- Destroyed.bitGet+        pure (actorMap, Destroyed destroyed)
+ src/lib/Rattletrap/Type/Rotation.hs view
@@ -0,0 +1,48 @@+module Rattletrap.Type.Rotation where++import qualified Data.Foldable as Foldable+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.CompressedWordVector as CompressedWordVector+import qualified Rattletrap.Type.Quaternion as Quaternion+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Rotation+  = CompressedWordVector CompressedWordVector.CompressedWordVector+  | Quaternion Quaternion.Quaternion+  deriving (Eq, Show)++instance Json.FromJSON Rotation where+  parseJSON = Json.withObject "Rotation" $ \object ->+    Foldable.asum+      [ fmap CompressedWordVector $ Json.required object "compressed_word_vector",+        fmap Quaternion $ Json.required object "quaternion"+      ]++instance Json.ToJSON Rotation where+  toJSON x = case x of+    CompressedWordVector y ->+      Json.object [Json.pair "compressed_word_vector" y]+    Quaternion y -> Json.object [Json.pair "quaternion" y]++schema :: Schema.Schema+schema =+  Schema.named "rotation" . Schema.oneOf $+    fmap+      (\(k, v) -> Schema.object [(Json.pair k $ Schema.ref v, True)])+      [ ("compressed_word_vector", CompressedWordVector.schema),+        ("quaternion", Quaternion.schema)+      ]++bitPut :: Rotation -> BitPut.BitPut+bitPut r = case r of+  CompressedWordVector cwv -> CompressedWordVector.bitPut cwv+  Quaternion q -> Quaternion.bitPut q++bitGet :: Version.Version -> BitGet.BitGet Rotation+bitGet version =+  if Version.atLeast 868 22 7 version+    then fmap Quaternion Quaternion.bitGet+    else fmap CompressedWordVector CompressedWordVector.bitGet
+ src/lib/Rattletrap/Type/Section.hs view
@@ -0,0 +1,96 @@+module Rattletrap.Type.Section where++import qualified Control.Monad as Monad+import qualified Data.ByteString as ByteString+import qualified Data.Text as Text+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Exception.CrcMismatch as CrcMismatch+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Crc as Crc+import qualified Rattletrap.Utility.Json as Json++-- | A section is a large piece of a 'Rattletrap.Replay.Replay'. It has a+-- 32-bit size (in bytes), a 32-bit CRC (see "Rattletrap.Utility.Crc"), and then a+-- bunch of data (the body). This interface is provided so that you don't have+-- to think about the size and CRC.+data Section a = Section+  { -- | read only+    size :: U32.U32,+    -- | read only+    crc :: U32.U32,+    -- | The actual content in the section.+    body :: a+  }+  deriving (Eq, Show)++instance (Json.FromJSON a) => Json.FromJSON (Section a) where+  parseJSON = Json.withObject "Section" $ \object -> do+    size <- Json.required object "size"+    crc <- Json.required object "crc"+    body <- Json.required object "body"+    pure Section {size, crc, body}++instance (Json.ToJSON a) => Json.ToJSON (Section a) where+  toJSON x =+    Json.object+      [ Json.pair "size" $ size x,+        Json.pair "crc" $ crc x,+        Json.pair "body" $ body x+      ]++schema :: Schema.Schema -> Schema.Schema+schema s =+  Schema.named ("section-" <> Text.unpack (Schema.name s)) $+    Schema.object+      [ (Json.pair "size" $ Schema.ref U32.schema, True),+        (Json.pair "crc" $ Schema.ref U32.schema, True),+        (Json.pair "body" $ Schema.ref s, True)+      ]++create :: (a -> BytePut.BytePut) -> a -> Section a+create encode body_ =+  let bytes = BytePut.toByteString $ encode body_+   in Section+        { size = U32.fromWord32 . fromIntegral $ ByteString.length bytes,+          crc = U32.fromWord32 $ Crc.compute bytes,+          body = body_+        }++-- | Given a way to put the 'body', puts a section. This will also put+-- the size and CRC.+bytePut :: (a -> BytePut.BytePut) -> Section a -> BytePut.BytePut+bytePut putBody section =+  let rawBody = BytePut.toByteString . putBody $ body section+      size_ = ByteString.length rawBody+      crc_ = Crc.compute rawBody+   in U32.bytePut (U32.fromWord32 (fromIntegral size_))+        <> U32.bytePut (U32.fromWord32 crc_)+        <> BytePut.byteString rawBody++byteGet ::+  Bool -> (U32.U32 -> ByteGet.ByteGet a) -> ByteGet.ByteGet (Section a)+byteGet skip getBody = ByteGet.label "Section" $ do+  size <- ByteGet.label "size" U32.byteGet+  crc <- ByteGet.label "crc" U32.byteGet+  body <- ByteGet.label "body" $ do+    rawBody <- ByteGet.byteString . fromIntegral $ U32.toWord32 size+    Monad.unless skip $ do+      let expected = U32.toWord32 crc+          actual = Crc.compute rawBody+      Monad.when (actual /= expected) . ByteGet.throw $+        CrcMismatch.CrcMismatch+          expected+          actual+    ByteGet.embed (getBody size) rawBody+  pure Section {size, crc, body}++crcMessage :: U32.U32 -> U32.U32 -> String+crcMessage actual expected =+  unwords+    [ "[RT10] actual CRC",+      show actual,+      "does not match expected CRC",+      show expected+    ]
+ src/lib/Rattletrap/Type/Str.hs view
@@ -0,0 +1,101 @@+module Rattletrap.Type.Str where++import qualified Data.ByteString as ByteString+import qualified Data.Char as Char+import qualified Data.Int as Int+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Text.Encoding.Error as Text+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.I32 as I32+import qualified Rattletrap.Utility.Bytes as Bytes+import qualified Rattletrap.Utility.Json as Json++newtype Str+  = Str Text.Text+  deriving (Eq, Ord, Show)++instance Json.FromJSON Str where+  parseJSON = fmap fromText . Json.parseJSON++instance Json.ToJSON Str where+  toJSON = Json.toJSON . toText++schema :: Schema.Schema+schema = Schema.named "str" $ Json.object [Json.pair "type" "string"]++fromText :: Text.Text -> Str+fromText = Str++toText :: Str -> Text.Text+toText (Str x) = x++fromString :: String -> Str+fromString = fromText . Text.pack++toString :: Str -> String+toString = Text.unpack . toText++bytePut :: Str -> BytePut.BytePut+bytePut text =+  let size = getTextSize text+      encode = getTextEncoder size+   in I32.bytePut size <> (BytePut.byteString . encode . addNull $ toText text)++bitPut :: Str -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++getTextSize :: Str -> I32.I32+getTextSize text =+  let value = toText text+      scale = if Text.all Char.isLatin1 value then 1 else -1 :: Int.Int32+      rawSize =+        if Text.null value+          then 0+          else fromIntegral (Text.length value) + 1 :: Int.Int32+      size =+        if value == Text.pack "\x00\x00\x00None"+          then 0x05000000+          else scale * rawSize :: Int.Int32+   in I32.fromInt32 size++getTextEncoder :: I32.I32 -> Text.Text -> ByteString.ByteString+getTextEncoder size text =+  if I32.toInt32 size < 0+    then Text.encodeUtf16LE text+    else Bytes.encodeLatin1 text++addNull :: Text.Text -> Text.Text+addNull text = if Text.null text then text else Text.snoc text '\x00'++byteGet :: ByteGet.ByteGet Str+byteGet = ByteGet.label "Str" $ do+  size <- ByteGet.label "size" I32.byteGet+  bytes <- ByteGet.label "value" . ByteGet.byteString $ normalizeTextSize size+  pure . fromText . dropNull $ getTextDecoder size bytes++bitGet :: BitGet.BitGet Str+bitGet = do+  rawSize <- I32.bitGet+  bytes <- BitGet.byteString (normalizeTextSize rawSize)+  pure (fromText (dropNull (getTextDecoder rawSize bytes)))++normalizeTextSize :: (Integral a) => I32.I32 -> a+normalizeTextSize size = case I32.toInt32 size of+  0x05000000 -> 8+  x -> (if x < 0 then negate . (*) 2 else id) $ fromIntegral x++getTextDecoder :: I32.I32 -> ByteString.ByteString -> Text.Text+getTextDecoder size bytes =+  let decode =+        if I32.toInt32 size < 0+          then Text.decodeUtf16LEWith Text.lenientDecode+          else Text.decodeLatin1+   in decode bytes++dropNull :: Text.Text -> Text.Text+dropNull = Text.dropWhileEnd (== '\x00')
+ src/lib/Rattletrap/Type/U32.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.U32 where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype U32+  = U32 Word.Word32+  deriving (Eq, Ord, Show)++instance Json.FromJSON U32 where+  parseJSON = fmap fromWord32 . Json.parseJSON++instance Json.ToJSON U32 where+  toJSON = Json.toJSON . toWord32++schema :: Schema.Schema+schema =+  Schema.named "u32" $+    Json.object+      [ Json.pair "type" "integer",+        Json.pair "minimum" (minBound :: Word.Word32),+        Json.pair "maximum" (maxBound :: Word.Word32)+      ]++fromWord32 :: Word.Word32 -> U32+fromWord32 = U32++toWord32 :: U32 -> Word.Word32+toWord32 (U32 x) = x++bytePut :: U32 -> BytePut.BytePut+bytePut = BytePut.word32 . toWord32++bitPut :: U32 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet U32+byteGet = ByteGet.label "U32" $ fmap fromWord32 ByteGet.word32++bitGet :: BitGet.BitGet U32+bitGet = BitGet.fromByteGet byteGet 4
+ src/lib/Rattletrap/Type/U64.hs view
@@ -0,0 +1,48 @@+module Rattletrap.Type.U64 where++import qualified Data.Text as Text+import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json+import qualified Text.Read as Read++newtype U64+  = U64 Word.Word64+  deriving (Eq, Show)++instance Json.FromJSON U64 where+  parseJSON =+    Json.withText "U64" $+      either fail (pure . fromWord64)+        . Read.readEither+        . Text.unpack++instance Json.ToJSON U64 where+  toJSON = Json.toJSON . show . toWord64++schema :: Schema.Schema+schema =+  Schema.named "u64" $+    Json.object [Json.pair "type" "string", Json.pair "pattern" "^[0-9]+$"]++fromWord64 :: Word.Word64 -> U64+fromWord64 = U64++toWord64 :: U64 -> Word.Word64+toWord64 (U64 x) = x++bytePut :: U64 -> BytePut.BytePut+bytePut = BytePut.word64 . toWord64++bitPut :: U64 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet U64+byteGet = ByteGet.label "U64" $ fmap fromWord64 ByteGet.word64++bitGet :: BitGet.BitGet U64+bitGet = BitGet.fromByteGet byteGet 8
+ src/lib/Rattletrap/Type/U8.hs view
@@ -0,0 +1,46 @@+module Rattletrap.Type.U8 where++import qualified Data.Word as Word+import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Utility.Json as Json++newtype U8+  = U8 Word.Word8+  deriving (Eq, Show)++instance Json.FromJSON U8 where+  parseJSON = fmap fromWord8 . Json.parseJSON++instance Json.ToJSON U8 where+  toJSON = Json.toJSON . toWord8++schema :: Schema.Schema+schema =+  Schema.named "u8" $+    Json.object+      [ Json.pair "type" "integer",+        Json.pair "minimum" (minBound :: Word.Word8),+        Json.pair "maximum" (maxBound :: Word.Word8)+      ]++fromWord8 :: Word.Word8 -> U8+fromWord8 = U8++toWord8 :: U8 -> Word.Word8+toWord8 (U8 x) = x++bytePut :: U8 -> BytePut.BytePut+bytePut = BytePut.word8 . toWord8++bitPut :: U8 -> BitPut.BitPut+bitPut = BitPut.fromBytePut . bytePut++byteGet :: ByteGet.ByteGet U8+byteGet = ByteGet.label "U8" $ fmap fromWord8 ByteGet.word8++bitGet :: BitGet.BitGet U8+bitGet = BitGet.fromByteGet byteGet 1
+ src/lib/Rattletrap/Type/Vector.hs view
@@ -0,0 +1,93 @@+module Rattletrap.Type.Vector where++import qualified Rattletrap.BitGet as BitGet+import qualified Rattletrap.BitPut as BitPut+import qualified Rattletrap.Schema as Schema+import qualified Rattletrap.Type.CompressedWord as CompressedWord+import qualified Rattletrap.Type.Version as Version+import qualified Rattletrap.Utility.Json as Json++data Vector = Vector+  { size :: CompressedWord.CompressedWord,+    -- | This field is guaranteed to be small. In other words, it won't overflow.+    -- It's stored as a regular 'Word' rather than something more precise like a+    -- 'Word8' because it just gets passed to a functions that expect 'Word's.+    -- There's no reason to do a bunch of conversions.+    bias :: Word,+    -- | See 'bias'.+    x :: Int,+    -- | See 'bias'.+    y :: Int,+    -- | See 'bias'.+    z :: Int+  }+  deriving (Eq, Show)++instance Json.FromJSON Vector where+  parseJSON = Json.withObject "Vector" $ \object -> do+    size <- Json.required object "size"+    bias <- Json.required object "bias"+    x <- Json.required object "x"+    y <- Json.required object "y"+    z <- Json.required object "z"+    pure Vector {size, bias, x, y, z}++instance Json.ToJSON Vector where+  toJSON a =+    Json.object+      [ Json.pair "size" $ size a,+        Json.pair "bias" $ bias a,+        Json.pair "x" $ x a,+        Json.pair "y" $ y a,+        Json.pair "z" $ z a+      ]++schema :: Schema.Schema+schema =+  Schema.named "vector" $+    Schema.object+      [ (Json.pair "size" $ Schema.ref CompressedWord.schema, True),+        (Json.pair "bias" $ Schema.ref Schema.integer, True),+        (Json.pair "x" $ Schema.ref Schema.integer, True),+        (Json.pair "y" $ Schema.ref Schema.integer, True),+        (Json.pair "z" $ Schema.ref Schema.integer, True)+      ]++bitPut :: Vector -> BitPut.BitPut+bitPut vector =+  let bitSize =+        round (logBase (2 :: Float) (fromIntegral (bias vector))) - 1 :: Word+      dx = fromIntegral (x vector + fromIntegral (bias vector)) :: Word+      dy = fromIntegral (y vector + fromIntegral (bias vector)) :: Word+      dz = fromIntegral (z vector + fromIntegral (bias vector)) :: Word+      limit = 2 ^ (bitSize + 2) :: Word+   in CompressedWord.bitPut (size vector)+        <> CompressedWord.bitPut (CompressedWord.CompressedWord limit dx)+        <> CompressedWord.bitPut (CompressedWord.CompressedWord limit dy)+        <> CompressedWord.bitPut (CompressedWord.CompressedWord limit dz)++bitGet :: Version.Version -> BitGet.BitGet Vector+bitGet version = BitGet.label "Vector" $ do+  size <-+    BitGet.label "size"+      . CompressedWord.bitGet+      $ if Version.atLeast 868 22 7 version then 21 else 19+  let limit = getLimit size+      bias = getBias size+  x <- BitGet.label "x" $ getPart limit bias+  y <- BitGet.label "y" $ getPart limit bias+  z <- BitGet.label "z" $ getPart limit bias+  pure Vector {size, bias, x, y, z}++getPart :: Word -> Word -> BitGet.BitGet Int+getPart limit bias = fmap (fromDelta bias) (CompressedWord.bitGet limit)++getLimit :: CompressedWord.CompressedWord -> Word+getLimit = (2 ^) . (+ 2) . CompressedWord.value++getBias :: CompressedWord.CompressedWord -> Word+getBias = (2 ^) . (+ 1) . CompressedWord.value++fromDelta :: Word -> CompressedWord.CompressedWord -> Int+fromDelta bias_ x_ =+  fromIntegral (CompressedWord.value x_) - fromIntegral bias_
+ src/lib/Rattletrap/Type/Version.hs view
@@ -0,0 +1,38 @@+module Rattletrap.Type.Version where++import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Type.U32 as U32+import qualified Rattletrap.Utility.Monad as Monad++data Version = Version+  { major :: U32.U32,+    minor :: U32.U32,+    patch :: Maybe U32.U32+  }+  deriving (Eq, Show)++atLeast :: Int -> Int -> Int -> Version -> Bool+atLeast m n p v =+  (U32.toWord32 (major v) >= fromIntegral m)+    && (U32.toWord32 (minor v) >= fromIntegral n)+    && (maybe 0 U32.toWord32 (patch v) >= fromIntegral p)++bytePut :: Version -> BytePut.BytePut+bytePut x =+  U32.bytePut (major x)+    <> U32.bytePut (minor x)+    <> foldMap+      U32.bytePut+      (patch x)++byteGet :: ByteGet.ByteGet Version+byteGet = ByteGet.label "Version" $ do+  major <- ByteGet.label "major" U32.byteGet+  minor <- ByteGet.label "minor" U32.byteGet+  patch <-+    ByteGet.label "patch" $+      Monad.whenMaybe+        (U32.toWord32 major >= 868 && U32.toWord32 minor >= 18)+        U32.byteGet+  pure Version {major, minor, patch}
+ src/lib/Rattletrap/Utility/Bytes.hs view
@@ -0,0 +1,13 @@+module Rattletrap.Utility.Bytes where++import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Latin1+import qualified Data.Text as Text++encodeLatin1 :: Text.Text -> ByteString.ByteString+encodeLatin1 text = Latin1.pack (Text.unpack text)++padBytes :: (Integral a) => a -> ByteString.ByteString -> ByteString.ByteString+padBytes size bytes =+  bytes+    <> ByteString.replicate (fromIntegral size - ByteString.length bytes) 0x00
+ src/lib/Rattletrap/Utility/Crc.hs view
@@ -0,0 +1,293 @@+module Rattletrap.Utility.Crc where++import qualified Data.Array.Unboxed as Array+import qualified Data.Bits as Bits+import qualified Data.ByteString as ByteString+import qualified Data.Word as Word++-- | Computes the CRC32 of some bytes. This is done to ensure that the bytes+-- are valid before trying to parse them.+--+-- @+-- compute ('Data.ByteString.Lazy.pack' [0x00])+-- @+--+-- This CRC uses an initial value of @0xefcbf201@ and a polynomial of+-- @0x04c11db7@.+compute :: ByteString.ByteString -> Word.Word32+compute = Bits.complement . ByteString.foldl' update initial++update :: Word.Word32 -> Word.Word8 -> Word.Word32+update crc byte =+  let index = Bits.xor byte . unsafeWord32ToWord8 $ Bits.shiftR crc 24+      left = table Array.! index+      right = Bits.shiftL crc 8+   in Bits.xor left right++unsafeWord32ToWord8 :: Word.Word32 -> Word.Word8+unsafeWord32ToWord8 = fromIntegral++initial :: Word.Word32+initial = Bits.complement 0xefcbf201++table :: Array.Array Word.Word8 Word.Word32+table =+  Array.listArray+    (0, 255)+    [ 0x00000000,+      0x04c11db7,+      0x09823b6e,+      0x0d4326d9,+      0x130476dc,+      0x17c56b6b,+      0x1a864db2,+      0x1e475005,+      0x2608edb8,+      0x22c9f00f,+      0x2f8ad6d6,+      0x2b4bcb61,+      0x350c9b64,+      0x31cd86d3,+      0x3c8ea00a,+      0x384fbdbd,+      0x4c11db70,+      0x48d0c6c7,+      0x4593e01e,+      0x4152fda9,+      0x5f15adac,+      0x5bd4b01b,+      0x569796c2,+      0x52568b75,+      0x6a1936c8,+      0x6ed82b7f,+      0x639b0da6,+      0x675a1011,+      0x791d4014,+      0x7ddc5da3,+      0x709f7b7a,+      0x745e66cd,+      0x9823b6e0,+      0x9ce2ab57,+      0x91a18d8e,+      0x95609039,+      0x8b27c03c,+      0x8fe6dd8b,+      0x82a5fb52,+      0x8664e6e5,+      0xbe2b5b58,+      0xbaea46ef,+      0xb7a96036,+      0xb3687d81,+      0xad2f2d84,+      0xa9ee3033,+      0xa4ad16ea,+      0xa06c0b5d,+      0xd4326d90,+      0xd0f37027,+      0xddb056fe,+      0xd9714b49,+      0xc7361b4c,+      0xc3f706fb,+      0xceb42022,+      0xca753d95,+      0xf23a8028,+      0xf6fb9d9f,+      0xfbb8bb46,+      0xff79a6f1,+      0xe13ef6f4,+      0xe5ffeb43,+      0xe8bccd9a,+      0xec7dd02d,+      0x34867077,+      0x30476dc0,+      0x3d044b19,+      0x39c556ae,+      0x278206ab,+      0x23431b1c,+      0x2e003dc5,+      0x2ac12072,+      0x128e9dcf,+      0x164f8078,+      0x1b0ca6a1,+      0x1fcdbb16,+      0x018aeb13,+      0x054bf6a4,+      0x0808d07d,+      0x0cc9cdca,+      0x7897ab07,+      0x7c56b6b0,+      0x71159069,+      0x75d48dde,+      0x6b93dddb,+      0x6f52c06c,+      0x6211e6b5,+      0x66d0fb02,+      0x5e9f46bf,+      0x5a5e5b08,+      0x571d7dd1,+      0x53dc6066,+      0x4d9b3063,+      0x495a2dd4,+      0x44190b0d,+      0x40d816ba,+      0xaca5c697,+      0xa864db20,+      0xa527fdf9,+      0xa1e6e04e,+      0xbfa1b04b,+      0xbb60adfc,+      0xb6238b25,+      0xb2e29692,+      0x8aad2b2f,+      0x8e6c3698,+      0x832f1041,+      0x87ee0df6,+      0x99a95df3,+      0x9d684044,+      0x902b669d,+      0x94ea7b2a,+      0xe0b41de7,+      0xe4750050,+      0xe9362689,+      0xedf73b3e,+      0xf3b06b3b,+      0xf771768c,+      0xfa325055,+      0xfef34de2,+      0xc6bcf05f,+      0xc27dede8,+      0xcf3ecb31,+      0xcbffd686,+      0xd5b88683,+      0xd1799b34,+      0xdc3abded,+      0xd8fba05a,+      0x690ce0ee,+      0x6dcdfd59,+      0x608edb80,+      0x644fc637,+      0x7a089632,+      0x7ec98b85,+      0x738aad5c,+      0x774bb0eb,+      0x4f040d56,+      0x4bc510e1,+      0x46863638,+      0x42472b8f,+      0x5c007b8a,+      0x58c1663d,+      0x558240e4,+      0x51435d53,+      0x251d3b9e,+      0x21dc2629,+      0x2c9f00f0,+      0x285e1d47,+      0x36194d42,+      0x32d850f5,+      0x3f9b762c,+      0x3b5a6b9b,+      0x0315d626,+      0x07d4cb91,+      0x0a97ed48,+      0x0e56f0ff,+      0x1011a0fa,+      0x14d0bd4d,+      0x19939b94,+      0x1d528623,+      0xf12f560e,+      0xf5ee4bb9,+      0xf8ad6d60,+      0xfc6c70d7,+      0xe22b20d2,+      0xe6ea3d65,+      0xeba91bbc,+      0xef68060b,+      0xd727bbb6,+      0xd3e6a601,+      0xdea580d8,+      0xda649d6f,+      0xc423cd6a,+      0xc0e2d0dd,+      0xcda1f604,+      0xc960ebb3,+      0xbd3e8d7e,+      0xb9ff90c9,+      0xb4bcb610,+      0xb07daba7,+      0xae3afba2,+      0xaafbe615,+      0xa7b8c0cc,+      0xa379dd7b,+      0x9b3660c6,+      0x9ff77d71,+      0x92b45ba8,+      0x9675461f,+      0x8832161a,+      0x8cf30bad,+      0x81b02d74,+      0x857130c3,+      0x5d8a9099,+      0x594b8d2e,+      0x5408abf7,+      0x50c9b640,+      0x4e8ee645,+      0x4a4ffbf2,+      0x470cdd2b,+      0x43cdc09c,+      0x7b827d21,+      0x7f436096,+      0x7200464f,+      0x76c15bf8,+      0x68860bfd,+      0x6c47164a,+      0x61043093,+      0x65c52d24,+      0x119b4be9,+      0x155a565e,+      0x18197087,+      0x1cd86d30,+      0x029f3d35,+      0x065e2082,+      0x0b1d065b,+      0x0fdc1bec,+      0x3793a651,+      0x3352bbe6,+      0x3e119d3f,+      0x3ad08088,+      0x2497d08d,+      0x2056cd3a,+      0x2d15ebe3,+      0x29d4f654,+      0xc5a92679,+      0xc1683bce,+      0xcc2b1d17,+      0xc8ea00a0,+      0xd6ad50a5,+      0xd26c4d12,+      0xdf2f6bcb,+      0xdbee767c,+      0xe3a1cbc1,+      0xe760d676,+      0xea23f0af,+      0xeee2ed18,+      0xf0a5bd1d,+      0xf464a0aa,+      0xf9278673,+      0xfde69bc4,+      0x89b8fd09,+      0x8d79e0be,+      0x803ac667,+      0x84fbdbd0,+      0x9abc8bd5,+      0x9e7d9662,+      0x933eb0bb,+      0x97ffad0c,+      0xafb010b1,+      0xab710d06,+      0xa6322bdf,+      0xa2f33668,+      0xbcb4666d,+      0xb8757bda,+      0xb5365d03,+      0xb1f740b4+    ]
+ src/lib/Rattletrap/Utility/Helper.hs view
@@ -0,0 +1,46 @@+-- | This module provides helper functions for converting replays to and from+-- both their binary format and JSON.+module Rattletrap.Utility.Helper where++import qualified Control.Exception as Exception+import qualified Data.Bifunctor as Bifunctor+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Rattletrap.ByteGet as ByteGet+import qualified Rattletrap.BytePut as BytePut+import qualified Rattletrap.Exception.InvalidJson as InvalidJson+import qualified Rattletrap.Type.Content as Content+import qualified Rattletrap.Type.Replay as Replay+import qualified Rattletrap.Type.Section as Section+import qualified Rattletrap.Utility.Json as Json++-- | Parses a raw replay.+decodeReplayFile ::+  Bool ->+  Bool ->+  ByteString.ByteString ->+  Either ([String], Exception.SomeException) Replay.Replay+decodeReplayFile fast = ByteGet.run . Replay.byteGet fast++-- | Encodes a replay as JSON.+encodeReplayJson :: Replay.Replay -> LazyByteString.ByteString+encodeReplayJson = Json.encodePretty++-- | Parses a JSON replay.+decodeReplayJson ::+  ByteString.ByteString ->+  Either ([String], Exception.SomeException) Replay.Replay+decodeReplayJson =+  Bifunctor.first ((,) [] . Exception.toException . InvalidJson.InvalidJson)+    . Json.decode++-- | Encodes a raw replay.+encodeReplayFile :: Bool -> Replay.Replay -> LazyByteString.ByteString+encodeReplayFile fast replay =+  BytePut.toLazyByteString . Replay.bytePut $+    if fast+      then+        replay+          { Replay.content = Section.create Content.bytePut Content.empty+          }+      else replay
+ src/lib/Rattletrap/Utility/Json.hs view
@@ -0,0 +1,55 @@+{- hlint ignore "Avoid restricted extensions" -}+{-# LANGUAGE CPP #-}++module Rattletrap.Utility.Json+  ( module Rattletrap.Utility.Json,+    Aeson.FromJSON (parseJSON),+    Key.Key,+    Aeson.ToJSON (toJSON),+    Aeson.Value,+    Aeson.encode,+    Aeson.object,+    Aeson.withObject,+    Aeson.withText,+  )+where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Encode.Pretty as Aeson+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.Types as Aeson+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString++keyToString :: Key.Key -> String+keyToString = Key.toString++required ::+  (Aeson.FromJSON value) => Aeson.Object -> String -> Aeson.Parser value+required object key = object Aeson..: Key.fromString key++optional ::+  (Aeson.FromJSON value) =>+  Aeson.Object ->+  String ->+  Aeson.Parser (Maybe value)+optional object key = object Aeson..:? Key.fromString key++# if MIN_VERSION_aeson(2, 2, 0)+pair :: (Aeson.ToJSON value, Aeson.KeyValue e p) => String -> value -> p+# else+pair :: (Aeson.ToJSON value, Aeson.KeyValue p) => String -> value -> p+# endif+pair key value = Key.fromString key Aeson..= value++decode :: (Aeson.FromJSON a) => ByteString.ByteString -> Either String a+decode = Aeson.eitherDecodeStrict'++encodePretty :: (Aeson.ToJSON a) => a -> LazyByteString.ByteString+encodePretty =+  Aeson.encodePretty'+    Aeson.defConfig+      { Aeson.confCompare = compare,+        Aeson.confIndent = Aeson.Tab,+        Aeson.confTrailingNewline = True+      }
+ src/lib/Rattletrap/Utility/Monad.hs view
@@ -0,0 +1,4 @@+module Rattletrap.Utility.Monad where++whenMaybe :: (Applicative m) => Bool -> m a -> m (Maybe a)+whenMaybe p f = if p then fmap Just f else pure Nothing
+ src/lib/Rattletrap/Version.hs view
@@ -0,0 +1,7 @@+module Rattletrap.Version where++import qualified Data.Version as Version+import qualified Paths_rattletrap as Package++string :: String+string = Version.showVersion Package.version
+ src/test/Main.hs view
@@ -0,0 +1,181 @@+import qualified Control.Monad as Monad+import qualified Data.ByteString as ByteString+import qualified GHC.Clock as Clock+import qualified Rattletrap+import qualified System.FilePath as FilePath+import qualified Text.Printf as Printf++main :: IO ()+main = do+  let directory = "output"+  generateSchema directory+  mapM_ (testReplay directory) replays++generateSchema :: FilePath -> IO ()+generateSchema directory =+  Rattletrap.rattletrap+    ""+    ["--schema", "--output", FilePath.combine directory "schema.json"]++testReplay :: FilePath -> (FilePath, String) -> IO ()+testReplay directory (uuid, name) = do+  let inputFile = FilePath.combine "replays" $ uuid <> ".replay"+  input <- ByteString.readFile inputFile+  let mb = fromIntegral (ByteString.length input) / (1024 * 1024 :: Double)+  Printf.printf "- replay: %s %s (%.3f mb)\n" uuid name mb+  let jsonFile = FilePath.combine directory $ uuid <> ".json"+  do+    (s, ()) <- withDuration $ decode inputFile jsonFile+    Printf.printf "  decode: %.3f s @ %.3f mb/s\n" s (mb / s)+  let outputFile = FilePath.combine directory $ uuid <> ".replay"+  do+    (s, ()) <- withDuration $ encode jsonFile outputFile+    Printf.printf "  encode: %.3f s @ %.3f mb/s\n" s (mb / s)+  output <- ByteString.readFile outputFile+  Monad.when (output /= input) $ fail "output does not match input"++withDuration :: IO a -> IO (Double, a)+withDuration action = do+  before <- Clock.getMonotonicTime+  result <- action+  after <- Clock.getMonotonicTime+  pure (after - before, result)++decode :: FilePath -> FilePath -> IO ()+decode input output =+  Rattletrap.rattletrap "" ["--compact", "--input", input, "--output", output]++encode :: FilePath -> FilePath -> IO ()+encode input output =+  Rattletrap.rattletrap "" ["--input", input, "--output", output]++replays :: [(String, String)]+replays =+  [ ("0008", "a flip time"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("000b", "nintendo switch"), -- https://github.com/tfausak/rattletrap/pull/60+    ("0121", "RLCS 2"), -- https://github.com/nickbabcock/boxcars/pull/120+    ("0416", "v1.78 demolition"), -- https://github.com/tfausak/rattletrap/pull/164+    ("07e9", "a game mode before Neo Tokyo"), -- https://github.com/tfausak/rattletrap/commit/b806f9b+    ("0ad2", "some Latin-1 text"), -- https://github.com/tfausak/rattletrap/commit/13a8b2d+    ("0ae9", "fte"), -- https://github.com/jjbott/RocketLeagueReplayParser/issues/50+    ("0ca5", "with QQ remote ID"), -- https://github.com/nickbabcock/boxcars/pull/69+    ("0e76", "v1.95 rumble"), -- https://github.com/tfausak/rattletrap/pull/237+    ("1205", "rumble mode"), -- https://github.com/tfausak/rattletrap/commit/5256500+    ("160c", "a dedicated server IP"), -- https://github.com/tfausak/rattletrap/commit/5c64a6d+    ("16d5", "new property types"), -- https://github.com/tfausak/rattletrap/pull/41+    ("18d6", "an online loadout attribute"), -- https://github.com/tfausak/rattletrap/commit/a9900e7+    ("1a12", "overtime"), -- https://github.com/tfausak/rattletrap/commit/ada6053+    ("1ae4", "a game time"), -- https://github.com/tfausak/rattletrap/commit/d08176e+    ("1bc2", "no padding after the frames"), -- https://github.com/tfausak/rattletrap/commit/c9a2dd8+    ("1d1d", "a camera pitch"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("1d25", "bumblebee"), -- https://github.com/tfausak/rattletrap/issues/280+    ("1ec9", "a V1.63 match"), -- https://github.com/tfausak/rattletrap/pull/132+    ("1ef9", "a private hoops match"), -- https://github.com/tfausak/rattletrap/commit/5570839+    ("1f37", "splitscreen players"), -- https://github.com/tfausak/rattletrap/commit/c4d2f32+    ("2066", "replicated boost"), -- https://github.com/tfausak/rattletrap/issues/296+    ("2114", "a match save"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("21a8", "v1.66"), -- https://github.com/tfausak/rattletrap/pull/142+    ("2266", "dropshot"), -- https://github.com/tfausak/rattletrap/pull/36+    ("22ba", "a vote to forfeit"), -- https://github.com/tfausak/rattletrap/commit/86656bb+    ("27b6", "some UTF-16 text"), -- https://github.com/tfausak/rattletrap/commit/c4d2f32+    ("29f5", "frames"), -- https://github.com/tfausak/rattletrap/commit/bf4b6af+    ("2cf8", "demo v2.01"), -- https://github.com/tfausak/rattletrap/pull/243+    ("2cfe", "a new playstation id"), -- https://github.com/tfausak/rattletrap/issues/51+    ("3381", "patch 1.37"), -- https://github.com/tfausak/rattletrap/pull/48+    ("35f6", "heatseeker v2.01"), -- https://github.com/tfausak/rattletrap/commit/57d86c2+    ("372d", "a camera yaw attribute"), -- https://github.com/tfausak/rattletrap/commit/9c1516c+    ("383e", "older unknown content field"), -- https://github.com/tfausak/rattletrap/pull/123+    ("387f", "a frozen attribute"), -- https://github.com/tfausak/rattletrap/commit/93ce196+    ("3abd", "rlcs"), -- https://github.com/tfausak/rattletrap/pull/86+    ("3bd0", "v2.45"), -- https://github.com/tfausak/rattletrap/issues/311+    ("3ea1", "a custom team name"), -- https://github.com/tfausak/rattletrap/commit/cf4d145+    ("4050", "v2.08 dodge impulse"), -- https://github.com/tfausak/rattletrap/issues/247+    ("4126", "a game mode after Neo Tokyo"), -- https://github.com/tfausak/rattletrap/commit/a1cf21e+    ("419a", "a club match"), -- https://github.com/tfausak/rattletrap/commit/8e35043+    ("42f0", "reservations after Neo Tokyo"), -- https://github.com/tfausak/rattletrap/commit/163684f+    ("42f2", "anniversary ball"), -- https://github.com/tfausak/rattletrap/issues/147+    ("43a9", "tutorial"), -- https://github.com/nickbabcock/boxcars/pull/70+    ("4bc3", "with timed out attribute"), -- https://github.com/tfausak/rattletrap/pull/98+    ("504e", "some messages"), -- https://github.com/tfausak/rattletrap/commit/1d4a538+    ("5123", "rep stat title"), -- https://github.com/nickbabcock/boxcars/pull/78+    ("520e", "no pickup attribute"), -- https://github.com/tfausak/rattletrap/pull/38+    ("524f", "quat edge case"), -- https://github.com/tfausak/rattletrap/pull/87+    ("52aa", "a match-ending attribute"), -- https://github.com/tfausak/rattletrap/commit/5c64a6d+    ("540d", "a demolish attribute"), -- https://github.com/tfausak/rattletrap/commit/65ce033+    ("54ae", "replicated car scale"), -- https://github.com/nickbabcock/boxcars/pull/79+    ("551c", "private match settings"), -- https://github.com/tfausak/rattletrap/commit/5c9ebfc+    ("59d3", "v2.23"), -- https://github.com/tfausak/rattletrap/issues/274+    ("5a06", "esports items"), -- https://github.com/tfausak/rattletrap/pull/114+    ("5e0b", "max channels"), -- https://github.com/tfausak/rattletrap/issues/254+    ("6210", "different player history key"), -- https://github.com/tfausak/rattletrap/pull/63+    ("6320", "a forfeit attribute"), -- https://github.com/tfausak/rattletrap/pull/20+    ("6688", "a malformed byte property"), -- https://github.com/tfausak/rattletrap/commit/b1ec18b+    ("6b0d", "patch 1.37"), -- https://github.com/tfausak/rattletrap/pull/48+    ("6d1b", "a flip right"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("6f7c", "a map with numbers"), -- https://github.com/tfausak/rattletrap/commit/2629511+    ("7083", "weird basketball capitalization"), -- https://github.com/tfausak/rattletrap/pull/63+    ("7109", "a boost modifier"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("717a", "v2.43"), -- https://github.com/tfausak/rattletrap/issues/307+    ("7256", "special edition"), -- https://github.com/tfausak/rattletrap/pull/103+    ("7588", "another malformed byte property"), -- https://github.com/nickbabcock/boxcars/pull/68+    ("75ce", "primary and secondary titles"), -- https://github.com/tfausak/rattletrap/pull/69+    ("79ae", "voice update replay"), -- https://github.com/tfausak/rattletrap/pull/265+    ("7bf6", "an online loadouts attribute"), -- https://github.com/tfausak/rattletrap/commit/89d02f7+    ("81d1", "gridiron"), -- https://github.com/tfausak/rattletrap/pull/180+    ("83aa", "v2.46 replicated demolish extended"), -- https://github.com/tfausak/rattletrap/issues/316+    ("89cb", "remote user data"), -- https://github.com/tfausak/rattletrap/commit/163684f+    ("8ae5", "new painted items"), -- https://github.com/tfausak/rattletrap/pull/43+    ("92a6", "with server performance state"), -- https://github.com/tfausak/rattletrap/pull/93+    ("946f", "patch 1.43"), -- https://github.com/tfausak/rattletrap/pull/69+    ("9704", "a batarang"), -- https://github.com/tfausak/rattletrap/commit/5958e5c+    ("98e5", "a player using behind view"), -- https://github.com/tfausak/rattletrap/commit/163684f+    ("9a2c", "ghost hunt"), -- https://github.com/tfausak/rattletrap/pull/160+    ("9e35", "spike rush"), -- https://github.com/tfausak/rattletrap/pull/160+    ("9eaa", "newer replay without trailing bytes"), -- https://github.com/tfausak/rattletrap/pull/126+    ("a09e", "a tournament"), -- https://github.com/tfausak/rattletrap/pull/69+    ("a128", "a round count down"), -- https://github.com/tfausak/rattletrap/commit/8bb778d+    ("a184", "max score"), -- https://github.com/tfausak/rattletrap/pull/158+    ("a1c0", "epic system id"), -- https://github.com/tfausak/rattletrap/pull/167+    ("a52f", "some more mutators"), -- https://github.com/tfausak/rattletrap/commit/ee7afa0+    ("a558", "extended explosion data"), -- https://github.com/tfausak/rattletrap/pull/44+    ("a671", "a waiting player"), -- https://github.com/tfausak/rattletrap/commit/163684f+    ("a676", "new user color"), -- https://github.com/tfausak/rattletrap/pull/93+    ("a7f0", "a ready attribute"), -- https://github.com/tfausak/rattletrap/commit/78af1fd+    ("a9df", "salty shores patch 1.45"), -- https://github.com/tfausak/rattletrap/pull/78+    ("aa70", "patch 1.50 - TitleID attribute"), -- https://github.com/tfausak/rattletrap/pull/93+    ("ae46", "mvp"), -- https://github.com/nickbabcock/boxcars/pull/80+    ("afb1", "patch 1.37"), -- https://github.com/tfausak/rattletrap/pull/48+    ("b9f9", "a party leader"), -- https://github.com/tfausak/rattletrap/commit/bba2cfd+    ("c14f", "some mutators"), -- https://github.com/tfausak/rattletrap/commit/bba2cfd+    ("c23b", "new psynet id"), -- https://github.com/tfausak/rattletrap/pull/118+    ("c62c", "more boolean attributes"), -- https://github.com/nickbabcock/boxcars/pull/77+    ("c837", "a spectator"), -- https://github.com/tfausak/rattletrap/commit/bba2cfd+    ("cc4c", "after Starbase ARC"), -- https://github.com/tfausak/rattletrap/pull/20+    ("d044", "hoops mutators"), -- https://github.com/tfausak/rattletrap/pull/34+    ("d1d5", "v1.68"), -- https://github.com/tfausak/rattletrap/pull/146+    ("d236", "rlcs s2"), -- https://github.com/tfausak/rattletrap/pull/88+    ("d428", "a private hockey match"), -- https://github.com/tfausak/rattletrap/commit/4c104b2+    ("d44c", "ranked tournament"), -- https://github.com/tfausak/rattletrap/pull/167+    ("d52e", "psynet system id"), -- https://github.com/tfausak/rattletrap/pull/99+    ("d5d6", "health max"), -- https://github.com/nickbabcock/boxcars/pull/80+    ("d7fb", "an explosion attribute"), -- https://github.com/tfausak/rattletrap/commit/c554e3e+    ("d818", "heatseeker"), -- https://github.com/tfausak/rattletrap/pull/160+    ("db70", "new lag indicator"), -- https://github.com/tfausak/rattletrap/pull/69+    ("dcab", "weird ball attribute value"), -- https://github.com/tfausak/rattletrap/issues/149+    ("dcb3", "a pawn type attribute"), -- https://github.com/tfausak/rattletrap/commit/7d7f438+    ("dd14", "v1.88"), -- https://github.com/tfausak/rattletrap/pull/170+    ("de56", "a problematic product attribute"), -- https://github.com/tfausak/rattletrap/issues/51+    ("e2f9", "bTearOff"), -- https://github.com/nickbabcock/boxcars/pull/76+    ("e80d", "unlimited time"), -- https://github.com/tfausak/rattletrap/pull/76+    ("e978", "distracted"), -- https://github.com/tfausak/rattletrap/issues/156+    ("eae3", "an actor/object ID collision"), -- https://github.com/tfausak/rattletrap/commit/d8fad06+    ("eae8", "custom team colors"), -- https://github.com/tfausak/rattletrap/commit/809240f+    ("ecd5", "new match guid attribute"), -- https://github.com/tfausak/rattletrap/issues/270+    ("edbb", "remote role"), -- https://github.com/tfausak/rattletrap/pull/106+    ("f08e", "post game lobby"), -- https://github.com/jjbott/RocketLeagueReplayParser/issues/46+    ("f299", "a location attribute"), -- https://github.com/tfausak/rattletrap/commit/21b09c5+    ("f7b9", "a hockey game event"), -- https://github.com/tfausak/rattletrap/commit/3e16d7f+    ("f811", "no frames"), -- https://github.com/tfausak/rattletrap/commit/bf4b6af+    ("fdc7", "an MVP"), -- https://github.com/tfausak/rattletrap/commit/65019d2+    ("ffb7", "with difficulty") -- https://github.com/tfausak/rattletrap/pull/167+  ]
− stack.yaml
@@ -1,5 +0,0 @@-resolver: nightly-2017-11-25--packages:-  - .-  - tools
− tests/Main.hs
@@ -1,106 +0,0 @@-import qualified Control.Monad as Monad-import qualified Data.ByteString.Lazy as ByteString-import qualified Rattletrap-import qualified System.FilePath as FilePath-import qualified System.IO.Temp as Temp-import qualified Test.Hspec as Hspec--main :: IO ()-main = Hspec.hspec spec--spec :: Hspec.Spec-spec = Hspec.describe "Rattletrap" (mapM_ (uncurry itCanRoundTrip) replays)--itCanRoundTrip :: String -> String -> Hspec.Spec-itCanRoundTrip uuid description =-  Hspec.it (specName uuid description) (specBody uuid)--specName :: String -> String -> String-specName uuid description = unwords [take 4 uuid, description]--specBody :: String -> IO ()-specBody uuid = do-  let inputFile = pathToReplay uuid-  input <- ByteString.readFile inputFile-  Temp.withSystemTempDirectory-    "replay-"-    ( \directory -> do-      let jsonFile = FilePath.combine directory "replay.json"-      Rattletrap.mainWithArgs ["decode", inputFile, jsonFile]-      let outputFile = FilePath.combine directory "output.replay"-      Rattletrap.mainWithArgs ["encode", jsonFile, outputFile]-      output <- ByteString.readFile outputFile-      Monad.unless-        (output == input)-        (Hspec.expectationFailure "output does not match input")-    )--pathToReplay :: String -> FilePath-pathToReplay uuid =-  FilePath.joinPath ["replays", FilePath.addExtension uuid ".replay"]--replays :: [(String, String)]-replays =-  [ ("00080014003600090000036E0F65CCEB", "a flip time")-  , ("07E925B1423653D44CB8B4B2524792C1", "a game mode before Neo Tokyo")-  , ("0AD21FEE4A512906BEB6C98136AAF49A", "some Latin-1 text")-  , ("1205D96C4D819800927791820096CD49", "rumble mode")-  , ("160CA83E41083BFD8E6315B4BFCA0561", "a dedicated server IP")-  , ("16D580EF479483E015207C901776F9FB", "new property types")-  , ("18D6738D415B70B5BE4C299588D3C141", "an online loadout attribute")-  , ("1A126AC24CAA0DB0E98835BD960B8AF8", "overtime")-  , ("1AE415514DFC65DCBF8B8391AD35488D", "a game time")-  , ("1BC2D01444ACE577D01E988EADD4DFD0", "no padding after the frames")-  , ("1D1DE97D4941C86E43FE0093563DB621", "a camera pitch")-  , ("1EF90FCC4F719F606A5327B3CDD782A4", "a private hoops match")-  , ("1F3798E540B0C37A946561ABBB3037F9", "splitscreen players")-  , ("211466D04B983F5A33CC2FA1D5928672", "a match save")-  , ("22660E3649FC7971E5653692473D4318", "dropshot")-  , ("22BACD794ABE7B92E50E9CBDBD9C59CE", "a vote to forfeit")-  , ("27B6A7B64553F0F685874584F96BAB1B", "some UTF-16 text")-  , ("29F582C34A65EB34D358A784CBE3C189", "frames")-  , ("2CFE577044E651D3FA9DBF83ECA8BCC3", "a new playstation id")-  , ("338173964F9F71EBDD31058A1936CBB4", "patch 1.37")-  , ("372DBFCA4BDB340E4357B6BD43032802", "a camera yaw attribute")-  , ("387F059C47C09E253C875CA990EFD9F2", "a frozen attribute")-  , ("3EA147DD485B8DD24810689A7A989E44", "a custom team name")-  , ("4126861E477F4A03DE2A4080374D7908", "a game mode after Neo Tokyo")-  , ("42F0D8DA4FC89AE7B80FCAB7F637A8EA", "reservations after Neo Tokyo")-  , ("504ED825482186E771FAA9B642CE5CE4", "some messages")-  , ("520E1BFF468CF6C3C48D1EA85D9C7909", "no pickup attribute")-  , ("52AA67F94090C19D33C5009E54D31FE4", "a match-ending attribute")-  , ("540DA764423C8FB24EB9D486D982F16F", "a demolish attribute")-  , ("551CA4D44FF2B86015DE44A6B5790D4C", "private match settings")-  , ("6320E51C49066A7C210A2993C2201D5F", "a forfeit attribute")-  , ("6688EEE34BFEB3EC3A9E3283098CC712", "a malformed byte property")-  , ("6B0D1614417085A7AAD82EAA30D8DABE", "patch 1.37")-  , ("6D1B06D844A5BB91B81FD4B5B28F08BA", "a flip right")-  , ("6F7CFCD24638F8A6567AB3A8B9958A90", "a map with numbers")-  , ("7109EB9846D303E54B7ACBA792036213", "a boost modifier")-  , ("7BF6073F4614CE0A438994B9A260DA6A", "an online loadouts attribute")-  , ("89CBA30E46FA5385BDD35DA4285D4D2E", "remote user data")-  , ("8AE551FF406D7B82ED853B8C7BFF8CDA", "new painted items")-  , ("9704208245D7DD851F2FB2BC7DFD9AC3", "a batarang")-  , ("98E58A904D713F2DE202358E8573265D", "a player using behind view")-  , ("A128B3AB45D5A18E3EF9CF93C9576BCE", "a round count down")-  , ("A52F804845573D8DA65E97BF59026A43", "some more mutators")-  , ("A558B1B44124D6E021640884E8EEC2A7", "extended explosion data")-  , ("A6711CE74272B2E663DCC9A200A218E3", "a waiting player")-  , ("A7F001A1417A19BFA8C90990D8F7C2FF", "a ready attribute")-  , ("AFB1F46A49737E36928E1EABC6F5B7AD", "patch 1.37")-  , ("B9F9B87D4A9D0A3D25D4EC91C0401DE2", "a party leader")-  , ("C14F7E0E4D9B5E6BE9AD5D8ED56B174C", "some mutators")-  , ("C8372B1345B1803DEF039F815DBD802D", "a spectator")-  , ("CC4CA70D4F7A67EBAD0ED9B9923106F7", "after Starbase ARC")-  , ("D0449F5F4AA775B86FFA7DA2B5A3204E", "hoops mutators")-  , ("D428F81646A98C25902CE988AE5C14C8", "a private hockey match")-  , ("D7FB197A451D69075A0C99A2F49A4053", "an explosion attribute")-  , ("DCB3A6B94A9DBE46FDE5EAA9B012F6C8", "a pawn type attribute")-  , ("DE56B1B146687EA2E62E31A37A08C8EB", "a problematic product attribute")-  , ("EAE311E84BA35B590A6FDBA6DD4F2FEB", "an actor/object ID collision")-  , ("EAE8DADA4BB2DC5422792C9B4A67392D", "custom team colors")-  , ("F299F176491554B11E34AB91CA76B2CE", "a location attribute")-  , ("F7B9E14545C7467B89A00895980FCD73", "a hockey game event")-  , ("F811C1D24888015E23B598AD8628C742", "no frames")-  , ("FDC79DA84DD463D4BCCE6B892829AC88", "an MVP")-  ]