packages feed

rattletrap (empty) → 0.1.0

raw patch · 75 files changed

+3565/−0 lines, 75 filesdep +aesondep +aeson-casingdep +basesetup-changed

Dependencies added: aeson, aeson-casing, base, bimap, binary, binary-bits, bytestring, containers, data-binary-ieee754, filepath, hlint, rattletrap, regex-compat, tasty, tasty-hspec, template-haskell, text, vector

Files

+ HLint.hs view
@@ -0,0 +1,10 @@+module HLint+  (+  ) where++import "hint" HLint.Default+import "hint" HLint.Dollar++ignore "Use &&&"++ignore "Use <|>"
+ LICENSE.markdown view
@@ -0,0 +1,23 @@+# [The MIT License (MIT)][]++Copyright (c) 2016 Taylor Fausak <taylor@fausak.me>++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
+ README.markdown view
@@ -0,0 +1,66 @@+# [Rattletrap][]++[![Windows build badge][]][windows build]+[![Build badge][]][build]++Rattletrap parses and generates [Rocket League][] replays.++## Install++Rattletrap does not yet offer compiled binaries for download. To install it,+first install [Stack][]. Then download or clone this repository and run+`stack --install-ghc install`.++## Parse++Rattletrap can parse (decode) Rocket League replays and output them as JSON.++``` sh+> rattletrap decode input.replay output.json+# or+> rattletrap decode input.replay > output.json+# or+> rattletrap.decode < input.replay > output.json+```++The resulting JSON is minified, but extremely large. A 4.3 MB replay file turns+into a 123 MB JSON file. To easily view the file, use a JSON pretty printer and+a pager. For example:++``` sh+> cat output.json | python -m json.tool | less+```++## Generate++Rattletrap can also generate (encode) Rocket League replays from JSON files.++``` sh+> rattletrap encode input.json output.replay+# or+> rattletrap encode input.json > output.replay+# or+> rattletrap.encode < input.json > output.replay+```++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+[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+[Stack]: https://docs.haskellstack.org/en/stable/README/
+ Setup.hs view
@@ -0,0 +1,8 @@+module Main+  ( main+  ) where++import qualified Distribution.Simple++main :: IO ()+main = Distribution.Simple.defaultMain
+ executable/Main.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Main+  ( main+  ) where++import Rattletrap++import qualified Control.Monad as Monad+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Casing as Casing+import qualified Data.Aeson.TH 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 Language.Haskell.TH as TH+import qualified System.Environment as Environment++main :: IO ()+main = do+  args <- Environment.getArgs+  mainWithArgs args++mainWithArgs :: [String] -> IO ()+mainWithArgs args =+  case args of+    "decode":files -> do+      (getInput, putOutput) <- getIO files+      input <- getInput+      let replay = Binary.runGet getReplay input+      let output = Aeson.encode replay+      putOutput output+    "encode":files -> do+      (getInput, putOutput) <- getIO files+      input <- getInput+      case Aeson.eitherDecode input of+        Left message -> fail ("could not parse JSON: " ++ message)+        Right replay -> do+          let output = Binary.runPut (putReplay replay)+          putOutput output+    _ -> fail ("unexpected arguments " ++ show args)++getIO+  :: Monad m+  => [FilePath] -> m (IO ByteString.ByteString, ByteString.ByteString -> IO ())+getIO files =+  case files of+    [] -> pure (ByteString.getContents, ByteString.putStr)+    [i] -> pure (ByteString.readFile i, ByteString.putStr)+    [i, o] -> pure (ByteString.readFile i, ByteString.writeFile o)+    _ -> fail ("unexpected arguments " ++ show files)++$(Monad.foldM+    (\declarations name -> do+       let options =+             Casing.aesonDrop (length (TH.nameBase name)) Casing.snakeCase+       newDeclarations <- Aeson.deriveJSON options name+       pure (newDeclarations ++ declarations))+    []+    [ ''Attribute+    , ''AttributeMapping+    , ''AttributeValue+    , ''BooleanAttributeValue+    , ''ByteAttributeValue+    , ''Cache+    , ''CamSettingsAttributeValue+    , ''ClassMapping+    , ''CompressedWord+    , ''CompressedWordVector+    , ''Content+    , ''DemolishAttributeValue+    , ''DestroyedReplicationValue+    , ''Dictionary+    , ''EnumAttributeValue+    , ''ExplosionAttributeValue+    , ''FlaggedIntAttributeValue+    , ''Float32+    , ''FloatAttributeValue+    , ''Frame+    , ''GameModeAttributeValue+    , ''Header+    , ''Initialization+    , ''Int32+    , ''Int8+    , ''Int8Vector+    , ''IntAttributeValue+    , ''KeyFrame+    , ''List+    , ''LoadoutAttributeValue+    , ''LoadoutOnlineAttributeValue+    , ''LoadoutsAttributeValue+    , ''LoadoutsOnlineAttributeValue+    , ''LocationAttributeValue+    , ''Mark+    , ''Message+    , ''MusicStingerAttributeValue+    , ''PartyLeaderAttributeValue+    , ''PickupAttributeValue+    , ''PrivateMatchSettingsAttributeValue+    , ''Property+    , ''PropertyValue+    , ''QWordAttributeValue+    , ''RemoteId+    , ''Replay+    , ''Replication+    , ''ReplicationValue+    , ''ReservationAttributeValue+    , ''RigidBodyStateAttributeValue+    , ''SpawnedReplicationValue+    , ''StringAttributeValue+    , ''TeamPaintAttributeValue+    , ''Text+    , ''UniqueIdAttributeValue+    , ''UpdatedReplicationValue+    , ''Vector+    , ''WeldedInfoAttributeValue+    , ''Word32+    , ''Word64+    , ''Word8+    ])
+ library/Rattletrap.hs view
@@ -0,0 +1,41 @@+module Rattletrap+  ( module Export+  ) where++import Rattletrap.ActorMap as Export+import Rattletrap.Attribute as Export+import Rattletrap.AttributeMapping as Export+import Rattletrap.AttributeValue as Export+import Rattletrap.AttributeValueType as Export+import Rattletrap.Cache as Export+import Rattletrap.ClassAttributeMap as Export+import Rattletrap.ClassMapping as Export+import Rattletrap.CompressedWord as Export+import Rattletrap.CompressedWordVector as Export+import Rattletrap.Content as Export+import Rattletrap.Crc as Export+import Rattletrap.Data as Export+import Rattletrap.Dictionary as Export+import Rattletrap.Float32 as Export+import Rattletrap.Frame as Export+import Rattletrap.Header as Export+import Rattletrap.Initialization as Export+import Rattletrap.Int32 as Export+import Rattletrap.Int8 as Export+import Rattletrap.Int8Vector as Export+import Rattletrap.KeyFrame as Export+import Rattletrap.List as Export+import Rattletrap.Mark as Export+import Rattletrap.Message as Export+import Rattletrap.Property as Export+import Rattletrap.PropertyValue as Export+import Rattletrap.RemoteId as Export+import Rattletrap.Replay as Export+import Rattletrap.Replication as Export+import Rattletrap.ReplicationValue as Export+import Rattletrap.Text as Export+import Rattletrap.Utility as Export+import Rattletrap.Vector as Export+import Rattletrap.Word32 as Export+import Rattletrap.Word64 as Export+import Rattletrap.Word8 as Export
+ library/Rattletrap/ActorMap.hs view
@@ -0,0 +1,14 @@+module Rattletrap.ActorMap where++import Rattletrap.CompressedWord+import Rattletrap.Word32++import qualified Data.Map as Map++type ActorMap = Map.Map CompressedWord Word32++makeActorMap :: ActorMap+makeActorMap = Map.empty++updateActorMap :: CompressedWord -> Word32 -> ActorMap -> ActorMap+updateActorMap = Map.insert
+ library/Rattletrap/Attribute.hs view
@@ -0,0 +1,57 @@+module Rattletrap.Attribute where++import Rattletrap.ActorMap+import Rattletrap.AttributeValue+import Rattletrap.ClassAttributeMap+import Rattletrap.CompressedWord++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data Attribute = Attribute+  { attributeId :: CompressedWord+  , attributeValue :: AttributeValue+  } deriving (Eq, Ord, Show)++getAttributes+  :: (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)+  -> ClassAttributeMap+  -> ActorMap+  -> CompressedWord+  -> BinaryBit.BitGet Attribute+getAttribute version classAttributeMap actorMap actorId =+  case getAttributeIdLimit classAttributeMap actorMap actorId of+    Nothing -> fail ("could not get attribute ID limit for " ++ show actorId)+    Just limit -> do+      id_ <- getCompressedWord limit+      case getAttributeName classAttributeMap actorMap actorId id_ of+        Nothing -> fail ("could not get attribute name for " ++ show id_)+        Just name -> do+          value <- getAttributeValue version name+          pure Attribute {attributeId = id_, attributeValue = value}++putAttribute :: Attribute -> BinaryBit.BitPut ()+putAttribute attribute = do+  BinaryBit.putBool True+  putCompressedWord (attributeId attribute)+  putAttributeValue (attributeValue attribute)
+ library/Rattletrap/AttributeMapping.hs view
@@ -0,0 +1,23 @@+module Rattletrap.AttributeMapping where++import Rattletrap.Word32++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+    {attributeMappingObjectId = objectId, attributeMappingStreamId = streamId}++putAttributeMapping :: AttributeMapping -> Binary.Put+putAttributeMapping attributeMapping = do+  putWord32 (attributeMappingObjectId attributeMapping)+  putWord32 (attributeMappingStreamId attributeMapping)
+ library/Rattletrap/AttributeValue.hs view
@@ -0,0 +1,187 @@+module Rattletrap.AttributeValue+  ( module Rattletrap.AttributeValue+  , module Export+  ) where++import Rattletrap.AttributeValue.Boolean as Export+import Rattletrap.AttributeValue.Byte as Export+import Rattletrap.AttributeValue.CamSettings as Export+import Rattletrap.AttributeValue.Demolish as Export+import Rattletrap.AttributeValue.Enum as Export+import Rattletrap.AttributeValue.Explosion as Export+import Rattletrap.AttributeValue.FlaggedInt as Export+import Rattletrap.AttributeValue.Float as Export+import Rattletrap.AttributeValue.GameMode as Export+import Rattletrap.AttributeValue.Int as Export+import Rattletrap.AttributeValue.Loadout as Export+import Rattletrap.AttributeValue.LoadoutOnline as Export+import Rattletrap.AttributeValue.Loadouts as Export+import Rattletrap.AttributeValue.LoadoutsOnline as Export+import Rattletrap.AttributeValue.Location as Export+import Rattletrap.AttributeValue.MusicStinger as Export+import Rattletrap.AttributeValue.PartyLeader as Export+import Rattletrap.AttributeValue.Pickup as Export+import Rattletrap.AttributeValue.PrivateMatchSettings as Export+import Rattletrap.AttributeValue.QWord as Export+import Rattletrap.AttributeValue.Reservation as Export+import Rattletrap.AttributeValue.RigidBodyState as Export+import Rattletrap.AttributeValue.String as Export+import Rattletrap.AttributeValue.TeamPaint as Export+import Rattletrap.AttributeValue.UniqueId as Export+import Rattletrap.AttributeValue.WeldedInfo as Export++import Rattletrap.AttributeValueType+import Rattletrap.Data+import Rattletrap.Text++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Map as Map++data AttributeValue+  = BooleanAttribute BooleanAttributeValue+  | ByteAttribute ByteAttributeValue+  | CamSettingsAttribute CamSettingsAttributeValue+  | DemolishAttribute DemolishAttributeValue+  | EnumAttribute EnumAttributeValue+  | ExplosionAttribute ExplosionAttributeValue+  | FlaggedIntAttribute FlaggedIntAttributeValue+  | FloatAttribute FloatAttributeValue+  | GameModeAttribute GameModeAttributeValue+  | IntAttribute IntAttributeValue+  | LoadoutAttribute LoadoutAttributeValue+  | LoadoutOnlineAttribute LoadoutOnlineAttributeValue+  | LoadoutsAttribute LoadoutsAttributeValue+  | LoadoutsOnlineAttribute LoadoutsOnlineAttributeValue+  | LocationAttribute LocationAttributeValue+  | MusicStingerAttribute MusicStingerAttributeValue+  | PartyLeaderAttribute PartyLeaderAttributeValue+  | PickupAttribute PickupAttributeValue+  | PrivateMatchSettingsAttribute PrivateMatchSettingsAttributeValue+  | QWordAttribute QWordAttributeValue+  | ReservationAttribute ReservationAttributeValue+  | RigidBodyStateAttribute RigidBodyStateAttributeValue+  | StringAttribute StringAttributeValue+  | TeamPaintAttribute TeamPaintAttributeValue+  | UniqueIdAttribute UniqueIdAttributeValue+  | WeldedInfoAttribute WeldedInfoAttributeValue+  deriving (Eq, Ord, Show)++getAttributeValue :: (Int, Int) -> Text -> BinaryBit.BitGet AttributeValue+getAttributeValue version name =+  case Map.lookup name attributeValueTypes of+    Just constructor ->+      case constructor of+        AVBoolean -> do+          x <- getBooleanAttributeValue+          pure (BooleanAttribute x)+        AVByte -> do+          x <- getByteAttributeValue+          pure (ByteAttribute x)+        AVCamSettings -> do+          x <- getCamSettingsAttributeValue+          pure (CamSettingsAttribute x)+        AVDemolish -> do+          x <- getDemolishAttributeValue+          pure (DemolishAttribute x)+        AVEnum -> do+          x <- getEnumAttributeValue+          pure (EnumAttribute x)+        AVExplosion -> do+          x <- getExplosionAttributeValue+          pure (ExplosionAttribute x)+        AVFlaggedInt -> do+          x <- getFlaggedIntAttributeValue+          pure (FlaggedIntAttribute x)+        AVFloat -> do+          x <- getFloatAttributeValue+          pure (FloatAttribute x)+        AVGameMode -> do+          x <- getGameModeAttributeValue version+          pure (GameModeAttribute x)+        AVInt -> do+          x <- getIntAttributeValue+          pure (IntAttribute x)+        AVLoadout -> do+          x <- getLoadoutAttributeValue+          pure (LoadoutAttribute x)+        AVLoadoutOnline -> do+          x <- getLoadoutOnlineAttributeValue+          pure (LoadoutOnlineAttribute x)+        AVLoadouts -> do+          x <- getLoadoutsAttributeValue+          pure (LoadoutsAttribute x)+        AVLoadoutsOnline -> do+          x <- getLoadoutsOnlineAttributeValue+          pure (LoadoutsOnlineAttribute x)+        AVLocation -> do+          x <- getLocationAttributeValue+          pure (LocationAttribute x)+        AVMusicStinger -> do+          x <- getMusicStingerAttributeValue+          pure (MusicStingerAttribute x)+        AVPartyLeader -> do+          x <- getPartyLeaderAttributeValue+          pure (PartyLeaderAttribute x)+        AVPickup -> do+          x <- getPickupAttributeValue+          pure (PickupAttribute x)+        AVPrivateMatchSettings -> do+          x <- getPrivateMatchSettingsAttributeValue+          pure (PrivateMatchSettingsAttribute x)+        AVQWord -> do+          x <- getQWordAttributeValue+          pure (QWordAttribute x)+        AVReservation -> do+          x <- getReservationAttributeValue version+          pure (ReservationAttribute x)+        AVRigidBodyState -> do+          x <- getRigidBodyStateAttributeValue+          pure (RigidBodyStateAttribute x)+        AVString -> do+          x <- getStringAttributeValue+          pure (StringAttribute x)+        AVTeamPaint -> do+          x <- getTeamPaintAttributeValue+          pure (TeamPaintAttribute x)+        AVUniqueId -> do+          x <- getUniqueIdAttributeValue+          pure (UniqueIdAttribute x)+        AVWeldedInfo -> do+          x <- getWeldedInfoAttributeValue+          pure (WeldedInfoAttribute x)+    Nothing -> fail ("don't know how to get attribute value " ++ show name)++attributeValueTypes :: Map.Map Text AttributeValueType+attributeValueTypes =+  Map.mapKeys stringToText (Map.fromList rawAttributeValueTypes)++putAttributeValue :: AttributeValue -> BinaryBit.BitPut ()+putAttributeValue value =+  case value of+    BooleanAttribute x -> putBooleanAttributeValue x+    ByteAttribute x -> putByteAttributeValue x+    CamSettingsAttribute x -> putCamSettingsAttributeValue x+    DemolishAttribute x -> putDemolishAttributeValue x+    EnumAttribute x -> putEnumAttributeValue x+    ExplosionAttribute x -> putExplosionAttributeValue x+    FlaggedIntAttribute x -> putFlaggedIntAttributeValue x+    FloatAttribute x -> putFloatAttributeValue x+    GameModeAttribute x -> putGameModeAttributeValue x+    IntAttribute x -> putIntAttributeValue x+    LoadoutAttribute x -> putLoadoutAttributeValue x+    LoadoutOnlineAttribute x -> putLoadoutOnlineAttributeValue x+    LoadoutsAttribute x -> putLoadoutsAttributeValue x+    LoadoutsOnlineAttribute x -> putLoadoutsOnlineAttributeValue x+    LocationAttribute x -> putLocationAttributeValue x+    MusicStingerAttribute x -> putMusicStingerAttributeValue x+    PartyLeaderAttribute x -> putPartyLeaderAttributeValue x+    PickupAttribute x -> putPickupAttributeValue x+    PrivateMatchSettingsAttribute x -> putPrivateMatchSettingsAttributeValue x+    QWordAttribute x -> putQWordAttributeValue x+    ReservationAttribute x -> putReservationAttributeValue x+    RigidBodyStateAttribute x -> putRigidBodyStateAttributeValue x+    StringAttribute x -> putStringAttributeValue x+    TeamPaintAttribute x -> putTeamPaintAttributeValue x+    UniqueIdAttribute x -> putUniqueIdAttributeValue x+    WeldedInfoAttribute x -> putWeldedInfoAttributeValue x
+ library/Rattletrap/AttributeValue/Boolean.hs view
@@ -0,0 +1,17 @@+module Rattletrap.AttributeValue.Boolean where++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype BooleanAttributeValue = BooleanAttributeValue+  { booleanAttributeValueValue :: Bool+  } deriving (Eq, Ord, Show)++getBooleanAttributeValue :: BinaryBit.BitGet BooleanAttributeValue+getBooleanAttributeValue = do+  value <- BinaryBit.getBool+  pure (BooleanAttributeValue value)++putBooleanAttributeValue :: BooleanAttributeValue -> BinaryBit.BitPut ()+putBooleanAttributeValue booleanAttributeValue =+  BinaryBit.putBool (booleanAttributeValueValue booleanAttributeValue)
+ library/Rattletrap/AttributeValue/Byte.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.Byte where++import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype ByteAttributeValue = ByteAttributeValue+  { byteAttributeValueValue :: Word8+  } deriving (Eq, Ord, Show)++getByteAttributeValue :: BinaryBit.BitGet ByteAttributeValue+getByteAttributeValue = do+  value <- getWord8Bits+  pure (ByteAttributeValue value)++putByteAttributeValue :: ByteAttributeValue -> BinaryBit.BitPut ()+putByteAttributeValue byteAttributeValue =+  putWord8Bits (byteAttributeValueValue byteAttributeValue)
+ library/Rattletrap/AttributeValue/CamSettings.hs view
@@ -0,0 +1,36 @@+module Rattletrap.AttributeValue.CamSettings where++import Rattletrap.Float32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data CamSettingsAttributeValue = CamSettingsAttributeValue+  { camSettingsAttributeValueFov :: Float32+  , camSettingsAttributeValueHeight :: Float32+  , camSettingsAttributeValueAngle :: Float32+  , camSettingsAttributeValueDistance :: Float32+  , camSettingsAttributeValueStiffness :: Float32+  , camSettingsAttributeValueSwivelSpeed :: Float32+  } deriving (Eq, Ord, Show)++getCamSettingsAttributeValue :: BinaryBit.BitGet CamSettingsAttributeValue+getCamSettingsAttributeValue = do+  fov <- getFloat32Bits+  height <- getFloat32Bits+  angle <- getFloat32Bits+  distance <- getFloat32Bits+  stiffness <- getFloat32Bits+  swivelSpeed <- getFloat32Bits+  pure+    (CamSettingsAttributeValue fov height angle distance stiffness swivelSpeed)++putCamSettingsAttributeValue :: CamSettingsAttributeValue -> BinaryBit.BitPut ()+putCamSettingsAttributeValue camSettingsAttributeValue = do+  putFloat32Bits (camSettingsAttributeValueFov camSettingsAttributeValue)+  putFloat32Bits (camSettingsAttributeValueHeight camSettingsAttributeValue)+  putFloat32Bits (camSettingsAttributeValueAngle camSettingsAttributeValue)+  putFloat32Bits (camSettingsAttributeValueDistance camSettingsAttributeValue)+  putFloat32Bits (camSettingsAttributeValueStiffness camSettingsAttributeValue)+  putFloat32Bits+    (camSettingsAttributeValueSwivelSpeed camSettingsAttributeValue)
+ library/Rattletrap/AttributeValue/Demolish.hs view
@@ -0,0 +1,42 @@+module Rattletrap.AttributeValue.Demolish where++import Rattletrap.Vector+import Rattletrap.Word32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data DemolishAttributeValue = DemolishAttributeValue+  { demolishAttributeValueAttackerFlag :: Bool+  , demolishAttributeValueAttackerActorId :: Word32+  , demolishAttributeValueVictimFlag :: Bool+  , demolishAttributeValueVictimActorId :: Word32+  , demolishAttributeValueAttackerVelocity :: Vector+  , demolishAttributeValueVictimVelocity :: Vector+  } deriving (Eq, Ord, Show)++getDemolishAttributeValue :: BinaryBit.BitGet DemolishAttributeValue+getDemolishAttributeValue = do+  attackerFlag <- BinaryBit.getBool+  attackerActorId <- getWord32Bits+  victimFlag <- BinaryBit.getBool+  victimActorId <- getWord32Bits+  attackerVelocity <- getVector+  victimVelocity <- getVector+  pure+    (DemolishAttributeValue+       attackerFlag+       attackerActorId+       victimFlag+       victimActorId+       attackerVelocity+       victimVelocity)++putDemolishAttributeValue :: DemolishAttributeValue -> BinaryBit.BitPut ()+putDemolishAttributeValue demolishAttributeValue = do+  BinaryBit.putBool (demolishAttributeValueAttackerFlag demolishAttributeValue)+  putWord32Bits (demolishAttributeValueAttackerActorId demolishAttributeValue)+  BinaryBit.putBool (demolishAttributeValueVictimFlag demolishAttributeValue)+  putWord32Bits (demolishAttributeValueVictimActorId demolishAttributeValue)+  putVector (demolishAttributeValueAttackerVelocity demolishAttributeValue)+  putVector (demolishAttributeValueVictimVelocity demolishAttributeValue)
+ library/Rattletrap/AttributeValue/Enum.hs view
@@ -0,0 +1,18 @@+module Rattletrap.AttributeValue.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 EnumAttributeValue = EnumAttributeValue+  { enumAttributeValueValue :: Word.Word16+  } deriving (Eq, Ord, Show)++getEnumAttributeValue :: BinaryBit.BitGet EnumAttributeValue+getEnumAttributeValue = do+  value <- BinaryBit.getWord16be 11+  pure (EnumAttributeValue value)++putEnumAttributeValue :: EnumAttributeValue -> BinaryBit.BitPut ()+putEnumAttributeValue enumAttributeValue =+  BinaryBit.putWord16be 11 (enumAttributeValueValue enumAttributeValue)
+ library/Rattletrap/AttributeValue/Explosion.hs view
@@ -0,0 +1,25 @@+module Rattletrap.AttributeValue.Explosion where++import Rattletrap.Int32+import Rattletrap.Vector++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data ExplosionAttributeValue = ExplosionAttributeValue+  { explosionAttributeValueActorId :: Int32+  , explosionAttributeValueLocation :: Vector+  } deriving (Eq, Ord, Show)++getExplosionAttributeValue :: BinaryBit.BitGet ExplosionAttributeValue+getExplosionAttributeValue = do+  False <- BinaryBit.getBool+  actorId <- getInt32Bits+  location <- getVector+  pure (ExplosionAttributeValue actorId location)++putExplosionAttributeValue :: ExplosionAttributeValue -> BinaryBit.BitPut ()+putExplosionAttributeValue explosionAttributeValue = do+  BinaryBit.putBool False+  putInt32Bits (explosionAttributeValueActorId explosionAttributeValue)+  putVector (explosionAttributeValueLocation explosionAttributeValue)
+ library/Rattletrap/AttributeValue/FlaggedInt.hs view
@@ -0,0 +1,22 @@+module Rattletrap.AttributeValue.FlaggedInt where++import Rattletrap.Int32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data FlaggedIntAttributeValue = FlaggedIntAttributeValue+  { flaggedIntAttributeValueFlag :: Bool+  , flaggedIntAttributeValueInt :: Int32+  } deriving (Eq, Ord, Show)++getFlaggedIntAttributeValue :: BinaryBit.BitGet FlaggedIntAttributeValue+getFlaggedIntAttributeValue = do+  flag <- BinaryBit.getBool+  int <- getInt32Bits+  pure (FlaggedIntAttributeValue flag int)++putFlaggedIntAttributeValue :: FlaggedIntAttributeValue -> BinaryBit.BitPut ()+putFlaggedIntAttributeValue flaggedIntAttributeValue = do+  BinaryBit.putBool (flaggedIntAttributeValueFlag flaggedIntAttributeValue)+  putInt32Bits (flaggedIntAttributeValueInt flaggedIntAttributeValue)
+ library/Rattletrap/AttributeValue/Float.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.Float where++import Rattletrap.Float32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype FloatAttributeValue = FloatAttributeValue+  { floatAttributeValueValue :: Float32+  } deriving (Eq, Ord, Show)++getFloatAttributeValue :: BinaryBit.BitGet FloatAttributeValue+getFloatAttributeValue = do+  value <- getFloat32Bits+  pure (FloatAttributeValue value)++putFloatAttributeValue :: FloatAttributeValue -> BinaryBit.BitPut ()+putFloatAttributeValue floatAttributeValue =+  putFloat32Bits (floatAttributeValueValue floatAttributeValue)
+ library/Rattletrap/AttributeValue/GameMode.hs view
@@ -0,0 +1,26 @@+module Rattletrap.AttributeValue.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 GameModeAttributeValue = GameModeAttributeValue+  { gameModeAttributeValueNumBits :: Int+  , gameModeAttributeValueWord :: Word.Word8+  } deriving (Eq, Ord, Show)++getGameModeAttributeValue :: (Int, Int)+                          -> BinaryBit.BitGet GameModeAttributeValue+getGameModeAttributeValue version = do+  let numBits =+        if version < (868, 12)+          then 2+          else 8+  word <- BinaryBit.getWord8 numBits+  pure (GameModeAttributeValue numBits word)++putGameModeAttributeValue :: GameModeAttributeValue -> BinaryBit.BitPut ()+putGameModeAttributeValue gameModeAttributeValue = do+  let numBits = gameModeAttributeValueNumBits gameModeAttributeValue+  let word = gameModeAttributeValueWord gameModeAttributeValue+  BinaryBit.putWord8 numBits word
+ library/Rattletrap/AttributeValue/Int.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.Int where++import Rattletrap.Int32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype IntAttributeValue = IntAttributeValue+  { intAttributeValueValue :: Int32+  } deriving (Eq, Ord, Show)++getIntAttributeValue :: BinaryBit.BitGet IntAttributeValue+getIntAttributeValue = do+  value <- getInt32Bits+  pure (IntAttributeValue value)++putIntAttributeValue :: IntAttributeValue -> BinaryBit.BitPut ()+putIntAttributeValue intAttributeValue =+  putInt32Bits (intAttributeValueValue intAttributeValue)
+ library/Rattletrap/AttributeValue/Loadout.hs view
@@ -0,0 +1,61 @@+module Rattletrap.AttributeValue.Loadout where++import Rattletrap.Word32+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data LoadoutAttributeValue = LoadoutAttributeValue+  { loadoutAttributeValueVersion :: Word8+  , loadoutAttributeValueBody :: Word32+  , loadoutAttributeValueDecal :: Word32+  , loadoutAttributeValueWheels :: Word32+  , loadoutAttributeValueRocketTrail :: Word32+  , loadoutAttributeValueAntenna :: Word32+  , loadoutAttributeValueTopper :: Word32+  , loadoutAttributeValueUnknown1 :: Word32+  , loadoutAttributeValueUnknown2 :: Maybe Word32+  } deriving (Eq, Ord, Show)++getLoadoutAttributeValue :: BinaryBit.BitGet LoadoutAttributeValue+getLoadoutAttributeValue = do+  version <- getWord8Bits+  body <- getWord32Bits+  decal <- getWord32Bits+  wheels <- getWord32Bits+  rocketTrail <- getWord32Bits+  antenna <- getWord32Bits+  topper <- getWord32Bits+  g <- getWord32Bits+  h <-+    if version > Word8 10+      then do+        h <- getWord32Bits+        pure (Just h)+      else pure Nothing+  pure+    (LoadoutAttributeValue+       version+       body+       decal+       wheels+       rocketTrail+       antenna+       topper+       g+       h)++putLoadoutAttributeValue :: LoadoutAttributeValue -> BinaryBit.BitPut ()+putLoadoutAttributeValue loadoutAttributeValue = do+  putWord8Bits (loadoutAttributeValueVersion loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueBody loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueDecal loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueWheels loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueRocketTrail loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueAntenna loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueTopper loadoutAttributeValue)+  putWord32Bits (loadoutAttributeValueUnknown1 loadoutAttributeValue)+  case loadoutAttributeValueUnknown2 loadoutAttributeValue of+    Nothing -> pure ()+    Just x -> putWord32Bits x
+ library/Rattletrap/AttributeValue/LoadoutOnline.hs view
@@ -0,0 +1,42 @@+module Rattletrap.AttributeValue.LoadoutOnline where++import Rattletrap.CompressedWord+import Rattletrap.Word32+import Rattletrap.Word8++import qualified Control.Monad as Monad+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype LoadoutOnlineAttributeValue = LoadoutOnlineAttributeValue+  { loadoutAttributeValueValue :: [[(Word32, CompressedWord)]]+  } deriving (Eq, Ord, Show)++getLoadoutOnlineAttributeValue :: BinaryBit.BitGet LoadoutOnlineAttributeValue+getLoadoutOnlineAttributeValue = do+  size <- getWord8Bits+  values <-+    Monad.replicateM+      (fromIntegral (word8Value size))+      (do innerSize <- getWord8Bits+          Monad.replicateM+            (fromIntegral (word8Value innerSize))+            (do x <- getWord32Bits+                y <- getCompressedWord 27+                pure (x, y)))+  pure (LoadoutOnlineAttributeValue values)++putLoadoutOnlineAttributeValue :: LoadoutOnlineAttributeValue+                               -> BinaryBit.BitPut ()+putLoadoutOnlineAttributeValue loadoutAttributeValue = do+  let values = loadoutAttributeValueValue loadoutAttributeValue+  putWord8Bits (Word8 (fromIntegral (length values)))+  mapM_+    (\xs -> do+       putWord8Bits (Word8 (fromIntegral (length xs)))+       mapM_+         (\(x, y) -> do+            putWord32Bits x+            putCompressedWord y)+         xs)+    values
+ library/Rattletrap/AttributeValue/Loadouts.hs view
@@ -0,0 +1,22 @@+module Rattletrap.AttributeValue.Loadouts where++import Rattletrap.AttributeValue.Loadout++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data LoadoutsAttributeValue = LoadoutsAttributeValue+  { loadoutsAttributeValueBlue :: LoadoutAttributeValue+  , loadoutsAttributeValueOrange :: LoadoutAttributeValue+  } deriving (Eq, Ord, Show)++getLoadoutsAttributeValue :: BinaryBit.BitGet LoadoutsAttributeValue+getLoadoutsAttributeValue = do+  blue <- getLoadoutAttributeValue+  orange <- getLoadoutAttributeValue+  pure (LoadoutsAttributeValue blue orange)++putLoadoutsAttributeValue :: LoadoutsAttributeValue -> BinaryBit.BitPut ()+putLoadoutsAttributeValue loadoutsAttributeValue = do+  putLoadoutAttributeValue (loadoutsAttributeValueBlue loadoutsAttributeValue)+  putLoadoutAttributeValue (loadoutsAttributeValueOrange loadoutsAttributeValue)
+ library/Rattletrap/AttributeValue/LoadoutsOnline.hs view
@@ -0,0 +1,34 @@+module Rattletrap.AttributeValue.LoadoutsOnline where++import Rattletrap.AttributeValue.LoadoutOnline++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data LoadoutsOnlineAttributeValue = LoadoutsOnlineAttributeValue+  { loadoutsOnlineAttributeValueBlue :: LoadoutOnlineAttributeValue+  , loadoutsOnlineAttributeValueOrange :: LoadoutOnlineAttributeValue+  , loadoutsOnlineAttributeValueUnknown1 :: Bool+  , loadoutsOnlineAttributeValueUnknown2 :: Bool+  } deriving (Eq, Ord, Show)++getLoadoutsOnlineAttributeValue :: BinaryBit.BitGet LoadoutsOnlineAttributeValue+getLoadoutsOnlineAttributeValue = do+  blueLoadout <- getLoadoutOnlineAttributeValue+  orangeLoadout <- getLoadoutOnlineAttributeValue+  unknown1 <- BinaryBit.getBool+  unknown2 <- BinaryBit.getBool+  pure+    (LoadoutsOnlineAttributeValue blueLoadout orangeLoadout unknown1 unknown2)++putLoadoutsOnlineAttributeValue :: LoadoutsOnlineAttributeValue+                                -> BinaryBit.BitPut ()+putLoadoutsOnlineAttributeValue loadoutsOnlineAttributeValue = do+  putLoadoutOnlineAttributeValue+    (loadoutsOnlineAttributeValueBlue loadoutsOnlineAttributeValue)+  putLoadoutOnlineAttributeValue+    (loadoutsOnlineAttributeValueOrange loadoutsOnlineAttributeValue)+  BinaryBit.putBool+    (loadoutsOnlineAttributeValueUnknown1 loadoutsOnlineAttributeValue)+  BinaryBit.putBool+    (loadoutsOnlineAttributeValueUnknown2 loadoutsOnlineAttributeValue)
+ library/Rattletrap/AttributeValue/Location.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.Location where++import Rattletrap.Vector++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype LocationAttributeValue = LocationAttributeValue+  { locationAttributeValueValue :: Vector+  } deriving (Eq, Ord, Show)++getLocationAttributeValue :: BinaryBit.BitGet LocationAttributeValue+getLocationAttributeValue = do+  value <- getVector+  pure (LocationAttributeValue value)++putLocationAttributeValue :: LocationAttributeValue -> BinaryBit.BitPut ()+putLocationAttributeValue locationAttributeValue =+  putVector (locationAttributeValueValue locationAttributeValue)
+ library/Rattletrap/AttributeValue/MusicStinger.hs view
@@ -0,0 +1,27 @@+module Rattletrap.AttributeValue.MusicStinger where++import Rattletrap.Word32+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data MusicStingerAttributeValue = MusicStingerAttributeValue+  { musicStingerAttributeValueFlag :: Bool+  , musicStingerAttributeValueCue :: Word32+  , musicStingerAttributeValueTrigger :: Word8+  } deriving (Eq, Ord, Show)++getMusicStingerAttributeValue :: BinaryBit.BitGet MusicStingerAttributeValue+getMusicStingerAttributeValue = do+  flag <- BinaryBit.getBool+  cue <- getWord32Bits+  trigger <- getWord8Bits+  pure (MusicStingerAttributeValue flag cue trigger)++putMusicStingerAttributeValue :: MusicStingerAttributeValue+                              -> BinaryBit.BitPut ()+putMusicStingerAttributeValue musicStingerAttributeValue = do+  BinaryBit.putBool (musicStingerAttributeValueFlag musicStingerAttributeValue)+  putWord32Bits (musicStingerAttributeValueCue musicStingerAttributeValue)+  putWord8Bits (musicStingerAttributeValueTrigger musicStingerAttributeValue)
+ library/Rattletrap/AttributeValue/PartyLeader.hs view
@@ -0,0 +1,33 @@+module Rattletrap.AttributeValue.PartyLeader where++import Rattletrap.RemoteId+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data PartyLeaderAttributeValue = PartyLeaderAttributeValue+  { partyLeaderAttributeValueSystemId :: Word8+  , partyLeaderAttributeValueId :: Maybe (RemoteId, Word8)+  } deriving (Eq, Ord, Show)++getPartyLeaderAttributeValue :: BinaryBit.BitGet PartyLeaderAttributeValue+getPartyLeaderAttributeValue = do+  systemId <- getWord8Bits+  maybeRemoteAndLocalId <-+    if systemId == Word8 0+      then pure Nothing+      else do+        remoteId <- getRemoteId systemId+        localId <- getWord8Bits+        pure (Just (remoteId, localId))+  pure (PartyLeaderAttributeValue systemId maybeRemoteAndLocalId)++putPartyLeaderAttributeValue :: PartyLeaderAttributeValue -> BinaryBit.BitPut ()+putPartyLeaderAttributeValue partyLeaderAttributeValue = do+  putWord8Bits (partyLeaderAttributeValueSystemId partyLeaderAttributeValue)+  case partyLeaderAttributeValueId partyLeaderAttributeValue of+    Nothing -> pure ()+    Just (remoteId, localId) -> do+      putRemoteId remoteId+      putWord8Bits localId
+ library/Rattletrap/AttributeValue/Pickup.hs view
@@ -0,0 +1,32 @@+module Rattletrap.AttributeValue.Pickup where++import Rattletrap.Word32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data PickupAttributeValue = PickupAttributeValue+  { pickupAttributeValueInstigatorId :: Maybe Word32+  , pickupAttributeValuePickedUp :: Bool+  } deriving (Eq, Ord, Show)++getPickupAttributeValue :: BinaryBit.BitGet PickupAttributeValue+getPickupAttributeValue = do+  instigator <- BinaryBit.getBool+  maybeInstigatorId <-+    if instigator+      then do+        instigatorId <- getWord32Bits+        pure (Just instigatorId)+      else pure Nothing+  pickedUp <- BinaryBit.getBool+  pure (PickupAttributeValue maybeInstigatorId pickedUp)++putPickupAttributeValue :: PickupAttributeValue -> BinaryBit.BitPut ()+putPickupAttributeValue pickupAttributeValue = do+  case pickupAttributeValueInstigatorId pickupAttributeValue of+    Nothing -> BinaryBit.putBool False+    Just instigatorId -> do+      BinaryBit.putBool True+      putWord32Bits instigatorId+  BinaryBit.putBool (pickupAttributeValuePickedUp pickupAttributeValue)
+ library/Rattletrap/AttributeValue/PrivateMatchSettings.hs view
@@ -0,0 +1,54 @@+module Rattletrap.AttributeValue.PrivateMatchSettings where++import Rattletrap.Text+import Rattletrap.Word32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data PrivateMatchSettingsAttributeValue = PrivateMatchSettingsAttributeValue+  { privateMatchSettingsAttributeValueMutators :: Text+  , privateMatchSettingsAttributeValueJoinableBy :: Word32+  , privateMatchSettingsAttributeValueMaxPlayers :: Word32+  , privateMatchSettingsAttributeValueGameName :: Text+  , privateMatchSettingsAttributeValuePassword :: Text+  , privateMatchSettingsAttributeValueFlag :: Bool+  } deriving (Eq, Ord, Show)++getPrivateMatchSettingsAttributeValue :: BinaryBit.BitGet PrivateMatchSettingsAttributeValue+getPrivateMatchSettingsAttributeValue = do+  mutators <- getTextBits+  joinableBy <- getWord32Bits+  maxPlayers <- getWord32Bits+  gameName <- getTextBits+  password <- getTextBits+  flag <- BinaryBit.getBool+  pure+    (PrivateMatchSettingsAttributeValue+       mutators+       joinableBy+       maxPlayers+       gameName+       password+       flag)++putPrivateMatchSettingsAttributeValue :: PrivateMatchSettingsAttributeValue+                                      -> BinaryBit.BitPut ()+putPrivateMatchSettingsAttributeValue privateMatchSettingsAttributeValue = do+  putTextBits+    (privateMatchSettingsAttributeValueMutators+       privateMatchSettingsAttributeValue)+  putWord32Bits+    (privateMatchSettingsAttributeValueJoinableBy+       privateMatchSettingsAttributeValue)+  putWord32Bits+    (privateMatchSettingsAttributeValueMaxPlayers+       privateMatchSettingsAttributeValue)+  putTextBits+    (privateMatchSettingsAttributeValueGameName+       privateMatchSettingsAttributeValue)+  putTextBits+    (privateMatchSettingsAttributeValuePassword+       privateMatchSettingsAttributeValue)+  BinaryBit.putBool+    (privateMatchSettingsAttributeValueFlag privateMatchSettingsAttributeValue)
+ library/Rattletrap/AttributeValue/QWord.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.QWord where++import Rattletrap.Word64++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype QWordAttributeValue = QWordAttributeValue+  { qWordAttributeValueValue :: Word64+  } deriving (Eq, Ord, Show)++getQWordAttributeValue :: BinaryBit.BitGet QWordAttributeValue+getQWordAttributeValue = do+  value <- getWord64Bits+  pure (QWordAttributeValue value)++putQWordAttributeValue :: QWordAttributeValue -> BinaryBit.BitPut ()+putQWordAttributeValue qWordAttributeValue =+  putWord64Bits (qWordAttributeValueValue qWordAttributeValue)
+ library/Rattletrap/AttributeValue/Reservation.hs view
@@ -0,0 +1,56 @@+module Rattletrap.AttributeValue.Reservation where++import Rattletrap.AttributeValue.UniqueId+import Rattletrap.CompressedWord+import Rattletrap.Text+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Word as Word++data ReservationAttributeValue = ReservationAttributeValue+  { reservationAttributeValueNumber :: CompressedWord+  , reservationAttributeValueUniqueId :: UniqueIdAttributeValue+  , reservationAttributeValueName :: Maybe Text+  , reservationAttributeValueUnknown1 :: Bool+  , reservationAttributeValueUnknown2 :: Bool+  , reservationAttributeValueUnknown3 :: Maybe Word.Word8+  } deriving (Eq, Ord, Show)++getReservationAttributeValue :: (Int, Int)+                             -> BinaryBit.BitGet ReservationAttributeValue+getReservationAttributeValue version = do+  number <- getCompressedWord 7+  uniqueId <- getUniqueIdAttributeValue+  name <-+    if uniqueIdAttributeValueSystemId uniqueId == Word8 0+      then pure Nothing+      else do+        name <- getTextBits+        pure (Just name)+  a <- BinaryBit.getBool+  b <- BinaryBit.getBool+  mc <-+    if version < (868, 12)+      then pure Nothing+      else do+        c <- BinaryBit.getWord8 6+        pure (Just c)+  pure (ReservationAttributeValue number uniqueId name a b mc)++putReservationAttributeValue :: ReservationAttributeValue -> BinaryBit.BitPut ()+putReservationAttributeValue reservationAttributeValue = do+  putCompressedWord (reservationAttributeValueNumber reservationAttributeValue)+  putUniqueIdAttributeValue+    (reservationAttributeValueUniqueId reservationAttributeValue)+  case reservationAttributeValueName reservationAttributeValue of+    Nothing -> pure ()+    Just name -> putTextBits name+  BinaryBit.putBool+    (reservationAttributeValueUnknown1 reservationAttributeValue)+  BinaryBit.putBool+    (reservationAttributeValueUnknown2 reservationAttributeValue)+  case reservationAttributeValueUnknown3 reservationAttributeValue of+    Nothing -> pure ()+    Just c -> BinaryBit.putWord8 6 c
+ library/Rattletrap/AttributeValue/RigidBodyState.hs view
@@ -0,0 +1,55 @@+module Rattletrap.AttributeValue.RigidBodyState where++import Rattletrap.CompressedWordVector+import Rattletrap.Vector++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data RigidBodyStateAttributeValue = RigidBodyStateAttributeValue+  { rigidBodyStateAttributeValueSleeping :: Bool+  , rigidBodyStateAttributeValueLocation :: Vector+  , rigidBodyStateAttributeValueRotation :: CompressedWordVector+  , rigidBodyStateAttributeValueLinearVelocity :: Maybe Vector+  , rigidBodyStateAttributeValueAngularVelocity :: Maybe Vector+  } deriving (Eq, Ord, Show)++getRigidBodyStateAttributeValue :: BinaryBit.BitGet RigidBodyStateAttributeValue+getRigidBodyStateAttributeValue = 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+    (RigidBodyStateAttributeValue+       sleeping+       location+       rotation+       linearVelocity+       angularVelocity)++putRigidBodyStateAttributeValue :: RigidBodyStateAttributeValue+                                -> BinaryBit.BitPut ()+putRigidBodyStateAttributeValue rigidBodyStateAttributeValue = do+  BinaryBit.putBool+    (rigidBodyStateAttributeValueSleeping rigidBodyStateAttributeValue)+  putVector (rigidBodyStateAttributeValueLocation rigidBodyStateAttributeValue)+  putCompressedWordVector+    (rigidBodyStateAttributeValueRotation rigidBodyStateAttributeValue)+  case rigidBodyStateAttributeValueLinearVelocity rigidBodyStateAttributeValue of+    Nothing -> pure ()+    Just linearVelocity -> putVector linearVelocity+  case rigidBodyStateAttributeValueAngularVelocity rigidBodyStateAttributeValue of+    Nothing -> pure ()+    Just angularVelocity -> putVector angularVelocity
+ library/Rattletrap/AttributeValue/String.hs view
@@ -0,0 +1,19 @@+module Rattletrap.AttributeValue.String where++import Rattletrap.Text++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++newtype StringAttributeValue = StringAttributeValue+  { stringAttributeValueValue :: Text+  } deriving (Eq, Ord, Show)++getStringAttributeValue :: BinaryBit.BitGet StringAttributeValue+getStringAttributeValue = do+  value <- getTextBits+  pure (StringAttributeValue value)++putStringAttributeValue :: StringAttributeValue -> BinaryBit.BitPut ()+putStringAttributeValue stringAttributeValue =+  putTextBits (stringAttributeValueValue stringAttributeValue)
+ library/Rattletrap/AttributeValue/TeamPaint.hs view
@@ -0,0 +1,38 @@+module Rattletrap.AttributeValue.TeamPaint where++import Rattletrap.Word32+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data TeamPaintAttributeValue = TeamPaintAttributeValue+  { teamPaintAttributeValueTeam :: Word8+  , teamPaintAttributeValuePrimaryColor :: Word8+  , teamPaintAttributeValueAccentColor :: Word8+  , teamPaintAttributeValuePrimaryFinish :: Word32+  , teamPaintAttributeValueAccentFinish :: Word32+  } deriving (Eq, Ord, Show)++getTeamPaintAttributeValue :: BinaryBit.BitGet TeamPaintAttributeValue+getTeamPaintAttributeValue = do+  team <- getWord8Bits+  primaryColor <- getWord8Bits+  accentColor <- getWord8Bits+  primaryFinish <- getWord32Bits+  accentFinish <- getWord32Bits+  pure+    (TeamPaintAttributeValue+       team+       primaryColor+       accentColor+       primaryFinish+       accentFinish)++putTeamPaintAttributeValue :: TeamPaintAttributeValue -> BinaryBit.BitPut ()+putTeamPaintAttributeValue teamPaintAttributeValue = do+  putWord8Bits (teamPaintAttributeValueTeam teamPaintAttributeValue)+  putWord8Bits (teamPaintAttributeValuePrimaryColor teamPaintAttributeValue)+  putWord8Bits (teamPaintAttributeValueAccentColor teamPaintAttributeValue)+  putWord32Bits (teamPaintAttributeValuePrimaryFinish teamPaintAttributeValue)+  putWord32Bits (teamPaintAttributeValueAccentFinish teamPaintAttributeValue)
+ library/Rattletrap/AttributeValue/UniqueId.hs view
@@ -0,0 +1,26 @@+module Rattletrap.AttributeValue.UniqueId where++import Rattletrap.RemoteId+import Rattletrap.Word8++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data UniqueIdAttributeValue = UniqueIdAttributeValue+  { uniqueIdAttributeValueSystemId :: Word8+  , uniqueIdAttributeValueRemoteId :: RemoteId+  , uniqueIdAttributeValueLocalId :: Word8+  } deriving (Eq, Ord, Show)++getUniqueIdAttributeValue :: BinaryBit.BitGet UniqueIdAttributeValue+getUniqueIdAttributeValue = do+  systemId <- getWord8Bits+  remoteId <- getRemoteId systemId+  localId <- getWord8Bits+  pure (UniqueIdAttributeValue systemId remoteId localId)++putUniqueIdAttributeValue :: UniqueIdAttributeValue -> BinaryBit.BitPut ()+putUniqueIdAttributeValue uniqueIdAttributeValue = do+  putWord8Bits (uniqueIdAttributeValueSystemId uniqueIdAttributeValue)+  putRemoteId (uniqueIdAttributeValueRemoteId uniqueIdAttributeValue)+  putWord8Bits (uniqueIdAttributeValueLocalId uniqueIdAttributeValue)
+ library/Rattletrap/AttributeValue/WeldedInfo.hs view
@@ -0,0 +1,34 @@+module Rattletrap.AttributeValue.WeldedInfo where++import Rattletrap.Float32+import Rattletrap.Int32+import Rattletrap.Int8Vector+import Rattletrap.Vector++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data WeldedInfoAttributeValue = WeldedInfoAttributeValue+  { weldedInfoAttributeValueActive :: Bool+  , weldedInfoAttributeValueActorId :: Int32+  , weldedInfoAttributeValueOffset :: Vector+  , weldedInfoAttributeValueMass :: Float32+  , weldedInfoAttributeValueRotation :: Int8Vector+  } deriving (Eq, Ord, Show)++getWeldedInfoAttributeValue :: BinaryBit.BitGet WeldedInfoAttributeValue+getWeldedInfoAttributeValue = do+  active <- BinaryBit.getBool+  actorId <- getInt32Bits+  offset <- getVector+  mass <- getFloat32Bits+  rotation <- getInt8Vector+  pure (WeldedInfoAttributeValue active actorId offset mass rotation)++putWeldedInfoAttributeValue :: WeldedInfoAttributeValue -> BinaryBit.BitPut ()+putWeldedInfoAttributeValue weldedInfoAttributeValue = do+  BinaryBit.putBool (weldedInfoAttributeValueActive weldedInfoAttributeValue)+  putInt32Bits (weldedInfoAttributeValueActorId weldedInfoAttributeValue)+  putVector (weldedInfoAttributeValueOffset weldedInfoAttributeValue)+  putFloat32Bits (weldedInfoAttributeValueMass weldedInfoAttributeValue)+  putInt8Vector (weldedInfoAttributeValueRotation weldedInfoAttributeValue)
+ library/Rattletrap/AttributeValueType.hs view
@@ -0,0 +1,30 @@+module Rattletrap.AttributeValueType where++data AttributeValueType+  = AVBoolean+  | AVByte+  | AVCamSettings+  | AVDemolish+  | AVEnum+  | AVExplosion+  | AVFlaggedInt+  | AVFloat+  | AVGameMode+  | AVInt+  | AVLoadout+  | AVLoadoutOnline+  | AVLoadouts+  | AVLoadoutsOnline+  | AVLocation+  | AVMusicStinger+  | AVPartyLeader+  | AVPickup+  | AVPrivateMatchSettings+  | AVQWord+  | AVReservation+  | AVRigidBodyState+  | AVString+  | AVTeamPaint+  | AVUniqueId+  | AVWeldedInfo+  deriving (Eq, Ord, Show)
+ library/Rattletrap/Cache.hs view
@@ -0,0 +1,35 @@+module Rattletrap.Cache where++import Rattletrap.AttributeMapping+import Rattletrap.List+import Rattletrap.Word32++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+    { cacheClassId = classId+    , cacheParentCacheId = parentCacheId+    , cacheCacheId = cacheId+    , cacheAttributeMappings = 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 view
@@ -0,0 +1,247 @@+module Rattletrap.ClassAttributeMap where++import Rattletrap.ActorMap+import Rattletrap.AttributeMapping+import Rattletrap.Cache+import Rattletrap.ClassMapping+import Rattletrap.CompressedWord+import Rattletrap.Data+import Rattletrap.List+import Rattletrap.Text+import Rattletrap.Utility+import Rattletrap.Word32++import qualified Data.Bimap as Bimap+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set++data ClassAttributeMap = ClassAttributeMap+  { classAttributeMapObjectMap :: Bimap.Bimap Word32 Text+  , classAttributeMapClassMap :: Bimap.Bimap Word32 Text+  , classAttributeMapValue :: Map.Map Word32 (Bimap.Bimap Word32 Word32)+  } deriving (Eq, Show)++makeClassAttributeMap :: List Text+                      -> List ClassMapping+                      -> List Cache+                      -> ClassAttributeMap+makeClassAttributeMap objects classMappings caches =+  let objectMap = makeObjectMap objects+      classCache = makeClassCache classMappings caches+      attributeMap = makeAttributeMap caches+      classIds = map (\(_, classId, _, _) -> classId) classCache+      parentMap = makeParentMap classMappings caches+      value =+        Map.fromList+          (map+             (\classId ->+                let ownAttributes =+                      Maybe.fromMaybe+                        Bimap.empty+                        (Map.lookup classId attributeMap)+                    parentsAttributes =+                      case Map.lookup classId parentMap of+                        Nothing -> []+                        Just parentClassIds ->+                          map+                            (\parentClassId ->+                               Maybe.fromMaybe+                                 Bimap.empty+                                 (Map.lookup parentClassId attributeMap))+                            parentClassIds+                    attributes = ownAttributes : parentsAttributes+                in (classId, Bimap.fromList (concatMap Bimap.toList attributes)))+             classIds)+  in ClassAttributeMap+     { classAttributeMapObjectMap = objectMap+     , classAttributeMapClassMap = makeClassMap classMappings+     , classAttributeMapValue = value+     }++makeClassCache :: List ClassMapping+               -> List Cache+               -> [(Maybe Text, Word32, Word32, Word32)]+makeClassCache classMappings caches =+  let classMap = makeClassMap classMappings+  in 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 (Bimap.Bimap Word32 Word32)+makeAttributeMap caches =+  Map.fromList+    (map+       (\cache ->+          ( cacheClassId cache+          , Bimap.fromList+              (map+                 (\attributeMapping ->+                    ( attributeMappingStreamId attributeMapping+                    , attributeMappingObjectId attributeMapping))+                 (listValue (cacheAttributeMappings cache)))))+       (listValue caches))++makeShallowParentMap :: List ClassMapping -> List Cache -> Map.Map Word32 Word32+makeShallowParentMap classMappings caches =+  let classCache = makeClassCache classMappings caches+  in 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 :: List ClassMapping -> List Cache -> Map.Map Word32 [Word32]+makeParentMap classMappings caches =+  let shallowParentMap = makeShallowParentMap classMappings caches+  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 -> Bimap.Bimap Word32 Text+makeObjectMap objects =+  Bimap.fromList (zip (map Word32 [0 ..]) (listValue objects))++getObjectName :: ClassAttributeMap -> Word32 -> Maybe Text+getObjectName classAttributeMap objectId =+  Bimap.lookup objectId (classAttributeMapObjectMap classAttributeMap)++getClassName :: Text -> Maybe Text+getClassName rawObjectName =+  Map.lookup (normalizeObjectName rawObjectName) objectClasses++normalizeObjectName :: Text -> Text+normalizeObjectName objectName =+  stringToText+    (replace+       "_[0-9]+$"+       ""+       (replace "^[A-Z_a-z]+[.]TheWorld:" "TheWorld:" (textToString 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 :: ClassAttributeMap+                    -> ActorMap+                    -> CompressedWord+                    -> Maybe Word+getAttributeIdLimit classAttributeMap actorMap actorId = do+  attributeMap <- getAttributeMap classAttributeMap actorMap actorId+  let streamIds = Bimap.keys attributeMap+  let maxStreamId = maximum (Word32 0 : streamIds)+  let limit = fromIntegral (word32Value maxStreamId)+  pure limit++getAttributeName :: ClassAttributeMap+                 -> ActorMap+                 -> CompressedWord+                 -> CompressedWord+                 -> Maybe Text+getAttributeName classAttributeMap actorMap actorId streamId = do+  attributeMap <- getAttributeMap classAttributeMap actorMap actorId+  let key = Word32 (fromIntegral (compressedWordValue streamId))+  attributeId <- Bimap.lookup key attributeMap+  let objectMap = classAttributeMapObjectMap classAttributeMap+  Bimap.lookup attributeId objectMap++getAttributeMap+  :: ClassAttributeMap+  -> ActorMap+  -> CompressedWord+  -> Maybe (Bimap.Bimap Word32 Word32)+getAttributeMap classAttributeMap actorMap actorId = do+  objectId <- Map.lookup actorId actorMap+  objectName <- getObjectName classAttributeMap objectId+  className <- getClassName objectName+  let classMap = classAttributeMapClassMap classAttributeMap+  classId <- Bimap.lookupR className classMap+  let value = classAttributeMapValue classAttributeMap+  Map.lookup classId value
+ library/Rattletrap/ClassMapping.hs view
@@ -0,0 +1,22 @@+module Rattletrap.ClassMapping where++import Rattletrap.Text+import Rattletrap.Word32++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 {classMappingName = name, classMappingStreamId = streamId}++putClassMapping :: ClassMapping -> Binary.Put+putClassMapping classMapping = do+  putText (classMappingName classMapping)+  putWord32 (classMappingStreamId classMapping)
+ library/Rattletrap/CompressedWord.hs view
@@ -0,0 +1,59 @@+module Rattletrap.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/CompressedWordVector.hs view
@@ -0,0 +1,31 @@+module Rattletrap.CompressedWordVector where++import Rattletrap.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+    { compressedWordVectorX = x+    , compressedWordVectorY = y+    , compressedWordVectorZ = z+    }++putCompressedWordVector :: CompressedWordVector -> BinaryBit.BitPut ()+putCompressedWordVector compressedWordVector = do+  putCompressedWord (compressedWordVectorX compressedWordVector)+  putCompressedWord (compressedWordVectorY compressedWordVector)+  putCompressedWord (compressedWordVectorZ compressedWordVector)
+ library/Rattletrap/Content.hs view
@@ -0,0 +1,86 @@+module Rattletrap.Content where++import Rattletrap.ActorMap+import Rattletrap.Cache+import Rattletrap.ClassAttributeMap+import Rattletrap.ClassMapping+import Rattletrap.Frame+import Rattletrap.KeyFrame+import Rattletrap.List+import Rattletrap.Mark+import Rattletrap.Message+import Rattletrap.Text+import Rattletrap.Utility+import Rattletrap.Word32++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++data Content = Content+  { contentLevels :: List Text+  , contentKeyFrames :: List KeyFrame+  , contentStreamSize :: Word32+  , contentFrames :: [Frame]+  , contentMessages :: List Message+  , contentMarks :: List Mark+  , contentPackages :: List Text+  , contentObjects :: List Text+  , contentNames :: List Text+  , contentClassMappings :: List ClassMapping+  , contentCaches :: List Cache+  } deriving (Eq, Ord, Show)++getContent :: (Int, Int) -> Int -> Binary.Get Content+getContent version numFrames = 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+  let (frames, _) =+        Binary.runGet+          (BinaryBit.runBitGet+             (getFrames version numFrames classAttributeMap makeActorMap))+          (reverseBytes stream)+  pure+    Content+    { contentLevels = levels+    , contentKeyFrames = keyFrames+    , contentStreamSize = streamSize+    , contentFrames = frames+    , contentMessages = messages+    , contentMarks = marks+    , contentPackages = packages+    , contentObjects = objects+    , contentNames = names+    , contentClassMappings = classMappings+    , contentCaches = 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 view
@@ -0,0 +1,37 @@+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++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 view
@@ -0,0 +1,349 @@+module Rattletrap.Data where++import Rattletrap.AttributeValueType++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")+  -- , ("TAGame.VoteActor_TA", "Engine.ReplicationInfo")+  ]++rawClassesWithLocation :: [String]+rawClassesWithLocation =+  [ "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_TA"+  , "TAGame.Car_Season_TA"+  , "TAGame.Car_TA"+  ]++rawObjectClasses :: [(String, String)]+rawObjectClasses =+  [ ("Archetypes.Ball.Ball_Basketball", "TAGame.Ball_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_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_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.CameraSettingsActor_TA:PRI", "TAGame.CameraSettingsActor_TA")+  , ("TAGame.Default__CameraSettingsActor_TA", "TAGame.CameraSettingsActor_TA")+  , ("TAGame.Default__PRI_TA", "TAGame.PRI_TA")+  -- , ("TAGame.Default__VoteActor_TA", "TAGame.VoteActor_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")+  ]++rawAttributeValueTypes :: [(String, AttributeValueType)]+rawAttributeValueTypes =+  [ ("Engine.Actor:bBlockActors", AVBoolean)+  , ("Engine.Actor:DrawScale", AVFloat)+  , ("Engine.Actor:Role", AVEnum)+  , ("Engine.Actor:bCollideActors", AVBoolean)+  , ("Engine.Actor:bHidden", AVBoolean)+  , ("Engine.GameReplicationInfo:GameClass", AVFlaggedInt)+  , ("Engine.GameReplicationInfo:ServerName", AVString)+  , ("Engine.GameReplicationInfo:bMatchIsOver", AVBoolean)+  , ("Engine.Pawn:PlayerReplicationInfo", AVFlaggedInt)+  , ("Engine.PlayerReplicationInfo:Ping", AVByte)+  , ("Engine.PlayerReplicationInfo:PlayerID", AVInt)+  , ("Engine.PlayerReplicationInfo:PlayerName", AVString)+  , ("Engine.PlayerReplicationInfo:RemoteUserData", AVString)+  , ("Engine.PlayerReplicationInfo:Score", AVInt)+  , ("Engine.PlayerReplicationInfo:Team", AVFlaggedInt)+  , ("Engine.PlayerReplicationInfo:UniqueId", AVUniqueId)+  , ("Engine.PlayerReplicationInfo:bBot", AVBoolean)+  , ("Engine.PlayerReplicationInfo:bIsSpectator", AVBoolean)+  , ("Engine.PlayerReplicationInfo:bReadyToPlay", AVBoolean)+  , ("Engine.PlayerReplicationInfo:bWaitingPlayer", AVBoolean)+  , ("Engine.TeamInfo:Score", AVInt)+  , ("ProjectX.GRI_X:GameServerID", AVQWord)+  , ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", AVInt)+  , ("ProjectX.GRI_X:ReplicatedGamePlaylist", AVInt)+  , ("ProjectX.GRI_X:Reservations", AVReservation)+  , ("ProjectX.GRI_X:bGameStarted", AVBoolean)+  , ("TAGame.Ball_TA:GameEvent", AVFlaggedInt)+  , ("TAGame.Ball_TA:HitTeamNum", AVByte)+  , ("TAGame.Ball_TA:ReplicatedAddedCarBounceScale", AVFloat)+  , ("TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale", AVFloat)+  , ("TAGame.Ball_TA:ReplicatedBallScale", AVFloat)+  , ("TAGame.Ball_TA:ReplicatedExplosionData", AVExplosion)+  , ("TAGame.Ball_TA:ReplicatedWorldBounceScale", AVFloat)+  , ("TAGame.CameraSettingsActor_TA:CameraPitch", AVByte)+  , ("TAGame.CameraSettingsActor_TA:CameraYaw", AVByte)+  , ("TAGame.CameraSettingsActor_TA:PRI", AVFlaggedInt)+  , ("TAGame.CameraSettingsActor_TA:ProfileSettings", AVCamSettings)+  , ("TAGame.CameraSettingsActor_TA:bUsingBehindView", AVBoolean)+  , ("TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera", AVBoolean)+  , ("TAGame.CarComponent_Boost_TA:BoostModifier", AVFloat)+  , ("TAGame.CarComponent_Boost_TA:RechargeDelay", AVFloat)+  , ("TAGame.CarComponent_Boost_TA:RechargeRate", AVFloat)+  , ("TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount", AVByte)+  , ("TAGame.CarComponent_Boost_TA:UnlimitedBoostRefCount", AVInt)+  , ("TAGame.CarComponent_Boost_TA:bNoBoost", AVBoolean)+  , ("TAGame.CarComponent_Boost_TA:bUnlimitedBoost", AVBoolean)+  , ("TAGame.CarComponent_Dodge_TA:DodgeTorque", AVLocation)+  , ("TAGame.CarComponent_FlipCar_TA:FlipCarTime", AVFloat)+  , ("TAGame.CarComponent_FlipCar_TA:bFlipRight", AVBoolean)+  , ("TAGame.CarComponent_TA:ReplicatedActive", AVByte)+  , ("TAGame.CarComponent_TA:ReplicatedActivityTime", AVFloat)+  , ("TAGame.CarComponent_TA:Vehicle", AVFlaggedInt)+  , ("TAGame.Car_TA:AddedBallForceMultiplier", AVFloat)+  , ("TAGame.Car_TA:AddedCarForceMultiplier", AVFloat)+  , ("TAGame.Car_TA:AttachedPickup", AVFlaggedInt)+  , ("TAGame.Car_TA:ReplicatedDemolish", AVDemolish)+  , ("TAGame.Car_TA:TeamPaint", AVTeamPaint)+  , ("TAGame.CrowdActor_TA:GameEvent", AVFlaggedInt)+  , ("TAGame.CrowdActor_TA:ModifiedNoise", AVFloat)+  , ("TAGame.CrowdActor_TA:ReplicatedCountDownNumber", AVInt)+  , ("TAGame.CrowdActor_TA:ReplicatedOneShotSound", AVFlaggedInt)+  , ("TAGame.CrowdActor_TA:ReplicatedRoundCountDownNumber", AVInt)+  , ("TAGame.CrowdManager_TA:GameEvent", AVFlaggedInt)+  , ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", AVFlaggedInt)+  , ("TAGame.GRI_TA:NewDedicatedServerIP", AVString)+  , ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", AVPrivateMatchSettings)+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", AVMusicStinger)+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", AVByte)+  , ("TAGame.GameEvent_Soccar_TA:RoundNum", AVInt)+  , ("TAGame.GameEvent_Soccar_TA:SecondsRemaining", AVInt)+  , ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", AVFlaggedInt)+  , ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", AVBoolean)+  , ("TAGame.GameEvent_Soccar_TA:bOverTime", AVBoolean)+  , ("TAGame.GameEvent_TA:BotSkill", AVInt)+  , ("TAGame.GameEvent_TA:GameMode", AVGameMode)+  , ("TAGame.GameEvent_TA:MatchTypeClass", AVFlaggedInt)+  , ("TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining", AVInt)+  , ("TAGame.GameEvent_TA:ReplicatedStateIndex", AVByte)+  , ("TAGame.GameEvent_TA:ReplicatedStateName", AVInt)+  , ("TAGame.GameEvent_TA:bCanVoteToForfeit", AVBoolean)+  , ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", AVBoolean)+  , ("TAGame.GameEvent_Team_TA:MaxTeamSize", AVInt)+  , ("TAGame.PRI_TA:CameraPitch", AVByte)+  , ("TAGame.PRI_TA:CameraSettings", AVCamSettings)+  , ("TAGame.PRI_TA:CameraYaw", AVByte)+  , ("TAGame.PRI_TA:ClientLoadout", AVLoadout)+  , ("TAGame.PRI_TA:ClientLoadoutOnline", AVLoadoutOnline)+  , ("TAGame.PRI_TA:ClientLoadouts", AVLoadouts)+  , ("TAGame.PRI_TA:ClientLoadoutsOnline", AVLoadoutsOnline)+  , ("TAGame.PRI_TA:MatchAssists", AVInt)+  , ("TAGame.PRI_TA:MatchGoals", AVInt)+  , ("TAGame.PRI_TA:MatchSaves", AVInt)+  , ("TAGame.PRI_TA:MatchScore", AVInt)+  , ("TAGame.PRI_TA:MatchShots", AVInt)+  , ("TAGame.PRI_TA:PartyLeader", AVPartyLeader)+  , ("TAGame.PRI_TA:PawnType", AVByte)+  , ("TAGame.PRI_TA:PersistentCamera", AVFlaggedInt)+  , ("TAGame.PRI_TA:ReplicatedGameEvent", AVFlaggedInt)+  , ("TAGame.PRI_TA:Title", AVInt)+  , ("TAGame.PRI_TA:TotalXP", AVInt)+  , ("TAGame.PRI_TA:bIsInSplitScreen", AVBoolean)+  , ("TAGame.PRI_TA:bMatchMVP", AVBoolean)+  , ("TAGame.PRI_TA:bOnlineLoadoutSet", AVBoolean)+  , ("TAGame.PRI_TA:bOnlineLoadoutsSet", AVBoolean)+  , ("TAGame.PRI_TA:bReady", AVBoolean)+  , ("TAGame.PRI_TA:bUsingBehindView", AVBoolean)+  , ("TAGame.PRI_TA:bUsingSecondaryCamera", AVBoolean)+  , ("TAGame.RBActor_TA:ReplicatedRBState", AVRigidBodyState)+  , ("TAGame.RBActor_TA:WeldedInfo", AVWeldedInfo)+  , ("TAGame.RBActor_TA:bFrozen", AVBoolean)+  , ("TAGame.RBActor_TA:bIgnoreSyncing", AVBoolean)+  , ("TAGame.RBActor_TA:bReplayActor", AVBoolean)+  , ("TAGame.SpecialPickup_BallFreeze_TA:RepOrigSpeed", AVFloat)+  , ("TAGame.SpecialPickup_BallVelcro_TA:AttachTime", AVFloat)+  , ("TAGame.SpecialPickup_BallVelcro_TA:BreakTime", AVFloat)+  , ("TAGame.SpecialPickup_BallVelcro_TA:bBroken", AVBoolean)+  , ("TAGame.SpecialPickup_BallVelcro_TA:bHit", AVBoolean)+  , ("TAGame.SpecialPickup_Targeted_TA:Targeted", AVFlaggedInt)+  , ("TAGame.Team_Soccar_TA:GameScore", AVInt)+  , ("TAGame.Team_TA:CustomTeamName", AVString)+  , ("TAGame.Team_TA:GameEvent", AVFlaggedInt)+  , ("TAGame.Team_TA:LogoData", AVFlaggedInt)+  , ("TAGame.VehiclePickup_TA:ReplicatedPickupData", AVPickup)+  , ("TAGame.Vehicle_TA:ReplicatedSteer", AVByte)+  , ("TAGame.Vehicle_TA:ReplicatedThrottle", AVByte)+  , ("TAGame.Vehicle_TA:bDriving", AVBoolean)+  , ("TAGame.Vehicle_TA:bReplicatedHandbrake", AVBoolean)+  ]++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/Dictionary.hs view
@@ -0,0 +1,33 @@+module Rattletrap.Dictionary where++import Rattletrap.Text++import qualified Data.Binary as Binary++newtype Dictionary a = Dictionary+  { dictionaryValue :: [(Text, Maybe a)]+  } deriving (Eq, Ord, Show)++getDictionary :: Binary.Get a -> Binary.Get (Dictionary a)+getDictionary getValue = do+  key <- getText+  if isNoneKey key+    then pure (Dictionary [(key, Nothing)])+    else do+      value <- getValue+      let element = (key, Just value)+      Dictionary elements <- getDictionary getValue+      pure (Dictionary (element : elements))++putDictionary :: (a -> Binary.Put) -> Dictionary a -> Binary.Put+putDictionary putValue (Dictionary elements) =+  mapM_+    (\(key, maybeValue) -> do+       putText key+       case maybeValue of+         Nothing -> pure ()+         Just value -> putValue value)+    elements++isNoneKey :: Text -> Bool+isNoneKey text = filter (/= '\x00') (textToString text) == "None"
+ library/Rattletrap/Float32.hs view
@@ -0,0 +1,33 @@+module Rattletrap.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/Frame.hs view
@@ -0,0 +1,61 @@+module Rattletrap.Frame where++import Rattletrap.ActorMap+import Rattletrap.ClassAttributeMap+import Rattletrap.Float32+import Rattletrap.Replication++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data Frame = Frame+  { frameTime :: Float32+  , frameDelta :: Float32+  , frameReplications :: [Replication]+  } deriving (Eq, Ord, Show)++getFrames+  :: (Int, Int)+  -> Int+  -> ClassAttributeMap+  -> ActorMap+  -> BinaryBit.BitGet ([Frame], ActorMap)+getFrames version numFrames classAttributeMap actorMap = do+  maybeFrame <-+    if numFrames > 0+      then getFrame version classAttributeMap actorMap+      else pure Nothing+  case maybeFrame of+    Nothing -> pure ([], actorMap)+    Just (frame, newActorMap) -> do+      (frames, newerActorMap) <-+        getFrames version (numFrames - 1) classAttributeMap newActorMap+      pure (frame : frames, newerActorMap)++putFrames :: [Frame] -> BinaryBit.BitPut ()+putFrames = mapM_ putFrame++getFrame+  :: (Int, Int)+  -> ClassAttributeMap+  -> ActorMap+  -> BinaryBit.BitGet (Maybe (Frame, ActorMap))+getFrame version classAttributeMap actorMap = do+  time <- getFloat32Bits+  delta <- getFloat32Bits+  (replications, newActorMap) <-+    getReplications version classAttributeMap actorMap+  pure+    (Just+       ( Frame+         { frameTime = time+         , frameDelta = delta+         , frameReplications = replications+         }+       , newActorMap))++putFrame :: Frame -> BinaryBit.BitPut ()+putFrame frame = do+  putFloat32Bits (frameTime frame)+  putFloat32Bits (frameDelta frame)+  putReplications (frameReplications frame)
+ library/Rattletrap/Header.hs view
@@ -0,0 +1,36 @@+module Rattletrap.Header where++import Rattletrap.Dictionary+import Rattletrap.Property+import Rattletrap.Text+import Rattletrap.Word32++import qualified Data.Binary as Binary++data Header = Header+  { headerEngineVersion :: Word32+  , headerLicenseeVersion :: Word32+  , headerLabel :: Text+  , headerProperties :: Dictionary Property+  } deriving (Eq, Ord, Show)++getHeader :: Binary.Get Header+getHeader = do+  engineVersion <- getWord32+  licenseeVersion <- getWord32+  label <- getText+  properties <- getDictionary getProperty+  pure+    Header+    { headerEngineVersion = engineVersion+    , headerLicenseeVersion = licenseeVersion+    , headerLabel = label+    , headerProperties = properties+    }++putHeader :: Header -> Binary.Put+putHeader header = do+  putWord32 (headerEngineVersion header)+  putWord32 (headerLicenseeVersion header)+  putText (headerLabel header)+  putDictionary putProperty (headerProperties header)
+ library/Rattletrap/Initialization.hs view
@@ -0,0 +1,39 @@+module Rattletrap.Initialization where++import Rattletrap.Int8Vector+import Rattletrap.Vector++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data Initialization = Initialization+  { initializationLocation :: Maybe Vector+  , initializationRotation :: Maybe Int8Vector+  } 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+    {initializationLocation = location, initializationRotation = 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/Int32.hs view
@@ -0,0 +1,33 @@+module Rattletrap.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 int32) = Binary.putInt32le 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/Int8.hs view
@@ -0,0 +1,32 @@+module Rattletrap.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/Int8Vector.hs view
@@ -0,0 +1,42 @@+module Rattletrap.Int8Vector where++import Rattletrap.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 {int8VectorX = x, int8VectorY = y, int8VectorZ = 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/KeyFrame.hs view
@@ -0,0 +1,27 @@+module Rattletrap.KeyFrame where++import Rattletrap.Float32+import Rattletrap.Word32++import qualified Data.Binary as Binary++data KeyFrame = KeyFrame+  { keyFrameTime :: Float32+  , keyFrameFrame :: Word32+  , keyFramePosition :: Word32+  } deriving (Eq, Ord, Show)++getKeyFrame :: Binary.Get KeyFrame+getKeyFrame = do+  time <- getFloat32+  frame <- getWord32+  position <- getWord32+  pure+    KeyFrame+    {keyFrameTime = time, keyFrameFrame = frame, keyFramePosition = position}++putKeyFrame :: KeyFrame -> Binary.Put+putKeyFrame keyFrame = do+  putFloat32 (keyFrameTime keyFrame)+  putWord32 (keyFrameFrame keyFrame)+  putWord32 (keyFramePosition keyFrame)
+ library/Rattletrap/List.hs view
@@ -0,0 +1,21 @@+module Rattletrap.List where++import Rattletrap.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 elements) = do+  putWord32 (Word32 (fromIntegral (length elements)))+  mapM_ putElement elements
+ library/Rattletrap/Mark.hs view
@@ -0,0 +1,22 @@+module Rattletrap.Mark where++import Rattletrap.Text+import Rattletrap.Word32++import qualified Data.Binary as Binary++data Mark = Mark+  { markValue :: Text+  , markFrame :: Word32+  } deriving (Eq, Ord, Show)++getMark :: Binary.Get Mark+getMark = do+  value <- getText+  frame <- getWord32+  pure Mark {markValue = value, markFrame = frame}++putMark :: Mark -> Binary.Put+putMark mark = do+  putText (markValue mark)+  putWord32 (markFrame mark)
+ library/Rattletrap/Message.hs view
@@ -0,0 +1,25 @@+module Rattletrap.Message where++import Rattletrap.Text+import Rattletrap.Word32++import qualified Data.Binary as Binary++data Message = Message+  { messageFrame :: Word32+  , messageName :: Text+  , messageValue :: Text+  } deriving (Eq, Ord, Show)++getMessage :: Binary.Get Message+getMessage = do+  frame <- getWord32+  name <- getText+  value <- getText+  pure Message {messageFrame = frame, messageName = name, messageValue = value}++putMessage :: Message -> Binary.Put+putMessage message = do+  putWord32 (messageFrame message)+  putText (messageName message)+  putText (messageValue message)
+ library/Rattletrap/Property.hs view
@@ -0,0 +1,27 @@+module Rattletrap.Property where++import Rattletrap.PropertyValue+import Rattletrap.Text+import Rattletrap.Word64++import qualified Data.Binary as Binary++data Property = Property+  { propertyKind :: Text+  , propertySize :: Word64+  , propertyValue :: PropertyValue Property+  } deriving (Eq, Ord, Show)++getProperty :: Binary.Get Property+getProperty = do+  kind <- getText+  size <- getWord64+  value <- getPropertyValue getProperty kind+  pure+    Property {propertyKind = kind, propertySize = size, propertyValue = value}++putProperty :: Property -> Binary.Put+putProperty property = do+  putText (propertyKind property)+  putWord64 (propertySize property)+  putPropertyValue putProperty (propertyValue property)
+ library/Rattletrap/PropertyValue.hs view
@@ -0,0 +1,74 @@+module Rattletrap.PropertyValue where++import Rattletrap.Dictionary+import Rattletrap.Float32+import Rattletrap.Int32+import Rattletrap.List+import Rattletrap.Text+import Rattletrap.Word64+import Rattletrap.Word8++import qualified Data.Binary as Binary++data PropertyValue a+  = ArrayProperty (List (Dictionary a))+  | BoolProperty Word8+  | ByteProperty Text+                 (Maybe Text)+  | FloatProperty Float32+  | IntProperty Int32+  | NameProperty Text+  | 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 view
@@ -0,0 +1,43 @@+module Rattletrap.RemoteId where++import Rattletrap.Word64+import Rattletrap.Word8++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.Word as Word++data RemoteId+  = PlayStationId [Word.Word8]+  | SplitscreenId [Word.Word8]+  | SteamId Word64+  | XboxId Word64+  deriving (Eq, Ord, Show)++getRemoteId :: Word8 -> BinaryBit.BitGet RemoteId+getRemoteId systemId =+  case word8Value systemId of+    0 -> do+      bytes <- BinaryBit.getLazyByteString 3+      pure (SplitscreenId (ByteString.unpack bytes))+    1 -> do+      word64 <- getWord64Bits+      pure (SteamId word64)+    2 -> do+      bytes <- BinaryBit.getLazyByteString 32+      pure (PlayStationId (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 bytes ->+      BinaryBit.putByteString (ByteString.toStrict (ByteString.pack bytes))+    SplitscreenId bytes ->+      BinaryBit.putByteString (ByteString.toStrict (ByteString.pack bytes))+    SteamId word64 -> putWord64Bits word64+    XboxId word64 -> putWord64Bits word64
+ library/Rattletrap/Replay.hs view
@@ -0,0 +1,55 @@+module Rattletrap.Replay where++import Rattletrap.Content+import Rattletrap.Crc+import Rattletrap.Dictionary+import Rattletrap.Header+import Rattletrap.Int32+import Rattletrap.Property+import Rattletrap.PropertyValue+import Rattletrap.Text+import Rattletrap.Word32++import qualified Data.Binary as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as ByteString++data Replay = Replay+  { replayHeader :: Header+  , replayContent :: Content+  } deriving (Eq, Ord, Show)++getReplay :: Binary.Get Replay+getReplay = do+  _headerSize <- getWord32+  _headerCrc <- getWord32+  header <- getHeader+  _contentSize <- getWord32+  _contentCrc <- getWord32+  let majorVersion = fromIntegral (word32Value (headerEngineVersion header))+  let minorVersion = fromIntegral (word32Value (headerLicenseeVersion header))+  let version = (majorVersion, minorVersion)+  let numFrames =+        case lookup+               (stringToText "NumFrames")+               (dictionaryValue (headerProperties header)) of+          Just (Just (Property _ _ (IntProperty int32))) ->+            fromIntegral (int32Value int32)+          _ -> 0+  content <- getContent version numFrames+  pure Replay {replayHeader = header, replayContent = content}++putReplay :: Replay -> Binary.Put+putReplay replay = do+  let header = Binary.runPut (putHeader (replayHeader replay))+  let headerSize = ByteString.length header+  let headerCrc = getCrc32 header+  putWord32 (Word32 (fromIntegral headerSize))+  putWord32 (Word32 headerCrc)+  Binary.putLazyByteString header+  let content = Binary.runPut (putContent (replayContent replay))+  let contentSize = ByteString.length content+  let contentCrc = getCrc32 content+  putWord32 (Word32 (fromIntegral contentSize))+  putWord32 (Word32 contentCrc)+  Binary.putLazyByteString content
+ library/Rattletrap/Replication.hs view
@@ -0,0 +1,61 @@+module Rattletrap.Replication where++import Rattletrap.ActorMap+import Rattletrap.ClassAttributeMap+import Rattletrap.CompressedWord+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)+  -> ClassAttributeMap+  -> ActorMap+  -> BinaryBit.BitGet ([Replication], ActorMap)+getReplications version classAttributeMap actorMap = do+  maybeReplication <- getReplication version classAttributeMap actorMap+  case maybeReplication of+    Nothing -> pure ([], actorMap)+    Just (replication, newActorMap) -> do+      (replications, newerActorMap) <-+        getReplications version classAttributeMap newActorMap+      pure (replication : replications, newerActorMap)++putReplications :: [Replication] -> BinaryBit.BitPut ()+putReplications replications = do+  mapM_ putReplication replications+  BinaryBit.putBool False++getReplication+  :: (Int, Int)+  -> ClassAttributeMap+  -> ActorMap+  -> BinaryBit.BitGet (Maybe (Replication, ActorMap))+getReplication version classAttributeMap actorMap = do+  hasReplication <- BinaryBit.getBool+  if not hasReplication+    then pure Nothing+    else do+      actorId <- getCompressedWord maxActorId+      (value, newActorMap) <-+        getReplicationValue version classAttributeMap actorMap actorId+      pure+        (Just+           ( Replication+             {replicationActorId = actorId, replicationValue = value}+           , newActorMap))++putReplication :: Replication -> BinaryBit.BitPut ()+putReplication replication = do+  BinaryBit.putBool True+  putCompressedWord (replicationActorId replication)+  putReplicationValue (replicationValue replication)++maxActorId :: Word+maxActorId = 1023
+ library/Rattletrap/ReplicationValue.hs view
@@ -0,0 +1,64 @@+module Rattletrap.ReplicationValue+  ( module Rattletrap.ReplicationValue+  , module Export+  ) where++import Rattletrap.ReplicationValue.Destroyed as Export+import Rattletrap.ReplicationValue.Spawned as Export+import Rattletrap.ReplicationValue.Updated as Export++import Rattletrap.ActorMap+import Rattletrap.ClassAttributeMap+import Rattletrap.CompressedWord++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data ReplicationValue+  = SpawnedReplication SpawnedReplicationValue+  | UpdatedReplication UpdatedReplicationValue+  | DestroyedReplication DestroyedReplicationValue+  deriving (Eq, Ord, Show)++getReplicationValue+  :: (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) <-+            getSpawnedReplicationValue classAttributeMap actorMap actorId+          pure (SpawnedReplication x, newActorMap)+        else do+          x <-+            getUpdatedReplicationValue+              version+              classAttributeMap+              actorMap+              actorId+          pure (UpdatedReplication x, actorMap)+    else do+      x <- getDestroyedReplicationValue+      pure (DestroyedReplication x, actorMap)++putReplicationValue :: ReplicationValue -> BinaryBit.BitPut ()+putReplicationValue value =+  case value of+    SpawnedReplication x -> do+      BinaryBit.putBool True+      BinaryBit.putBool True+      putSpawnedReplicationValue x+    UpdatedReplication x -> do+      BinaryBit.putBool True+      BinaryBit.putBool False+      putUpdatedReplicationValue x+    DestroyedReplication x -> do+      BinaryBit.putBool False+      putDestroyedReplicationValue x
+ library/Rattletrap/ReplicationValue/Destroyed.hs view
@@ -0,0 +1,14 @@+module Rattletrap.ReplicationValue.Destroyed where++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data DestroyedReplicationValue =+  DestroyedReplicationValue+  deriving (Eq, Ord, Show)++getDestroyedReplicationValue :: BinaryBit.BitGet DestroyedReplicationValue+getDestroyedReplicationValue = pure DestroyedReplicationValue++putDestroyedReplicationValue :: DestroyedReplicationValue -> BinaryBit.BitPut ()+putDestroyedReplicationValue DestroyedReplicationValue = pure ()
+ library/Rattletrap/ReplicationValue/Spawned.hs view
@@ -0,0 +1,45 @@+module Rattletrap.ReplicationValue.Spawned where++import Rattletrap.ActorMap+import Rattletrap.ClassAttributeMap+import Rattletrap.CompressedWord+import Rattletrap.Initialization+import Rattletrap.Word32++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data SpawnedReplicationValue = SpawnedReplicationValue+  { spawnedReplicationValueFlag :: Bool+  , spawnedReplicationValueObjectId :: Word32+  , spawnedReplicationValueInitialization :: Initialization+  } deriving (Eq, Ord, Show)++getSpawnedReplicationValue+  :: ClassAttributeMap+  -> ActorMap+  -> CompressedWord+  -> BinaryBit.BitGet (SpawnedReplicationValue, ActorMap)+getSpawnedReplicationValue classAttributeMap actorMap actorId = do+  flag <- BinaryBit.getBool+  objectId <- getWord32Bits+  let newActorMap = updateActorMap actorId objectId actorMap+  case getObjectName classAttributeMap objectId of+    Nothing -> fail ("could not get object name for id " ++ show objectId)+    Just objectName ->+      case getClassName objectName of+        Nothing ->+          fail ("could not get class name for object " ++ show objectName)+        Just className -> do+          let hasLocation = classHasLocation className+          let hasRotation = classHasRotation className+          initialization <- getInitialization hasLocation hasRotation+          pure+            (SpawnedReplicationValue flag objectId initialization, newActorMap)++putSpawnedReplicationValue :: SpawnedReplicationValue -> BinaryBit.BitPut ()+putSpawnedReplicationValue spawnedReplicationValue = do+  BinaryBit.putBool (spawnedReplicationValueFlag spawnedReplicationValue)+  putWord32Bits (spawnedReplicationValueObjectId spawnedReplicationValue)+  putInitialization+    (spawnedReplicationValueInitialization spawnedReplicationValue)
+ library/Rattletrap/ReplicationValue/Updated.hs view
@@ -0,0 +1,27 @@+module Rattletrap.ReplicationValue.Updated where++import Rattletrap.ActorMap+import Rattletrap.Attribute+import Rattletrap.ClassAttributeMap+import Rattletrap.CompressedWord++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data UpdatedReplicationValue = UpdatedReplicationValue+  { updatedReplicationValueAttributes :: [Attribute]+  } deriving (Eq, Ord, Show)++getUpdatedReplicationValue+  :: (Int, Int)+  -> ClassAttributeMap+  -> ActorMap+  -> CompressedWord+  -> BinaryBit.BitGet UpdatedReplicationValue+getUpdatedReplicationValue version classAttributeMap actorMap actorId = do+  attributes <- getAttributes version classAttributeMap actorMap actorId+  pure (UpdatedReplicationValue attributes)++putUpdatedReplicationValue :: UpdatedReplicationValue -> BinaryBit.BitPut ()+putUpdatedReplicationValue updatedReplicationValue =+  putAttributes (updatedReplicationValueAttributes updatedReplicationValue)
+ library/Rattletrap/Text.hs view
@@ -0,0 +1,87 @@+module Rattletrap.Text where++import Rattletrap.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.Char8 as ByteString+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Encoding++data Text = Text+  { textSize :: Int32+  , textValue :: Text.Text+  } deriving (Eq, Ord, Show)++getText :: Binary.Get Text+getText = do+  rawSize <- getInt32+  let decode = getTextDecoder rawSize+  let size = getTextSize rawSize+  bytes <- Binary.getLazyByteString size+  let text = decode bytes+  pure (Text rawSize text)++putText :: Text -> Binary.Put+putText text = do+  let size = textSize text+  let encode = getTextEncoder size+  putInt32 size+  Binary.putLazyByteString (encode (textValue text))++getTextBits :: BinaryBit.BitGet Text+getTextBits = do+  rawSize <- getInt32Bits+  let decode = getTextDecoder rawSize+  let size = getTextSize rawSize+  bytes <- BinaryBit.getLazyByteString size+  let text = decode (reverseBytes bytes)+  pure (Text rawSize text)++putTextBits :: Text -> BinaryBit.BitPut ()+putTextBits text = do+  let size = textSize text+  let encode = getTextEncoder size+  putInt32Bits size+  BinaryBit.putByteString+    (ByteString.toStrict (reverseBytes (encode (textValue text))))++stringToText :: String -> Text+stringToText string =+  let value = Text.snoc (Text.pack string) '\x00'+      size = Int32 (fromIntegral (Text.length value))+  in Text size value++textToString :: Text -> String+textToString text = Text.unpack (Text.dropWhileEnd (== '\x00') (textValue text))++getTextSize+  :: Integral a+  => Int32 -> a+getTextSize (Int32 size) =+  if size == 0x05000000+    then 8+    else if size < 0+           then (-2 * fromIntegral size)+           else fromIntegral size++getTextDecoder :: Int32 -> ByteString.ByteString -> Text.Text+getTextDecoder (Int32 size) bytes =+  let decode =+        if size < 0+          then Encoding.decodeUtf16LE+          else Encoding.decodeLatin1+  in decode (ByteString.toStrict bytes)++getTextEncoder :: Int32 -> Text.Text -> ByteString.ByteString+getTextEncoder (Int32 size) text =+  if size < 0+    then ByteString.fromStrict (Encoding.encodeUtf16LE text)+    else encodeLatin1 text++encodeLatin1 :: Text.Text -> ByteString.ByteString+encodeLatin1 text = ByteString.pack (Text.unpack text)
+ library/Rattletrap/Utility.hs view
@@ -0,0 +1,33 @@+module Rattletrap.Utility where++import qualified Data.Bits as Bits+import qualified Data.ByteString.Lazy as ByteString+import qualified Data.Word as Word+import qualified Text.Regex as Regex++padBytes+  :: Integral a+  => a -> ByteString.ByteString -> ByteString.ByteString+padBytes size bytes =+  ByteString.concat+    [ bytes+    , ByteString.replicate (fromIntegral size - ByteString.length bytes) 0x00+    ]++replace :: String -> String -> String -> String+replace needle replacement haystack =+  Regex.subRegex (Regex.mkRegex needle) haystack replacement++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/Vector.hs view
@@ -0,0 +1,31 @@+module Rattletrap.Vector where++import Rattletrap.CompressedWord++import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit++data Vector = Vector+  { vectorBitSize :: CompressedWord+  , vectorDx :: CompressedWord+  , vectorDy :: CompressedWord+  , vectorDz :: CompressedWord+  } 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+  pure+    Vector+    {vectorBitSize = bitSize, vectorDx = dx, vectorDy = dy, vectorDz = dz}++putVector :: Vector -> BinaryBit.BitPut ()+putVector vector = do+  putCompressedWord (vectorBitSize vector)+  putCompressedWord (vectorDx vector)+  putCompressedWord (vectorDy vector)+  putCompressedWord (vectorDz vector)
+ library/Rattletrap/Word32.hs view
@@ -0,0 +1,33 @@+module Rattletrap.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 word32) = Binary.putWord32le 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/Word64.hs view
@@ -0,0 +1,33 @@+module Rattletrap.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/Word8.hs view
@@ -0,0 +1,33 @@+module Rattletrap.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 word8) = Binary.putWord8 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))
+ rattletrap.cabal view
@@ -0,0 +1,140 @@+name: rattletrap+version: 0.1.0+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE.markdown+maintainer: Taylor Fausak+synopsis: Parse and generate Rocket League replays.+description:+    Rattletrap parses and generates Rocket League replays.+category: Game+extra-source-files:+    HLint.hs+    README.markdown++library+    exposed-modules:+        Rattletrap+        Rattletrap.ActorMap+        Rattletrap.Attribute+        Rattletrap.AttributeMapping+        Rattletrap.AttributeValue+        Rattletrap.AttributeValue.Boolean+        Rattletrap.AttributeValue.Byte+        Rattletrap.AttributeValue.CamSettings+        Rattletrap.AttributeValue.Demolish+        Rattletrap.AttributeValue.Enum+        Rattletrap.AttributeValue.Explosion+        Rattletrap.AttributeValue.FlaggedInt+        Rattletrap.AttributeValue.Float+        Rattletrap.AttributeValue.GameMode+        Rattletrap.AttributeValue.Int+        Rattletrap.AttributeValue.Loadout+        Rattletrap.AttributeValue.LoadoutOnline+        Rattletrap.AttributeValue.Loadouts+        Rattletrap.AttributeValue.LoadoutsOnline+        Rattletrap.AttributeValue.Location+        Rattletrap.AttributeValue.MusicStinger+        Rattletrap.AttributeValue.PartyLeader+        Rattletrap.AttributeValue.Pickup+        Rattletrap.AttributeValue.PrivateMatchSettings+        Rattletrap.AttributeValue.QWord+        Rattletrap.AttributeValue.Reservation+        Rattletrap.AttributeValue.RigidBodyState+        Rattletrap.AttributeValue.String+        Rattletrap.AttributeValue.TeamPaint+        Rattletrap.AttributeValue.UniqueId+        Rattletrap.AttributeValue.WeldedInfo+        Rattletrap.AttributeValueType+        Rattletrap.Cache+        Rattletrap.ClassAttributeMap+        Rattletrap.ClassMapping+        Rattletrap.CompressedWord+        Rattletrap.CompressedWordVector+        Rattletrap.Content+        Rattletrap.Crc+        Rattletrap.Data+        Rattletrap.Dictionary+        Rattletrap.Float32+        Rattletrap.Frame+        Rattletrap.Header+        Rattletrap.Initialization+        Rattletrap.Int32+        Rattletrap.Int8+        Rattletrap.Int8Vector+        Rattletrap.KeyFrame+        Rattletrap.List+        Rattletrap.Mark+        Rattletrap.Message+        Rattletrap.Property+        Rattletrap.PropertyValue+        Rattletrap.RemoteId+        Rattletrap.Replay+        Rattletrap.Replication+        Rattletrap.ReplicationValue+        Rattletrap.ReplicationValue.Destroyed+        Rattletrap.ReplicationValue.Spawned+        Rattletrap.ReplicationValue.Updated+        Rattletrap.Text+        Rattletrap.Utility+        Rattletrap.Vector+        Rattletrap.Word32+        Rattletrap.Word64+        Rattletrap.Word8+    build-depends:+        base >=4.9.0.0 && <4.10,+        bimap >=0.3.2 && <0.4,+        binary >=0.8.3.0 && <0.9,+        binary-bits ==0.5.*,+        bytestring >=0.10.8.1 && <0.11,+        containers >=0.5.7.1 && <0.6,+        data-binary-ieee754 >=0.4.4 && <0.5,+        regex-compat >=0.95.1 && <0.96,+        text >=1.2.2.1 && <1.3,+        vector >=0.11.0.0 && <0.12+    default-language: Haskell2010+    hs-source-dirs: library+    ghc-options: -Wall++executable rattletrap+    main-is: Main.hs+    build-depends:+        aeson >=0.11.2.1 && <0.12,+        aeson-casing >=0.1.0.5 && <0.2,+        base >=4.9.0.0 && <4.10,+        binary >=0.8.3.0 && <0.9,+        bytestring >=0.10.8.1 && <0.11,+        rattletrap >=0.1.0 && <0.2,+        template-haskell >=2.11.0.0 && <2.12+    default-language: Haskell2010+    hs-source-dirs: executable+    ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N++test-suite lint+    type: exitcode-stdio-1.0+    main-is: Lint.hs+    build-depends:+        base >=4.9.0.0 && <4.10,+        hlint >=1.9.35 && <1.10+    default-language: Haskell2010+    hs-source-dirs: test+    other-modules:+        Test+    ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N+test-suite test+    type: exitcode-stdio-1.0+    main-is: Test.hs+    build-depends:+        base >=4.9.0.0 && <4.10,+        binary >=0.8.3.0 && <0.9,+        bytestring >=0.10.8.1 && <0.11,+        filepath >=1.4.1.0 && <1.5,+        rattletrap >=0.1.0 && <0.2,+        tasty >=0.11.0.4 && <0.12,+        tasty-hspec >=1.1.3 && <1.2+    default-language: Haskell2010+    hs-source-dirs: test+    other-modules:+        Lint+    ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N
+ test/Lint.hs view
@@ -0,0 +1,13 @@+module Main+  ( main+  ) where++import qualified Language.Haskell.HLint3 as HLint+import qualified System.Exit as Exit++main :: IO ()+main = do+  ideas <- HLint.hlint ["lint", "--cross", "."]+  if null ideas+    then Exit.exitSuccess+    else Exit.exitFailure
+ test/Test.hs view
@@ -0,0 +1,88 @@+module Main+  ( main+  ) where++import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as ByteString+import qualified Rattletrap+import qualified System.FilePath as FilePath+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.Hspec as Hspec++main :: IO ()+main = do+  tests <- Hspec.testSpec "rattletrap" (Hspec.parallel spec)+  Tasty.defaultMain tests++spec :: Hspec.Spec+spec = Hspec.describe "Rattletrap" (mapM_ (uncurry itCanGetAndPut) replays)++itCanGetAndPut :: String -> String -> Hspec.Spec+itCanGetAndPut uuid description =+  Hspec.it+    (uuid ++ ": a replay with " ++ description)+    (do let file = pathToReplay uuid+        (input, _, output) <- getAndPut file+        Hspec.shouldBe (output == input) True)++pathToReplay :: String -> FilePath+pathToReplay uuid =+  FilePath.joinPath ["test", "replays", FilePath.addExtension uuid ".replay"]++getAndPut :: FilePath+          -> IO (ByteString.ByteString, Rattletrap.Replay, ByteString.ByteString)+getAndPut file = do+  input <- ByteString.readFile file+  let replay = Binary.runGet Rattletrap.getReplay input+  let output = Binary.runPut (Rattletrap.putReplay replay)+  pure (input, replay, output)++replays :: [(String, String)]+replays =+  [ ("00080014003600090000036E0F65CCEB", "a flip time")+  , ("07E925B1423653D44CB8B4B2524792C1", "a game mode before Neo Tokyo")+  , ("1205D96C4D819800927791820096CD49", "rumble mode")+  , ("160CA83E41083BFD8E6315B4BFCA0561", "a dedicated server IP")+  , ("18D6738D415B70B5BE4C299588D3C141", "an online loadout attribute")+  , ("1A126AC24CAA0DB0E98835BD960B8AF8", "overtime")+  , ("1BC2D01444ACE577D01E988EADD4DFD0", "no padding after the frames")+  , ("1D1DE97D4941C86E43FE0093563DB621", "a camera pitch")+  , ("1EF90FCC4F719F606A5327B3CDD782A4", "a private hoops match")+  , ("211466D04B983F5A33CC2FA1D5928672", "a match save")+  , ("22BACD794ABE7B92E50E9CBDBD9C59CE", "a vote to forfeit")+  , ("29F582C34A65EB34D358A784CBE3C189", "frames")+  , ("2F817C8C44859C762980AE85B1862A31", "splitscreen players")+  , ("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")+  , ("52AA67F94090C19D33C5009E54D31FE4", "a match-ending attribute")+  , ("540DA764423C8FB24EB9D486D982F16F", "a demolish attribute")+  , ("551CA4D44FF2B86015DE44A6B5790D4C", "private match settings")+  , ("6688EEE34BFEB3EC3A9E3283098CC712", "a malformed byte property")+  , ("6D1B06D844A5BB91B81FD4B5B28F08BA", "a flip right")+  , ("7109EB9846D303E54B7ACBA792036213", "a boost modifier")+  , ("7BF6073F4614CE0A438994B9A260DA6A", "an online loadouts attribute")+  , ("89CBA30E46FA5385BDD35DA4285D4D2E", "remote user data")+  , ("A128B3AB45D5A18E3EF9CF93C9576BCE", "a round count down")+  , ("A52F804845573D8DA65E97BF59026A43", "some more mutators")+  , ("A6711CE74272B2E663DCC9A200A218E3", "a waiting player")+  , ("A7F001A1417A19BFA8C90990D8F7C2FF", "a ready attribute")+  -- , ("B82DDB624C393A4A425E68AB40DC2450", "TODO")+  , ("B9F9B87D4A9D0A3D25D4EC91C0401DE2", "a party leader")+  , ("C14F7E0E4D9B5E6BE9AD5D8ED56B174C", "some mutators")+  , ("C375E0EC4971B506C51678B465D35AE9", "some UTF-16 text")+  , ("C80A9959479CFE51673B3A889D717554", "some Latin-1 text")+  , ("C8372B1345B1803DEF039F815DBD802D", "a spectator")+  , ("CB5B422A4424262B2877E9A121329EF2", "a player using behind view")+  , ("D428F81646A98C25902CE988AE5C14C8", "a private hockey match")+  , ("D7FB197A451D69075A0C99A2F49A4053", "an explosion attribute")+  , ("DCB3A6B94A9DBE46FDE5EAA9B012F6C8", "a pawn type attribute")+  , ("EAE311E84BA35B590A6FDBA6DD4F2FEB", "an actor/object ID collision")+  , ("F299F176491554B11E34AB91CA76B2CE", "a location attribute")+  , ("F811C1D24888015E23B598AD8628C742", "no frames")+  , ("FDC79DA84DD463D4BCCE6B892829AC88", "an MVP")+  ]