diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -11,10 +11,9 @@
 can be used to modify replays in order to force everyone into the same car or
 change the map a game was played on.
 
-Rattletrap supports every version of Rocket League up to [1.66][], also known
-as the "season 12 update". If a replay can be played by the Rocket League
-client, it can be parsed by Rattletrap. (If not, that's a bug. Please report
-it!)
+Rattletrap supports every version of Rocket League up to [1.68][], which was
+released on 2019-10-14.  If a replay can be played by the Rocket League client,
+it can be parsed by Rattletrap. (If not, that's a bug. Please report it!)
 
 Rattletrap is a command-line application. You should only use it if you're
 comfortable running things in terminals or command prompts. Otherwise consider
@@ -135,7 +134,7 @@
 [Docker build badge]: https://img.shields.io/docker/build/taylorfausak/rattletrap.svg?label=docker&logo=docker&logoColor=white
 [docker build]: https://hub.docker.com/r/taylorfausak/rattletrap
 [Rocket League]: https://www.rocketleague.com
-[1.66]: https://www.rocketleague.com/news/patch-notes-v1-66-season-12-update-/
+[1.68]: https://www.rocketleague.com/news/patch-notes-v1-68-october-update/
 [Ball Chasing]: https://ballchasing.com
 [Haskell]: https://www.haskell.org
 [C# parser]: https://github.com/jjbott/RocketLeagueReplayParser
diff --git a/executables/Main.hs b/executables/Main.hs
deleted file mode 100644
--- a/executables/Main.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Main
-  ( main
-  )
-where
-
-import Rattletrap (main)
diff --git a/library/Rattletrap.hs b/library/Rattletrap.hs
deleted file mode 100644
--- a/library/Rattletrap.hs
+++ /dev/null
@@ -1,162 +0,0 @@
-module Rattletrap
-  ( Rattletrap.Console.Main.main
-  , Rattletrap.Console.Main.rattletrap
-  , Rattletrap.Utility.Helper.decodeReplayFile
-  , Rattletrap.Utility.Helper.encodeReplayJson
-  , Rattletrap.Utility.Helper.decodeReplayJson
-  , Rattletrap.Utility.Helper.encodeReplayFile
-  , Rattletrap.Type.AppliedDamageAttribute.AppliedDamageAttribute(..)
-  , Rattletrap.Type.Attribute.Attribute(..)
-  , Rattletrap.Type.AttributeMapping.AttributeMapping(..)
-  , Rattletrap.Type.AttributeType.AttributeType(..)
-  , Rattletrap.Type.AttributeValue.AttributeValue(..)
-  , Rattletrap.Type.BooleanAttribute.BooleanAttribute(..)
-  , Rattletrap.Type.ByteAttribute.ByteAttribute(..)
-  , Rattletrap.Type.Cache.Cache(..)
-  , Rattletrap.Type.CamSettingsAttribute.CamSettingsAttribute(..)
-  , Rattletrap.Type.ClassAttributeMap.ClassAttributeMap(..)
-  , Rattletrap.Type.ClassMapping.ClassMapping(..)
-  , Rattletrap.Type.ClubColorsAttribute.ClubColorsAttribute(..)
-  , Rattletrap.Type.CompressedWord.CompressedWord(..)
-  , Rattletrap.Type.CompressedWordVector.CompressedWordVector(..)
-  , Rattletrap.Type.Content.Content(..)
-  , Rattletrap.Type.DamageStateAttribute.DamageStateAttribute(..)
-  , Rattletrap.Type.DemolishAttribute.DemolishAttribute(..)
-  , Rattletrap.Type.DestroyedReplication.DestroyedReplication(..)
-  , Rattletrap.Type.Dictionary.Dictionary(..)
-  , Rattletrap.Type.EnumAttribute.EnumAttribute(..)
-  , Rattletrap.Type.ExplosionAttribute.ExplosionAttribute(..)
-  , Rattletrap.Type.ExtendedExplosionAttribute.ExtendedExplosionAttribute(..)
-  , Rattletrap.Type.FlaggedIntAttribute.FlaggedIntAttribute(..)
-  , Rattletrap.Type.Float32le.Float32le(..)
-  , Rattletrap.Type.FloatAttribute.FloatAttribute(..)
-  , Rattletrap.Type.Frame.Frame(..)
-  , Rattletrap.Type.GameModeAttribute.GameModeAttribute(..)
-  , Rattletrap.Type.Header.Header(..)
-  , Rattletrap.Type.Initialization.Initialization(..)
-  , Rattletrap.Type.Int32le.Int32le(..)
-  , Rattletrap.Type.Int64Attribute.Int64Attribute(..)
-  , Rattletrap.Type.Int64le.Int64le(..)
-  , Rattletrap.Type.Int8le.Int8le(..)
-  , Rattletrap.Type.Int8Vector.Int8Vector(..)
-  , Rattletrap.Type.IntAttribute.IntAttribute(..)
-  , Rattletrap.Type.KeyFrame.KeyFrame(..)
-  , Rattletrap.Type.List.List(..)
-  , Rattletrap.Type.LoadoutAttribute.LoadoutAttribute(..)
-  , Rattletrap.Type.LoadoutOnlineAttribute.LoadoutOnlineAttribute(..)
-  , Rattletrap.Type.LoadoutsAttribute.LoadoutsAttribute(..)
-  , Rattletrap.Type.LoadoutsOnlineAttribute.LoadoutsOnlineAttribute(..)
-  , Rattletrap.Type.LocationAttribute.LocationAttribute(..)
-  , Rattletrap.Type.Mark.Mark(..)
-  , Rattletrap.Type.Message.Message(..)
-  , Rattletrap.Type.MusicStingerAttribute.MusicStingerAttribute(..)
-  , Rattletrap.Type.PartyLeaderAttribute.PartyLeaderAttribute(..)
-  , Rattletrap.Type.PickupAttribute.PickupAttribute(..)
-  , Rattletrap.Type.PlayerHistoryKeyAttribute.PlayerHistoryKeyAttribute(..)
-  , Rattletrap.Type.PrivateMatchSettingsAttribute.PrivateMatchSettingsAttribute(..)
-  , Rattletrap.Type.ProductAttribute.ProductAttribute(..)
-  , Rattletrap.Type.Property.Property(..)
-  , Rattletrap.Type.PropertyValue.PropertyValue(..)
-  , Rattletrap.Type.Quaternion.Quaternion(..)
-  , Rattletrap.Type.QWordAttribute.QWordAttribute(..)
-  , Rattletrap.Type.RemoteId.RemoteId(..)
-  , Rattletrap.Type.Replay.Replay(..)
-  , Rattletrap.Type.Replication.Replication(..)
-  , Rattletrap.Type.ReplicationValue.ReplicationValue(..)
-  , Rattletrap.Type.ReservationAttribute.ReservationAttribute(..)
-  , Rattletrap.Type.RigidBodyStateAttribute.RigidBodyStateAttribute(..)
-  , Rattletrap.Type.Rotation.Rotation(..)
-  , Rattletrap.Type.Section.Section(..)
-  , Rattletrap.Type.SpawnedReplication.SpawnedReplication(..)
-  , Rattletrap.Type.StatEventAttribute.StatEventAttribute(..)
-  , Rattletrap.Type.Str.Str(..)
-  , Rattletrap.Type.StringAttribute.StringAttribute(..)
-  , Rattletrap.Type.TeamPaintAttribute.TeamPaintAttribute(..)
-  , Rattletrap.Type.TitleAttribute.TitleAttribute(..)
-  , Rattletrap.Type.UniqueIdAttribute.UniqueIdAttribute(..)
-  , Rattletrap.Type.UpdatedReplication.UpdatedReplication(..)
-  , Rattletrap.Type.Vector.Vector(..)
-  , Rattletrap.Type.WeldedInfoAttribute.WeldedInfoAttribute(..)
-  , Rattletrap.Type.Word32le.Word32le(..)
-  , Rattletrap.Type.Word64le.Word64le(..)
-  , Rattletrap.Type.Word8le.Word8le(..)
-  )
-where
-
-import qualified Rattletrap.Console.Main
-import qualified Rattletrap.Type.AppliedDamageAttribute
-import qualified Rattletrap.Type.Attribute
-import qualified Rattletrap.Type.AttributeMapping
-import qualified Rattletrap.Type.AttributeType
-import qualified Rattletrap.Type.AttributeValue
-import qualified Rattletrap.Type.BooleanAttribute
-import qualified Rattletrap.Type.ByteAttribute
-import qualified Rattletrap.Type.Cache
-import qualified Rattletrap.Type.CamSettingsAttribute
-import qualified Rattletrap.Type.ClassAttributeMap
-import qualified Rattletrap.Type.ClassMapping
-import qualified Rattletrap.Type.ClubColorsAttribute
-import qualified Rattletrap.Type.CompressedWord
-import qualified Rattletrap.Type.CompressedWordVector
-import qualified Rattletrap.Type.Content
-import qualified Rattletrap.Type.DamageStateAttribute
-import qualified Rattletrap.Type.DemolishAttribute
-import qualified Rattletrap.Type.DestroyedReplication
-import qualified Rattletrap.Type.Dictionary
-import qualified Rattletrap.Type.EnumAttribute
-import qualified Rattletrap.Type.ExplosionAttribute
-import qualified Rattletrap.Type.ExtendedExplosionAttribute
-import qualified Rattletrap.Type.FlaggedIntAttribute
-import qualified Rattletrap.Type.Float32le
-import qualified Rattletrap.Type.FloatAttribute
-import qualified Rattletrap.Type.Frame
-import qualified Rattletrap.Type.GameModeAttribute
-import qualified Rattletrap.Type.Header
-import qualified Rattletrap.Type.Initialization
-import qualified Rattletrap.Type.Int32le
-import qualified Rattletrap.Type.Int64Attribute
-import qualified Rattletrap.Type.Int64le
-import qualified Rattletrap.Type.Int8le
-import qualified Rattletrap.Type.Int8Vector
-import qualified Rattletrap.Type.IntAttribute
-import qualified Rattletrap.Type.KeyFrame
-import qualified Rattletrap.Type.List
-import qualified Rattletrap.Type.LoadoutAttribute
-import qualified Rattletrap.Type.LoadoutOnlineAttribute
-import qualified Rattletrap.Type.LoadoutsAttribute
-import qualified Rattletrap.Type.LoadoutsOnlineAttribute
-import qualified Rattletrap.Type.LocationAttribute
-import qualified Rattletrap.Type.Mark
-import qualified Rattletrap.Type.Message
-import qualified Rattletrap.Type.MusicStingerAttribute
-import qualified Rattletrap.Type.PartyLeaderAttribute
-import qualified Rattletrap.Type.PickupAttribute
-import qualified Rattletrap.Type.PlayerHistoryKeyAttribute
-import qualified Rattletrap.Type.PrivateMatchSettingsAttribute
-import qualified Rattletrap.Type.ProductAttribute
-import qualified Rattletrap.Type.Property
-import qualified Rattletrap.Type.PropertyValue
-import qualified Rattletrap.Type.Quaternion
-import qualified Rattletrap.Type.QWordAttribute
-import qualified Rattletrap.Type.RemoteId
-import qualified Rattletrap.Type.Replay
-import qualified Rattletrap.Type.Replication
-import qualified Rattletrap.Type.ReplicationValue
-import qualified Rattletrap.Type.ReservationAttribute
-import qualified Rattletrap.Type.RigidBodyStateAttribute
-import qualified Rattletrap.Type.Rotation
-import qualified Rattletrap.Type.Section
-import qualified Rattletrap.Type.SpawnedReplication
-import qualified Rattletrap.Type.StatEventAttribute
-import qualified Rattletrap.Type.Str
-import qualified Rattletrap.Type.StringAttribute
-import qualified Rattletrap.Type.TeamPaintAttribute
-import qualified Rattletrap.Type.TitleAttribute
-import qualified Rattletrap.Type.UniqueIdAttribute
-import qualified Rattletrap.Type.UpdatedReplication
-import qualified Rattletrap.Type.Vector
-import qualified Rattletrap.Type.WeldedInfoAttribute
-import qualified Rattletrap.Type.Word32le
-import qualified Rattletrap.Type.Word64le
-import qualified Rattletrap.Type.Word8le
-import qualified Rattletrap.Utility.Helper
diff --git a/library/Rattletrap/Console/Main.hs b/library/Rattletrap/Console/Main.hs
deleted file mode 100644
--- a/library/Rattletrap/Console/Main.hs
+++ /dev/null
@@ -1,240 +0,0 @@
-module Rattletrap.Console.Main
-  ( main
-  , rattletrap
-  )
-where
-
-import qualified Control.Monad as Monad
-import qualified Data.Aeson as Json
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Lazy as LazyBytes
-import qualified Data.Version as Version
-import qualified Network.HTTP.Client as Client
-import qualified Network.HTTP.Client.TLS as Client
-import qualified Paths_rattletrap as This
-import qualified Rattletrap.Type.Replay as Rattletrap
-import qualified Rattletrap.Utility.Helper as Rattletrap
-import qualified System.Console.GetOpt as Console
-import qualified System.Environment as Environment
-import qualified System.Exit as Exit
-import qualified System.FilePath as Path
-import qualified System.IO as IO
-import qualified Text.Printf as Printf
-
-main :: IO ()
-main = do
-  name <- Environment.getProgName
-  arguments <- Environment.getArgs
-  rattletrap name arguments
-
-rattletrap :: String -> [String] -> IO ()
-rattletrap name arguments = do
-  config <- getConfig arguments
-  Monad.when (configHelp config) (printHelp name *> Exit.exitFailure)
-  Monad.when (configVersion config) (printVersion *> Exit.exitFailure)
-  input <- getInput config
-  let decode = getDecoder config
-  replay <- either fail pure (decode input)
-  let encode = getEncoder config
-  putOutput config (encode replay)
-
-getDecoder :: Config -> Bytes.ByteString -> Either String Rattletrap.Replay
-getDecoder config = case getMode config of
-  ModeDecode -> Rattletrap.decodeReplayFile $ configFast config
-  ModeEncode -> Rattletrap.decodeReplayJson
-
-getEncoder :: Config -> Rattletrap.Replay -> Bytes.ByteString
-getEncoder config = case getMode config of
-  ModeDecode -> if configCompact config
-    then LazyBytes.toStrict . Json.encode
-    else Rattletrap.encodeReplayJson
-  ModeEncode -> Rattletrap.encodeReplayFile $ configFast config
-
-getInput :: Config -> IO Bytes.ByteString
-getInput config = case configInput config of
-  Nothing -> Bytes.getContents
-  Just fileOrUrl -> case Client.parseUrlThrow fileOrUrl of
-    Nothing -> Bytes.readFile fileOrUrl
-    Just request -> do
-      manager <- Client.newTlsManager
-      response <- Client.httpLbs request manager
-      pure (LazyBytes.toStrict (Client.responseBody response))
-
-putOutput :: Config -> Bytes.ByteString -> IO ()
-putOutput config = case configOutput config of
-  Nothing -> Bytes.putStr
-  Just file -> Bytes.writeFile file
-
-getConfig :: [String] -> IO Config
-getConfig arguments = do
-  let
-    (updates, unexpectedArguments, unknownOptions, problems) =
-      Console.getOpt' Console.Permute options arguments
-  printUnexpectedArguments unexpectedArguments
-  printUnknownOptions unknownOptions
-  printProblems problems
-  Monad.unless (null problems) Exit.exitFailure
-  either fail pure (Monad.foldM applyUpdate defaultConfig updates)
-
-type Option = Console.OptDescr Update
-
-type Update = Config -> Either String Config
-
-options :: [Option]
-options =
-  [ compactOption
-  , fastOption
-  , helpOption
-  , inputOption
-  , modeOption
-  , outputOption
-  , versionOption
-  ]
-
-compactOption :: Option
-compactOption = Console.Option
-  ['c']
-  ["compact"]
-  (Console.NoArg (\config -> pure config { configCompact = True }))
-  "minify JSON output"
-
-fastOption :: Option
-fastOption = Console.Option
-  ['f']
-  ["fast"]
-  (Console.NoArg (\config -> pure config { configFast = True }))
-  "only encode or decode the header"
-
-helpOption :: Option
-helpOption = Console.Option
-  ['h']
-  ["help"]
-  (Console.NoArg (\config -> pure config { configHelp = True }))
-  "show the help"
-
-inputOption :: Option
-inputOption = Console.Option
-  ['i']
-  ["input"]
-  (Console.ReqArg
-    (\input config -> pure config { configInput = Just input })
-    "FILE|URL"
-  )
-  "input file or URL"
-
-modeOption :: Option
-modeOption = Console.Option
-  ['m']
-  ["mode"]
-  (Console.ReqArg
-    (\rawMode config -> do
-      mode <- parseMode rawMode
-      pure config { configMode = Just mode }
-    )
-    "MODE"
-  )
-  "decode or encode"
-
-outputOption :: Option
-outputOption = Console.Option
-  ['o']
-  ["output"]
-  (Console.ReqArg
-    (\output config -> pure config { configOutput = Just output })
-    "FILE"
-  )
-  "output file"
-
-versionOption :: Option
-versionOption = Console.Option
-  ['v']
-  ["version"]
-  (Console.NoArg (\config -> pure config { configVersion = True }))
-  "show the version"
-
-applyUpdate :: Config -> Update -> Either String Config
-applyUpdate config update = update config
-
-data Config = Config
-  { configCompact :: Bool
-  , configFast :: Bool
-  , configHelp :: Bool
-  , configInput :: Maybe String
-  , configMode :: Maybe Mode
-  , configOutput :: Maybe String
-  , configVersion :: Bool
-  } deriving (Show)
-
-defaultConfig :: Config
-defaultConfig = Config
-  { configCompact = False
-  , configFast = False
-  , configHelp = False
-  , configInput = Nothing
-  , configMode = Nothing
-  , configOutput = Nothing
-  , configVersion = False
-  }
-
-getMode :: Config -> Mode
-getMode config = case getExtension (configInput config) of
-  ".json" -> ModeEncode
-  ".replay" -> ModeDecode
-  _ -> case getExtension (configOutput config) of
-    ".json" -> ModeDecode
-    ".replay" -> ModeEncode
-    _ -> ModeDecode
-
-getExtension :: Maybe String -> String
-getExtension = maybe "" Path.takeExtension
-
-data Mode
-  = ModeDecode
-  | ModeEncode
-  deriving (Show)
-
-parseMode :: String -> Either String Mode
-parseMode mode = case mode of
-  "decode" -> Right ModeDecode
-  "encode" -> Right ModeEncode
-  _ -> Left (Printf.printf "invalid mode: %s" (show mode))
-
-printUnexpectedArguments :: [String] -> IO ()
-printUnexpectedArguments = mapM_ printUnexpectedArgument
-
-printUnexpectedArgument :: String -> IO ()
-printUnexpectedArgument =
-  warnLn . Printf.printf "WARNING: unexpected argument `%s'"
-
-printUnknownOptions :: [String] -> IO ()
-printUnknownOptions = mapM_ printUnknownOption
-
-printUnknownOption :: String -> IO ()
-printUnknownOption = warnLn . Printf.printf "WARNING: unknown option `%s'"
-
-printProblems :: [String] -> IO ()
-printProblems = mapM_ printProblem
-
-printProblem :: String -> IO ()
-printProblem = warn . Printf.printf "ERROR: %s"
-
-printHelp :: String -> IO ()
-printHelp = warn . help
-
-help :: String -> String
-help name = Console.usageInfo (header name) options
-
-header :: String -> String
-header name = unwords [name, "version", version]
-
-version :: String
-version = Version.showVersion This.version
-
-printVersion :: IO ()
-printVersion = warnLn version
-
-warn :: String -> IO ()
-warn = IO.hPutStr IO.stderr
-
-warnLn :: String -> IO ()
-warnLn = IO.hPutStrLn IO.stderr
diff --git a/library/Rattletrap/Data.hs b/library/Rattletrap/Data.hs
deleted file mode 100644
--- a/library/Rattletrap/Data.hs
+++ /dev/null
@@ -1,642 +0,0 @@
--- brittany --columns 120
-module Rattletrap.Data
-  ( rawParentClasses
-  , rawClassesWithLocation
-  , rawClassesWithRotation
-  , rawObjectClasses
-  , rawAttributeTypes
-  , rawCrc32Table
-  )
-where
-
-import Rattletrap.Type.AttributeType
-
-rawParentClasses :: [(String, String)]
-rawParentClasses =
-  [ ("Engine.Actor", "Core.Object")
-  , ("Engine.GameReplicationInfo", "Engine.ReplicationInfo")
-  , ("Engine.Info", "Engine.Actor")
-  , ("Engine.Pawn", "Engine.Actor")
-  , ("Engine.PlayerReplicationInfo", "Engine.ReplicationInfo")
-  , ("Engine.ReplicationInfo", "Engine.Info")
-  , ("Engine.TeamInfo", "Engine.ReplicationInfo")
-  , ("ProjectX.GRI_X", "Engine.GameReplicationInfo")
-  , ("ProjectX.Pawn_X", "Engine.Pawn")
-  , ("ProjectX.PRI_X", "Engine.PlayerReplicationInfo")
-  , ("TAGame.Ball_TA", "TAGame.RBActor_TA")
-  , ("TAGame.CameraSettingsActor_TA", "Engine.ReplicationInfo")
-  , ("TAGame.Car_Season_TA", "TAGame.PRI_TA")
-  , ("TAGame.Car_TA", "TAGame.Vehicle_TA")
-  , ("TAGame.CarComponent_Boost_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.CarComponent_Dodge_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.CarComponent_DoubleJump_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.CarComponent_FlipCar_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.CarComponent_Jump_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.CarComponent_TA", "Engine.ReplicationInfo")
-  , ("TAGame.CrowdActor_TA", "Engine.ReplicationInfo")
-  , ("TAGame.CrowdManager_TA", "Engine.ReplicationInfo")
-  , ("TAGame.GameEvent_Season_TA", "TAGame.GameEvent_Soccar_TA")
-  , ("TAGame.GameEvent_Soccar_TA", "TAGame.GameEvent_Team_TA")
-  , ("TAGame.GameEvent_SoccarPrivate_TA", "TAGame.GameEvent_Soccar_TA")
-  , ("TAGame.GameEvent_SoccarSplitscreen_TA", "TAGame.GameEvent_SoccarPrivate_TA")
-  , ("TAGame.GameEvent_TA", "Engine.ReplicationInfo")
-  , ("TAGame.GameEvent_Team_TA", "TAGame.GameEvent_TA")
-  , ("TAGame.GRI_TA", "ProjectX.GRI_X")
-  , ("TAGame.InMapScoreboard_TA", "Engine.Actor")
-  , ("TAGame.PRI_TA", "ProjectX.PRI_X")
-  , ("TAGame.RBActor_TA", "ProjectX.Pawn_X")
-  , ("TAGame.SpecialPickup_BallCarSpring_TA", "TAGame.SpecialPickup_Spring_TA")
-  , ("TAGame.SpecialPickup_BallFreeze_TA", "TAGame.SpecialPickup_Targeted_TA")
-  , ("TAGame.SpecialPickup_BallGravity_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.SpecialPickup_BallLasso_TA", "TAGame.SpecialPickup_GrapplingHook_TA")
-  , ("TAGame.SpecialPickup_BallVelcro_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.SpecialPickup_Batarang_TA", "TAGame.SpecialPickup_BallLasso_TA")
-  , ("TAGame.SpecialPickup_BoostOverride_TA", "TAGame.SpecialPickup_Targeted_TA")
-  , ("TAGame.SpecialPickup_GrapplingHook_TA", "TAGame.SpecialPickup_Targeted_TA")
-  , ("TAGame.SpecialPickup_HitForce_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.SpecialPickup_Spring_TA", "TAGame.SpecialPickup_Targeted_TA")
-  , ("TAGame.SpecialPickup_Swapper_TA", "TAGame.SpecialPickup_Targeted_TA")
-  , ("TAGame.SpecialPickup_TA", "TAGame.CarComponent_TA")
-  , ("TAGame.SpecialPickup_Targeted_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.SpecialPickup_Tornado_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.SpecialPickup_HauntedBallBeam_TA", "TAGame.SpecialPickup_TA")
-  , ("TAGame.Team_Soccar_TA", "TAGame.Team_TA")
-  , ("TAGame.Team_TA", "Engine.TeamInfo")
-  , ("TAGame.Vehicle_TA", "TAGame.RBActor_TA")
-  , ("TAGame.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_TA")
-  , ("TAGame.VehiclePickup_TA", "Engine.ReplicationInfo")
-  ]
-
-rawClassesWithLocation :: [String]
-rawClassesWithLocation =
-  [ "TAGame.Ball_Breakout_TA"
-  , "Archetypes.Ball.Ball_Breakout"
-  , "TAGame.Ball_TA"
-  , "Archetypes.Ball.Ball_BasketBall_Mutator"
-  , "Archetypes.Ball.Ball_BasketBall"
-  , "Archetypes.Ball.Ball_Basketball"
-  , "Archetypes.Ball.Ball_Default"
-  , "Archetypes.Ball.Ball_Puck"
-  , "Archetypes.Ball.CubeBall"
-  , "Archetypes.Ball.Ball_Haunted"
-  , "TAGame.Ball_Haunted_TA"
-  , "TAGame.Car_Season_TA"
-  , "TAGame.Car_TA"
-  , "Archetypes.Car.Car_Default"
-  , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
-  , "Archetypes.SpecialPickups.SpecialPickup_HauntedBallBeam"
-  , "Archetypes.SpecialPickups.SpecialPickup_Rugby"
-  , "TAGame.CameraSettingsActor_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_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"
-  , "TAGame.Default__CameraSettingsActor_TA"
-  , "TAGame.Default__PRI_TA"
-  , "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"
-  , "TheWorld:PersistentLevel.CrowdActor_TA"
-  , "TheWorld:PersistentLevel.CrowdManager_TA"
-  , "TheWorld:PersistentLevel.InMapScoreboard_TA"
-  , "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"
-  , "TAGame.HauntedBallTrapTrigger_TA"
-  , "ProjectX.NetModeReplicator"
-  ]
-
-rawClassesWithRotation :: [String]
-rawClassesWithRotation =
-  [ "TAGame.Ball_Breakout_TA"
-  , "Archetypes.Ball.Ball_Breakout"
-  , "TAGame.Ball_TA"
-  , "Archetypes.Ball.Ball_BasketBall_Mutator"
-  , "Archetypes.Ball.Ball_BasketBall"
-  , "Archetypes.Ball.Ball_Basketball"
-  , "Archetypes.Ball.Ball_Default"
-  , "Archetypes.Ball.Ball_Puck"
-  , "Archetypes.Ball.CubeBall"
-  , "Archetypes.Ball.Ball_Haunted"
-  , "TAGame.Ball_Haunted_TA"
-  , "TAGame.Car_Season_TA"
-  , "TAGame.Car_TA"
-  , "Archetypes.Car.Car_Default"
-  , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
-  , "Archetypes.SpecialPickups.SpecialPickup_HauntedBallBeam"
-  , "Archetypes.SpecialPickups.SpecialPickup_Rugby"
-  -- This comments forces Brittany to use a multi-line layout for this list.
-  ]
-
-rawObjectClasses :: [(String, String)]
-rawObjectClasses =
-  [ ("Archetypes.Ball.Ball_BasketBall_Mutator", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.Ball_Basketball", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.Ball_BasketBall", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.Ball_Beachball", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.Ball_Breakout", "TAGame.Ball_Breakout_TA")
-  , ("Archetypes.Ball.Ball_Default", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.Ball_Haunted", "TAGame.Ball_Haunted_TA")
-  , ("Archetypes.Ball.Ball_Puck", "TAGame.Ball_TA")
-  , ("Archetypes.Ball.CubeBall", "TAGame.Ball_TA")
-  , ("Archetypes.Car.Car_Default", "TAGame.Car_TA")
-  , ("Archetypes.CarComponents.CarComponent_Boost", "TAGame.CarComponent_Boost_TA")
-  , ("Archetypes.CarComponents.CarComponent_Dodge", "TAGame.CarComponent_Dodge_TA")
-  , ("Archetypes.CarComponents.CarComponent_DoubleJump", "TAGame.CarComponent_DoubleJump_TA")
-  , ("Archetypes.CarComponents.CarComponent_FlipCar", "TAGame.CarComponent_FlipCar_TA")
-  , ("Archetypes.CarComponents.CarComponent_Jump", "TAGame.CarComponent_Jump_TA")
-  , ("Archetypes.GameEvent.GameEvent_Basketball", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_BasketballPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
-  , ("Archetypes.GameEvent.GameEvent_BasketballSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
-  , ("Archetypes.GameEvent.GameEvent_Breakout", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_Hockey", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_HockeyPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
-  , ("Archetypes.GameEvent.GameEvent_HockeySplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
-  , ("Archetypes.GameEvent.GameEvent_Items", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_Season", "TAGame.GameEvent_Season_TA")
-  , ("Archetypes.GameEvent.GameEvent_Season:CarArchetype", "TAGame.Car_TA")
-  , ("Archetypes.GameEvent.GameEvent_Soccar", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_SoccarLan", "TAGame.GameEvent_Soccar_TA")
-  , ("Archetypes.GameEvent.GameEvent_SoccarPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
-  , ("Archetypes.GameEvent.GameEvent_SoccarSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BallFreeze", "TAGame.SpecialPickup_BallFreeze_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BallGrapplingHook", "TAGame.SpecialPickup_GrapplingHook_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BallLasso", "TAGame.SpecialPickup_BallLasso_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BallSpring", "TAGame.SpecialPickup_BallCarSpring_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BallVelcro", "TAGame.SpecialPickup_BallVelcro_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_Batarang", "TAGame.SpecialPickup_Batarang_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_BoostOverride", "TAGame.SpecialPickup_BoostOverride_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_CarSpring", "TAGame.SpecialPickup_BallCarSpring_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_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.SpecialPickups.SpecialPickup_HauntedBallBeam", "TAGame.SpecialPickup_HauntedBallBeam_TA")
-  , ("Archetypes.SpecialPickups.SpecialPickup_Rugby", "TAGame.SpecialPickup_Rugby_TA")
-  , ("Archetypes.Teams.Team0", "TAGame.Team_Soccar_TA")
-  , ("Archetypes.Teams.Team1", "TAGame.Team_Soccar_TA")
-  , ("GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("GameInfo_Breakout.GameInfo.GameInfo_Breakout:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("GameInfo_Items.GameInfo.GameInfo_Items:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype", "TAGame.GRI_TA")
-  , ("ProjectX.Default__NetModeReplicator_X", "ProjectX.NetModeReplicator")
-  , ("TAGame.Default__CameraSettingsActor_TA", "TAGame.CameraSettingsActor_TA")
-  , ("TAGame.Default__PRI_TA", "TAGame.PRI_TA")
-  , ("TheWorld:PersistentLevel.BreakOutActor_Platform_TA", "TAGame.BreakOutActor_Platform_TA")
-  , ("TheWorld:PersistentLevel.CrowdActor_TA", "TAGame.CrowdActor_TA")
-  , ("TheWorld:PersistentLevel.CrowdManager_TA", "TAGame.CrowdManager_TA")
-  , ("TheWorld:PersistentLevel.InMapScoreboard_TA", "TAGame.InMapScoreboard_TA")
-  , ("TheWorld:PersistentLevel.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_Boost_TA")
-  , ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_1", "TAGame.HauntedBallTrapTrigger_TA")
-  , ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_0", "TAGame.HauntedBallTrapTrigger_TA")
-  ]
-
-rawAttributeTypes :: [(String, AttributeType)]
-rawAttributeTypes =
-  [ ("Engine.Actor:bBlockActors", AttributeTypeBoolean)
-  , ("Engine.Actor:bCollideActors", AttributeTypeBoolean)
-  , ("Engine.Actor:bHidden", AttributeTypeBoolean)
-  , ("Engine.Actor:DrawScale", AttributeTypeFloat)
-  , ("Engine.Actor:RemoteRole", AttributeTypeEnum)
-  , ("Engine.Actor:Role", AttributeTypeEnum)
-  , ("Engine.GameReplicationInfo:bMatchIsOver", AttributeTypeBoolean)
-  , ("Engine.GameReplicationInfo:GameClass", AttributeTypeFlaggedInt)
-  , ("Engine.GameReplicationInfo:ServerName", AttributeTypeString)
-  , ("Engine.Pawn:PlayerReplicationInfo", AttributeTypeFlaggedInt)
-  , ("Engine.PlayerReplicationInfo:bBot", AttributeTypeBoolean)
-  , ("Engine.PlayerReplicationInfo:bIsSpectator", AttributeTypeBoolean)
-  , ("Engine.PlayerReplicationInfo:bReadyToPlay", AttributeTypeBoolean)
-  , ("Engine.PlayerReplicationInfo:bTimedOut", AttributeTypeBoolean)
-  , ("Engine.PlayerReplicationInfo:bWaitingPlayer", AttributeTypeBoolean)
-  , ("Engine.PlayerReplicationInfo:Ping", AttributeTypeByte)
-  , ("Engine.PlayerReplicationInfo:PlayerID", AttributeTypeInt)
-  , ("Engine.PlayerReplicationInfo:PlayerName", AttributeTypeString)
-  , ("Engine.PlayerReplicationInfo:RemoteUserData", AttributeTypeString)
-  , ("Engine.PlayerReplicationInfo:Score", AttributeTypeInt)
-  , ("Engine.PlayerReplicationInfo:Team", AttributeTypeFlaggedInt)
-  , ("Engine.PlayerReplicationInfo:UniqueId", AttributeTypeUniqueId)
-  , ("Engine.TeamInfo:Score", AttributeTypeInt)
-  , ("ProjectX.GRI_X:bGameStarted", AttributeTypeBoolean)
-  , ("ProjectX.GRI_X:GameServerID", AttributeTypeQWord)
-  , ("ProjectX.GRI_X:MatchGUID", AttributeTypeString)
-  , ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", AttributeTypeInt)
-  , ("ProjectX.GRI_X:ReplicatedGamePlaylist", AttributeTypeInt)
-  , ("ProjectX.GRI_X:Reservations", AttributeTypeReservation)
-  , ("TAGame.Ball_Breakout_TA:AppliedDamage", AttributeTypeAppliedDamage)
-  , ("TAGame.Ball_Breakout_TA:DamageIndex", AttributeTypeInt)
-  , ("TAGame.Ball_Breakout_TA:LastTeamTouch", AttributeTypeByte)
-  , ("TAGame.Ball_TA:GameEvent", AttributeTypeFlaggedInt)
-  , ("TAGame.Ball_TA:HitTeamNum", AttributeTypeByte)
-  , ("TAGame.Ball_TA:ReplicatedAddedCarBounceScale", AttributeTypeFloat)
-  , ("TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale", AttributeTypeFloat)
-  , ("TAGame.Ball_TA:ReplicatedBallScale", AttributeTypeFloat)
-  , ("TAGame.Ball_TA:ReplicatedExplosionData", AttributeTypeExplosion)
-  , ("TAGame.Ball_TA:ReplicatedExplosionDataExtended", AttributeTypeExtendedExplosion)
-  , ("TAGame.Ball_TA:ReplicatedWorldBounceScale", AttributeTypeFloat)
-  , ("TAGame.BreakOutActor_Platform_TA:DamageState", AttributeTypeDamageState)
-  , ("TAGame.CameraSettingsActor_TA:bUsingBehindView", AttributeTypeBoolean)
-  , ("TAGame.CameraSettingsActor_TA:bMouseCameraToggleEnabled", AttributeTypeBoolean)
-  , ("TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera", AttributeTypeBoolean)
-  , ("TAGame.CameraSettingsActor_TA:bUsingSwivel", AttributeTypeBoolean)
-  , ("TAGame.CameraSettingsActor_TA:CameraPitch", AttributeTypeByte)
-  , ("TAGame.CameraSettingsActor_TA:CameraYaw", AttributeTypeByte)
-  , ("TAGame.CameraSettingsActor_TA:PRI", AttributeTypeFlaggedInt)
-  , ("TAGame.CameraSettingsActor_TA:ProfileSettings", AttributeTypeCamSettings)
-  , ("TAGame.Car_TA:AddedBallForceMultiplier", AttributeTypeFloat)
-  , ("TAGame.Car_TA:AddedCarForceMultiplier", AttributeTypeFloat)
-  , ("TAGame.Car_TA:AttachedPickup", AttributeTypeFlaggedInt)
-  , ("TAGame.Car_TA:ClubColors", AttributeTypeClubColors)
-  , ("TAGame.Car_TA:ReplicatedDemolish", AttributeTypeDemolish)
-  , ("TAGame.Car_TA:TeamPaint", AttributeTypeTeamPaint)
-  , ("TAGame.CarComponent_Boost_TA:bNoBoost", AttributeTypeBoolean)
-  , ("TAGame.CarComponent_Boost_TA:BoostModifier", AttributeTypeFloat)
-  , ("TAGame.CarComponent_Boost_TA:bUnlimitedBoost", AttributeTypeBoolean)
-  , ("TAGame.CarComponent_Boost_TA:RechargeDelay", AttributeTypeFloat)
-  , ("TAGame.CarComponent_Boost_TA:RechargeRate", AttributeTypeFloat)
-  , ("TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount", AttributeTypeByte)
-  , ("TAGame.CarComponent_Boost_TA:UnlimitedBoostRefCount", AttributeTypeInt)
-  , ("TAGame.CarComponent_Dodge_TA:DodgeTorque", AttributeTypeLocation)
-  , ("TAGame.CarComponent_FlipCar_TA:bFlipRight", AttributeTypeBoolean)
-  , ("TAGame.CarComponent_FlipCar_TA:FlipCarTime", AttributeTypeFloat)
-  , ("TAGame.CarComponent_TA:ReplicatedActive", AttributeTypeByte)
-  , ("TAGame.CarComponent_TA:ReplicatedActivityTime", AttributeTypeFloat)
-  , ("TAGame.CarComponent_TA:Vehicle", AttributeTypeFlaggedInt)
-  , ("TAGame.CrowdActor_TA:GameEvent", AttributeTypeFlaggedInt)
-  , ("TAGame.CrowdActor_TA:ModifiedNoise", AttributeTypeFloat)
-  , ("TAGame.CrowdActor_TA:ReplicatedCountDownNumber", AttributeTypeInt)
-  , ("TAGame.CrowdActor_TA:ReplicatedOneShotSound", AttributeTypeFlaggedInt)
-  , ("TAGame.CrowdActor_TA:ReplicatedRoundCountDownNumber", AttributeTypeInt)
-  , ("TAGame.CrowdManager_TA:GameEvent", AttributeTypeFlaggedInt)
-  , ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", AttributeTypeFlaggedInt)
-  , ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_Soccar_TA:bClubMatch", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_Soccar_TA:bOverTime", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_Soccar_TA:bUnlimitedTime", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_Soccar_TA:GameTime", AttributeTypeInt)
-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", AttributeTypeMusicStinger)
-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", AttributeTypeByte)
-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedServerPerformanceState", AttributeTypeByte)
-  , ("TAGame.GameEvent_Soccar_TA:ReplicatedStatEvent", AttributeTypeStatEvent)
-  , ("TAGame.GameEvent_Soccar_TA:RoundNum", AttributeTypeInt)
-  , ("TAGame.GameEvent_Soccar_TA:SecondsRemaining", AttributeTypeInt)
-  , ("TAGame.GameEvent_Soccar_TA:SeriesLength", AttributeTypeInt)
-  , ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", AttributeTypeFlaggedInt)
-  , ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", AttributeTypePrivateMatchSettings)
-  , ("TAGame.GameEvent_TA:bCanVoteToForfeit", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_TA:BotSkill", AttributeTypeInt)
-  , ("TAGame.GameEvent_TA:GameMode", AttributeTypeGameMode)
-  , ("TAGame.GameEvent_TA:MatchTypeClass", AttributeTypeFlaggedInt)
-  , ("TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining", AttributeTypeInt)
-  , ("TAGame.GameEvent_TA:ReplicatedRoundCountDownNumber", AttributeTypeInt)
-  , ("TAGame.GameEvent_TA:ReplicatedStateIndex", AttributeTypeByte)
-  , ("TAGame.GameEvent_TA:ReplicatedStateName", AttributeTypeInt)
-  , ("TAGame.GameEvent_Team_TA:bForfeit", AttributeTypeBoolean)
-  , ("TAGame.GameEvent_Team_TA:MaxTeamSize", AttributeTypeInt)
-  , ("TAGame.GRI_TA:NewDedicatedServerIP", AttributeTypeString)
-  , ("TAGame.PRI_TA:bIsInSplitScreen", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bMatchMVP", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bOnlineLoadoutSet", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bOnlineLoadoutsSet", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:BotProductName", AttributeTypeInt)
-  , ("TAGame.PRI_TA:bReady", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bUsingBehindView", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bUsingItems", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:bUsingSecondaryCamera", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:CameraPitch", AttributeTypeByte)
-  , ("TAGame.PRI_TA:CameraSettings", AttributeTypeCamSettings)
-  , ("TAGame.PRI_TA:CameraYaw", AttributeTypeByte)
-  , ("TAGame.PRI_TA:ClientLoadout", AttributeTypeLoadout)
-  , ("TAGame.PRI_TA:ClientLoadoutOnline", AttributeTypeLoadoutOnline)
-  , ("TAGame.PRI_TA:ClientLoadouts", AttributeTypeLoadouts)
-  , ("TAGame.PRI_TA:ClientLoadoutsOnline", AttributeTypeLoadoutsOnline)
-  , ("TAGame.PRI_TA:ClubID", AttributeTypeInt64)
-  , ("TAGame.PRI_TA:MatchAssists", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MatchBreakoutDamage", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MatchGoals", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MatchSaves", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MatchScore", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MatchShots", AttributeTypeInt)
-  , ("TAGame.PRI_TA:MaxTimeTillItem", AttributeTypeInt)
-  , ("TAGame.PRI_TA:PartyLeader", AttributeTypePartyLeader)
-  , ("TAGame.PRI_TA:PawnType", AttributeTypeByte)
-  , ("TAGame.PRI_TA:PersistentCamera", AttributeTypeFlaggedInt)
-  , ("TAGame.PRI_TA:PlayerHistoryKey", AttributeTypePlayerHistoryKey)
-  , ("TAGame.PRI_TA:PlayerHistoryValid", AttributeTypeBoolean)
-  , ("TAGame.PRI_TA:ReplicatedGameEvent", AttributeTypeFlaggedInt)
-  , ("TAGame.PRI_TA:ReplicatedWorstNetQualityBeyondLatency", AttributeTypeByte)
-  , ("TAGame.PRI_TA:SteeringSensitivity", AttributeTypeFloat)
-  , ("TAGame.PRI_TA:SkillTier", AttributeTypeFlaggedByte)
-  , ("TAGame.PRI_TA:TimeTillItem", AttributeTypeInt)
-  , ("TAGame.PRI_TA:Title", AttributeTypeInt)
-  , ("TAGame.PRI_TA:TotalXP", AttributeTypeInt)
-  , ("TAGame.PRI_TA:PrimaryTitle", AttributeTypeTitle)
-  , ("TAGame.PRI_TA:SecondaryTitle", AttributeTypeTitle)
-  , ("TAGame.PRI_TA:SpectatorShortcut", AttributeTypeInt)
-  , ("TAGame.RBActor_TA:bFrozen", AttributeTypeBoolean)
-  , ("TAGame.RBActor_TA:bIgnoreSyncing", AttributeTypeBoolean)
-  , ("TAGame.RBActor_TA:bReplayActor", AttributeTypeBoolean)
-  , ("TAGame.RBActor_TA:ReplicatedRBState", AttributeTypeRigidBodyState)
-  , ("TAGame.RBActor_TA:WeldedInfo", AttributeTypeWeldedInfo)
-  , ("TAGame.SpecialPickup_BallFreeze_TA:RepOrigSpeed", AttributeTypeFloat)
-  , ("TAGame.SpecialPickup_BallVelcro_TA:AttachTime", AttributeTypeFloat)
-  , ("TAGame.SpecialPickup_BallVelcro_TA:bBroken", AttributeTypeBoolean)
-  , ("TAGame.SpecialPickup_BallVelcro_TA:bHit", AttributeTypeBoolean)
-  , ("TAGame.SpecialPickup_BallVelcro_TA:BreakTime", AttributeTypeFloat)
-  , ("TAGame.SpecialPickup_Targeted_TA:Targeted", AttributeTypeFlaggedInt)
-  , ("TAGame.Team_Soccar_TA:GameScore", AttributeTypeInt)
-  , ("TAGame.Team_TA:ClubColors", AttributeTypeClubColors)
-  , ("TAGame.Team_TA:ClubID", AttributeTypeInt64)
-  , ("TAGame.Team_TA:CustomTeamName", AttributeTypeString)
-  , ("TAGame.Team_TA:GameEvent", AttributeTypeFlaggedInt)
-  , ("TAGame.Team_TA:LogoData", AttributeTypeFlaggedInt)
-  , ("TAGame.Vehicle_TA:bDriving", AttributeTypeBoolean)
-  , ("TAGame.Vehicle_TA:bReplicatedHandbrake", AttributeTypeBoolean)
-  , ("TAGame.Vehicle_TA:ReplicatedSteer", AttributeTypeByte)
-  , ("TAGame.Vehicle_TA:ReplicatedThrottle", AttributeTypeByte)
-  , ("TAGame.VehiclePickup_TA:bNoPickup", AttributeTypeBoolean)
-  , ("TAGame.VehiclePickup_TA:ReplicatedPickupData", AttributeTypePickup)
-  , ("TAGame.Ball_Haunted_TA:LastTeamTouch", AttributeTypeByte)
-  , ("TAGame.Ball_Haunted_TA:TotalActiveBeams", AttributeTypeByte)
-  , ("TAGame.Ball_Haunted_TA:DeactivatedGoalIndex", AttributeTypeByte)
-  , ("TAGame.Ball_Haunted_TA:ReplicatedBeamBrokenValue", AttributeTypeByte)
-  , ("TAGame.Ball_Haunted_TA:bIsBallBeamed", AttributeTypeBoolean)
-  , ("TAGame.SpecialPickup_Rugby_TA:bBallWelded", AttributeTypeBoolean)
-  ]
-
-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
-  ]
diff --git a/library/Rattletrap/Decode/AppliedDamageAttribute.hs b/library/Rattletrap/Decode/AppliedDamageAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/AppliedDamageAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Decode.AppliedDamageAttribute
-  ( decodeAppliedDamageAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Decode.Vector
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.AppliedDamageAttribute
-
-decodeAppliedDamageAttributeBits
-  :: (Int, Int, Int) -> DecodeBits AppliedDamageAttribute
-decodeAppliedDamageAttributeBits version =
-  AppliedDamageAttribute
-    <$> decodeWord8leBits
-    <*> decodeVectorBits version
-    <*> decodeInt32leBits
-    <*> decodeInt32leBits
diff --git a/library/Rattletrap/Decode/Attribute.hs b/library/Rattletrap/Decode/Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Attribute.hs
+++ /dev/null
@@ -1,74 +0,0 @@
-module Rattletrap.Decode.Attribute
-  ( decodeAttributesBits
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Decode.AttributeValue
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Type.Attribute
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-decodeAttributesBits
-  :: (Int, Int, Int)
-  -> ClassAttributeMap
-  -> Map CompressedWord Word32le
-  -> CompressedWord
-  -> DecodeBits [Attribute]
-decodeAttributesBits version classes actors actor = do
-  hasAttribute <- getBool
-  if hasAttribute
-    then
-      (:)
-      <$> decodeAttributeBits version classes actors actor
-      <*> decodeAttributesBits version classes actors actor
-    else pure []
-
-decodeAttributeBits
-  :: (Int, Int, Int)
-  -> ClassAttributeMap
-  -> Map CompressedWord Word32le
-  -> CompressedWord
-  -> DecodeBits Attribute
-decodeAttributeBits version classes actors actor = do
-  attributes <- lookupAttributeMap classes actors actor
-  limit <- lookupAttributeIdLimit attributes actor
-  attribute <- decodeCompressedWordBits limit
-  name <- lookupAttributeName classes attributes attribute
-  Attribute attribute name
-    <$> decodeAttributeValueBits
-          version
-          (classAttributeMapObjectMap classes)
-          name
-
-lookupAttributeMap
-  :: ClassAttributeMap
-  -> Map CompressedWord Word32le
-  -> CompressedWord
-  -> DecodeBits (Map Word32le Word32le)
-lookupAttributeMap classes actors actor = fromMaybe
-  ("could not get attribute map for " <> show actor)
-  (getAttributeMap classes actors actor)
-
-lookupAttributeIdLimit
-  :: Map Word32le Word32le -> CompressedWord -> DecodeBits Word
-lookupAttributeIdLimit attributes actor = fromMaybe
-  ("could not get attribute ID limit for " <> show actor)
-  (getAttributeIdLimit attributes)
-
-lookupAttributeName
-  :: ClassAttributeMap
-  -> Map Word32le Word32le
-  -> CompressedWord
-  -> DecodeBits Str
-lookupAttributeName classes attributes attribute = fromMaybe
-  ("could not get attribute name for " <> show attribute)
-  (getAttributeName classes attributes attribute)
-
-fromMaybe :: String -> Maybe a -> DecodeBits a
-fromMaybe message = maybe (fail message) pure
diff --git a/library/Rattletrap/Decode/AttributeMapping.hs b/library/Rattletrap/Decode/AttributeMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/AttributeMapping.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.AttributeMapping
-  ( decodeAttributeMapping
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.AttributeMapping
-
-decodeAttributeMapping :: Decode AttributeMapping
-decodeAttributeMapping =
-  AttributeMapping <$> decodeWord32le <*> decodeWord32le
diff --git a/library/Rattletrap/Decode/AttributeValue.hs b/library/Rattletrap/Decode/AttributeValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/AttributeValue.hs
+++ /dev/null
@@ -1,126 +0,0 @@
-module Rattletrap.Decode.AttributeValue
-  ( decodeAttributeValueBits
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Data
-import Rattletrap.Decode.AppliedDamageAttribute
-import Rattletrap.Decode.BooleanAttribute
-import Rattletrap.Decode.ByteAttribute
-import Rattletrap.Decode.CamSettingsAttribute
-import Rattletrap.Decode.ClubColorsAttribute
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.DamageStateAttribute
-import Rattletrap.Decode.DemolishAttribute
-import Rattletrap.Decode.EnumAttribute
-import Rattletrap.Decode.ExplosionAttribute
-import Rattletrap.Decode.ExtendedExplosionAttribute
-import Rattletrap.Decode.FlaggedIntAttribute
-import Rattletrap.Decode.FlaggedByteAttribute
-import Rattletrap.Decode.FloatAttribute
-import Rattletrap.Decode.GameModeAttribute
-import Rattletrap.Decode.Int64Attribute
-import Rattletrap.Decode.IntAttribute
-import Rattletrap.Decode.LoadoutAttribute
-import Rattletrap.Decode.LoadoutOnlineAttribute
-import Rattletrap.Decode.LoadoutsAttribute
-import Rattletrap.Decode.LoadoutsOnlineAttribute
-import Rattletrap.Decode.LocationAttribute
-import Rattletrap.Decode.MusicStingerAttribute
-import Rattletrap.Decode.PartyLeaderAttribute
-import Rattletrap.Decode.PickupAttribute
-import Rattletrap.Decode.PlayerHistoryKeyAttribute
-import Rattletrap.Decode.PrivateMatchSettingsAttribute
-import Rattletrap.Decode.QWordAttribute
-import Rattletrap.Decode.ReservationAttribute
-import Rattletrap.Decode.RigidBodyStateAttribute
-import Rattletrap.Decode.StatEventAttribute
-import Rattletrap.Decode.StringAttribute
-import Rattletrap.Decode.TeamPaintAttribute
-import Rattletrap.Decode.TitleAttribute
-import Rattletrap.Decode.UniqueIdAttribute
-import Rattletrap.Decode.WeldedInfoAttribute
-import Rattletrap.Type.AttributeType
-import Rattletrap.Type.AttributeValue
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-import qualified Data.Map as Map
-
-decodeAttributeValueBits
-  :: (Int, Int, Int) -> Map Word32le Str -> Str -> DecodeBits AttributeValue
-decodeAttributeValueBits version objectMap name = do
-  constructor <- maybe
-    (fail ("don't know how to get attribute value " <> show name))
-    pure
-    (Map.lookup name attributeTypes)
-  case constructor of
-    AttributeTypeAppliedDamage ->
-      AttributeValueAppliedDamage <$> decodeAppliedDamageAttributeBits version
-    AttributeTypeBoolean ->
-      AttributeValueBoolean <$> decodeBooleanAttributeBits
-    AttributeTypeByte -> AttributeValueByte <$> decodeByteAttributeBits
-    AttributeTypeCamSettings ->
-      AttributeValueCamSettings <$> decodeCamSettingsAttributeBits version
-    AttributeTypeClubColors ->
-      AttributeValueClubColors <$> decodeClubColorsAttributeBits
-    AttributeTypeDamageState ->
-      AttributeValueDamageState <$> decodeDamageStateAttributeBits version
-    AttributeTypeDemolish ->
-      AttributeValueDemolish <$> decodeDemolishAttributeBits version
-    AttributeTypeEnum -> AttributeValueEnum <$> decodeEnumAttributeBits
-    AttributeTypeExplosion ->
-      AttributeValueExplosion <$> decodeExplosionAttributeBits version
-    AttributeTypeExtendedExplosion -> AttributeValueExtendedExplosion
-      <$> decodeExtendedExplosionAttributeBits version
-    AttributeTypeFlaggedInt ->
-      AttributeValueFlaggedInt <$> decodeFlaggedIntAttributeBits
-    AttributeTypeFlaggedByte ->
-      AttributeValueFlaggedByte <$> decodeFlaggedByteAttributeBits
-    AttributeTypeFloat -> AttributeValueFloat <$> decodeFloatAttributeBits
-    AttributeTypeGameMode ->
-      AttributeValueGameMode <$> decodeGameModeAttributeBits version
-    AttributeTypeInt -> AttributeValueInt <$> decodeIntAttributeBits
-    AttributeTypeInt64 -> AttributeValueInt64 <$> decodeInt64AttributeBits
-    AttributeTypeLoadout ->
-      AttributeValueLoadout <$> decodeLoadoutAttributeBits
-    AttributeTypeLoadoutOnline ->
-      AttributeValueLoadoutOnline
-        <$> decodeLoadoutOnlineAttributeBits version objectMap
-    AttributeTypeLoadouts ->
-      AttributeValueLoadouts <$> decodeLoadoutsAttributeBits
-    AttributeTypeLoadoutsOnline ->
-      AttributeValueLoadoutsOnline
-        <$> decodeLoadoutsOnlineAttributeBits version objectMap
-    AttributeTypeLocation ->
-      AttributeValueLocation <$> decodeLocationAttributeBits version
-    AttributeTypeMusicStinger ->
-      AttributeValueMusicStinger <$> decodeMusicStingerAttributeBits
-    AttributeTypePartyLeader ->
-      AttributeValuePartyLeader <$> decodePartyLeaderAttributeBits version
-    AttributeTypePickup -> AttributeValuePickup <$> decodePickupAttributeBits
-    AttributeTypePlayerHistoryKey ->
-      AttributeValuePlayerHistoryKey <$> decodePlayerHistoryKeyAttributeBits
-    AttributeTypePrivateMatchSettings ->
-      AttributeValuePrivateMatchSettings
-        <$> decodePrivateMatchSettingsAttributeBits
-    AttributeTypeQWord -> AttributeValueQWord <$> decodeQWordAttributeBits
-    AttributeTypeReservation ->
-      AttributeValueReservation <$> decodeReservationAttributeBits version
-    AttributeTypeRigidBodyState -> AttributeValueRigidBodyState
-      <$> decodeRigidBodyStateAttributeBits version
-    AttributeTypeStatEvent ->
-      AttributeValueStatEvent <$> decodeStatEventAttributeBits
-    AttributeTypeString -> AttributeValueString <$> decodeStringAttributeBits
-    AttributeTypeTeamPaint ->
-      AttributeValueTeamPaint <$> decodeTeamPaintAttributeBits
-    AttributeTypeTitle -> AttributeValueTitle <$> decodeTitleAttributeBits
-    AttributeTypeUniqueId ->
-      AttributeValueUniqueId <$> decodeUniqueIdAttributeBits version
-    AttributeTypeWeldedInfo ->
-      AttributeValueWeldedInfo <$> decodeWeldedInfoAttributeBits version
-
-attributeTypes :: Map Str AttributeType
-attributeTypes = Map.mapKeys toStr (Map.fromList rawAttributeTypes)
diff --git a/library/Rattletrap/Decode/BooleanAttribute.hs b/library/Rattletrap/Decode/BooleanAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/BooleanAttribute.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Rattletrap.Decode.BooleanAttribute
-  ( decodeBooleanAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.BooleanAttribute
-
-decodeBooleanAttributeBits :: DecodeBits BooleanAttribute
-decodeBooleanAttributeBits = BooleanAttribute <$> getBool
diff --git a/library/Rattletrap/Decode/ByteAttribute.hs b/library/Rattletrap/Decode/ByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ByteAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.ByteAttribute
-  ( decodeByteAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.ByteAttribute
-
-decodeByteAttributeBits :: DecodeBits ByteAttribute
-decodeByteAttributeBits = ByteAttribute <$> decodeWord8leBits
diff --git a/library/Rattletrap/Decode/Cache.hs b/library/Rattletrap/Decode/Cache.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Cache.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Decode.Cache
-  ( decodeCache
-  )
-where
-
-import Rattletrap.Decode.AttributeMapping
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.List
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.Cache
-
-decodeCache :: Decode Cache
-decodeCache =
-  Cache
-    <$> decodeWord32le
-    <*> decodeWord32le
-    <*> decodeWord32le
-    <*> decodeList decodeAttributeMapping
diff --git a/library/Rattletrap/Decode/CamSettingsAttribute.hs b/library/Rattletrap/Decode/CamSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/CamSettingsAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Decode.CamSettingsAttribute
-  ( decodeCamSettingsAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Float32le
-import Rattletrap.Type.CamSettingsAttribute
-
-decodeCamSettingsAttributeBits
-  :: (Int, Int, Int) -> DecodeBits CamSettingsAttribute
-decodeCamSettingsAttributeBits version =
-  CamSettingsAttribute
-    <$> decodeFloat32leBits
-    <*> decodeFloat32leBits
-    <*> decodeFloat32leBits
-    <*> decodeFloat32leBits
-    <*> decodeFloat32leBits
-    <*> decodeFloat32leBits
-    <*> decodeWhen (version >= (868, 20, 0)) decodeFloat32leBits
diff --git a/library/Rattletrap/Decode/ClassMapping.hs b/library/Rattletrap/Decode/ClassMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ClassMapping.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.ClassMapping
-  ( decodeClassMapping
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.ClassMapping
-
-decodeClassMapping :: Decode ClassMapping
-decodeClassMapping = ClassMapping <$> decodeStr <*> decodeWord32le
diff --git a/library/Rattletrap/Decode/ClubColorsAttribute.hs b/library/Rattletrap/Decode/ClubColorsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ClubColorsAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.ClubColorsAttribute
-  ( decodeClubColorsAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.ClubColorsAttribute
-
-decodeClubColorsAttributeBits :: DecodeBits ClubColorsAttribute
-decodeClubColorsAttributeBits =
-  ClubColorsAttribute
-    <$> getBool
-    <*> decodeWord8leBits
-    <*> getBool
-    <*> decodeWord8leBits
diff --git a/library/Rattletrap/Decode/Common.hs b/library/Rattletrap/Decode/Common.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Common.hs
+++ /dev/null
@@ -1,70 +0,0 @@
-module Rattletrap.Decode.Common
-  ( Decode
-  , DecodeBits
-  , decodeWhen
-  , getBitsLE
-  , getByteStringBits
-  , getWord8Bits
-  , runDecode
-  , runDecodeBits
-  , toBits
-  , Binary.getFloatle
-  , Binary.getByteString
-  , Binary.getInt8
-  , Binary.getInt32le
-  , Binary.getInt64le
-  , Binary.getWord8
-  , Binary.getWord32le
-  , Binary.getWord64le
-  , BinaryBits.getBool
-  )
-where
-
-import qualified Control.Applicative as Applicative
-import qualified Control.Monad as Monad
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Get as BinaryBits
-import qualified Data.Binary.Get as Binary
-import qualified Data.Bits as Bits
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Lazy as LazyBytes
-import qualified Data.Word as Word
-import qualified Rattletrap.Utility.Bytes as Utility
-
-type Decode = Binary.Get
-
-type DecodeBits = BinaryBits.BitGet
-
-decodeWhen
-  :: (Applicative m, Applicative.Alternative f) => Bool -> m a -> m (f a)
-decodeWhen p f = if p then fmap pure f else pure Applicative.empty
-
-getByteStringBits :: Int -> DecodeBits Bytes.ByteString
-getByteStringBits = BinaryBits.getByteString
-
-getWord8Bits :: Int -> DecodeBits Word.Word8
-getWord8Bits = BinaryBits.getWord8
-
-runDecode :: Decode a -> Bytes.ByteString -> Either String a
-runDecode decode bytes =
-  case Binary.runGetOrFail decode (LazyBytes.fromStrict bytes) of
-    Left (_, _, x) -> Left x
-    Right (_, _, x) -> Right x
-
-runDecodeBits :: DecodeBits a -> Bytes.ByteString -> Either String a
-runDecodeBits = runDecode . BinaryBits.runBitGet
-
-toBits :: Decode a -> Int -> DecodeBits a
-toBits decode size = do
-  bytes <- BinaryBits.getByteString size
-  case runDecode decode (Utility.reverseBytes bytes) of
-    Left problem -> fail problem
-    Right result -> pure result
-
-getBitsLE :: Bits.Bits a => Int -> BinaryBits.BitGet a
-getBitsLE size = do
-  bits <- Monad.replicateM size BinaryBits.getBool
-  pure $ foldr
-    (\bit x -> let y = Bits.shiftL x 1 in if bit then Bits.setBit y 0 else y)
-    Bits.zeroBits
-    bits
diff --git a/library/Rattletrap/Decode/CompressedWord.hs b/library/Rattletrap/Decode/CompressedWord.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/CompressedWord.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-module Rattletrap.Decode.CompressedWord
-  ( decodeCompressedWordBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.CompressedWord
-
-import qualified Data.Bits as Bits
-
-decodeCompressedWordBits :: Word -> DecodeBits CompressedWord
-decodeCompressedWordBits limit =
-  CompressedWord limit <$> step limit (getMaxBits limit) 0 0
-
-getMaxBits :: Word -> Word
-getMaxBits x = do
-  let
-    n :: Word
-    n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
-  if x < 1024 && x == 2 ^ n then n + 1 else n
-
-step :: Word -> Word -> Word -> Word -> DecodeBits Word
-step limit maxBits position value = do
-  let x = Bits.shiftL 1 (fromIntegral position) :: Word
-  if position < maxBits && value + x <= limit
-    then do
-      bit <- getBool
-      let newValue = if bit then value + x else value
-      step limit maxBits (position + 1) newValue
-    else pure value
diff --git a/library/Rattletrap/Decode/CompressedWordVector.hs b/library/Rattletrap/Decode/CompressedWordVector.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/CompressedWordVector.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Decode.CompressedWordVector
-  ( decodeCompressedWordVectorBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Type.CompressedWordVector
-
-decodeCompressedWordVectorBits :: DecodeBits CompressedWordVector
-decodeCompressedWordVectorBits =
-  CompressedWordVector
-    <$> decodeCompressedWordBits limit
-    <*> decodeCompressedWordBits limit
-    <*> decodeCompressedWordBits limit
-
-limit :: Word
-limit = 65536
diff --git a/library/Rattletrap/Decode/Content.hs b/library/Rattletrap/Decode/Content.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Content.hs
+++ /dev/null
@@ -1,73 +0,0 @@
-module Rattletrap.Decode.Content
-  ( decodeContent
-  )
-where
-
-import Rattletrap.Decode.Cache
-import Rattletrap.Decode.ClassMapping
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Frame
-import Rattletrap.Decode.KeyFrame
-import Rattletrap.Decode.List
-import Rattletrap.Decode.Mark
-import Rattletrap.Decode.Message
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.Content
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Bytes
-
-import qualified Control.Monad.Trans.State as State
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-decodeContent
-  :: (Int, Int, Int)
-  -- ^ Version numbers, usually from 'Rattletrap.Header.getVersion'.
-  -> Int
-  -- ^ The number of frames in the stream, usually from
-  -- 'Rattletrap.Header.getNumFrames'.
-  -> Word
-  -- ^ The maximum number of channels in the stream, usually from
-  -- 'Rattletrap.Header.getMaxChannels'.
-  -> Decode Content
-decodeContent version numFrames maxChannels = do
-  (levels, keyFrames, streamSize) <-
-    (,,)
-    <$> decodeList decodeStr
-    <*> decodeList decodeKeyFrame
-    <*> decodeWord32le
-  (stream, messages, marks, packages, objects, names, classMappings, caches) <-
-    (,,,,,,,)
-    <$> getByteString (fromIntegral (word32leValue streamSize))
-    <*> decodeList decodeMessage
-    <*> decodeList decodeMark
-    <*> decodeList decodeStr
-    <*> decodeList decodeStr
-    <*> decodeList decodeStr
-    <*> decodeList decodeClassMapping
-    <*> decodeList decodeCache
-  let
-    classAttributeMap =
-      makeClassAttributeMap objects classMappings caches names
-    bitGet = State.evalStateT
-      (decodeFramesBits version numFrames maxChannels classAttributeMap)
-      mempty
-  frames <- either fail pure (runDecodeBits bitGet (reverseBytes stream))
-  unknown <- Binary.getRemainingLazyByteString
-  pure
-    (Content
-      levels
-      keyFrames
-      streamSize
-      frames
-      messages
-      marks
-      packages
-      objects
-      names
-      classMappings
-      caches
-      (LazyBytes.unpack unknown)
-    )
diff --git a/library/Rattletrap/Decode/DamageStateAttribute.hs b/library/Rattletrap/Decode/DamageStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/DamageStateAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Decode.DamageStateAttribute
-  ( decodeDamageStateAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Decode.Vector
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.DamageStateAttribute
-
-decodeDamageStateAttributeBits
-  :: (Int, Int, Int) -> DecodeBits DamageStateAttribute
-decodeDamageStateAttributeBits version =
-  DamageStateAttribute
-    <$> decodeWord8leBits
-    <*> getBool
-    <*> decodeInt32leBits
-    <*> decodeVectorBits version
-    <*> getBool
-    <*> getBool
diff --git a/library/Rattletrap/Decode/DemolishAttribute.hs b/library/Rattletrap/Decode/DemolishAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/DemolishAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Decode.DemolishAttribute
-  ( decodeDemolishAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Vector
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.DemolishAttribute
-
-decodeDemolishAttributeBits :: (Int, Int, Int) -> DecodeBits DemolishAttribute
-decodeDemolishAttributeBits version =
-  DemolishAttribute
-    <$> getBool
-    <*> decodeWord32leBits
-    <*> getBool
-    <*> decodeWord32leBits
-    <*> decodeVectorBits version
-    <*> decodeVectorBits version
diff --git a/library/Rattletrap/Decode/DestroyedReplication.hs b/library/Rattletrap/Decode/DestroyedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/DestroyedReplication.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Rattletrap.Decode.DestroyedReplication
-  ( decodeDestroyedReplicationBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.DestroyedReplication
-
-decodeDestroyedReplicationBits :: DecodeBits DestroyedReplication
-decodeDestroyedReplicationBits = pure DestroyedReplication
diff --git a/library/Rattletrap/Decode/Dictionary.hs b/library/Rattletrap/Decode/Dictionary.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Dictionary.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Rattletrap.Decode.Dictionary
-  ( decodeDictionary
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Type.Dictionary
-import Rattletrap.Type.Str
-
-decodeDictionary :: Decode a -> Decode (Dictionary a)
-decodeDictionary decodeValue = do
-  key <- decodeStr
-  case filter (/= '\x00') (fromStr key) of
-    "None" -> pure (DictionaryEnd key)
-    _ ->
-      DictionaryElement key <$> decodeValue <*> decodeDictionary decodeValue
diff --git a/library/Rattletrap/Decode/EnumAttribute.hs b/library/Rattletrap/Decode/EnumAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/EnumAttribute.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Rattletrap.Decode.EnumAttribute
-  ( decodeEnumAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.EnumAttribute
-
-decodeEnumAttributeBits :: DecodeBits EnumAttribute
-decodeEnumAttributeBits = EnumAttribute <$> getBitsLE 11
diff --git a/library/Rattletrap/Decode/ExplosionAttribute.hs b/library/Rattletrap/Decode/ExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ExplosionAttribute.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Rattletrap.Decode.ExplosionAttribute
-  ( decodeExplosionAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Decode.Vector
-import Rattletrap.Type.ExplosionAttribute
-
-decodeExplosionAttributeBits
-  :: (Int, Int, Int) -> DecodeBits ExplosionAttribute
-decodeExplosionAttributeBits version =
-  ExplosionAttribute
-    <$> getBool
-    <*> decodeInt32leBits
-    <*> decodeVectorBits version
diff --git a/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs b/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.ExtendedExplosionAttribute
-  ( decodeExtendedExplosionAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.ExplosionAttribute
-import Rattletrap.Decode.FlaggedIntAttribute
-import Rattletrap.Type.ExtendedExplosionAttribute
-
-decodeExtendedExplosionAttributeBits
-  :: (Int, Int, Int) -> DecodeBits ExtendedExplosionAttribute
-decodeExtendedExplosionAttributeBits version =
-  ExtendedExplosionAttribute
-    <$> decodeExplosionAttributeBits version
-    <*> decodeFlaggedIntAttributeBits
diff --git a/library/Rattletrap/Decode/FlaggedByteAttribute.hs b/library/Rattletrap/Decode/FlaggedByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/FlaggedByteAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.FlaggedByteAttribute
-  ( decodeFlaggedByteAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.FlaggedByteAttribute
-
-
-decodeFlaggedByteAttributeBits :: DecodeBits FlaggedByteAttribute
-decodeFlaggedByteAttributeBits = FlaggedByteAttribute <$> getBool <*> decodeWord8leBits
diff --git a/library/Rattletrap/Decode/FlaggedIntAttribute.hs b/library/Rattletrap/Decode/FlaggedIntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/FlaggedIntAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.FlaggedIntAttribute
-  ( decodeFlaggedIntAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Type.FlaggedIntAttribute
-
-decodeFlaggedIntAttributeBits :: DecodeBits FlaggedIntAttribute
-decodeFlaggedIntAttributeBits =
-  FlaggedIntAttribute <$> getBool <*> decodeInt32leBits
diff --git a/library/Rattletrap/Decode/Float32le.hs b/library/Rattletrap/Decode/Float32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Float32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Float32le
-  ( decodeFloat32le
-  , decodeFloat32leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Float32le
-
-decodeFloat32le :: Decode Float32le
-decodeFloat32le = Float32le <$> getFloatle
-
-decodeFloat32leBits :: DecodeBits Float32le
-decodeFloat32leBits = toBits decodeFloat32le 4
diff --git a/library/Rattletrap/Decode/FloatAttribute.hs b/library/Rattletrap/Decode/FloatAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/FloatAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.FloatAttribute
-  ( decodeFloatAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Float32le
-import Rattletrap.Type.FloatAttribute
-
-decodeFloatAttributeBits :: DecodeBits FloatAttribute
-decodeFloatAttributeBits = FloatAttribute <$> decodeFloat32leBits
diff --git a/library/Rattletrap/Decode/Frame.hs b/library/Rattletrap/Decode/Frame.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Frame.hs
+++ /dev/null
@@ -1,43 +0,0 @@
-module Rattletrap.Decode.Frame
-  ( decodeFramesBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Float32le
-import Rattletrap.Decode.Replication
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Frame
-import Rattletrap.Type.Word32le
-
-import qualified Control.Monad.Trans.Class as Trans
-import qualified Control.Monad.Trans.State as State
-import qualified Data.Map as Map
-
-decodeFramesBits
-  :: (Int, Int, Int)
-  -> Int
-  -> Word
-  -> ClassAttributeMap
-  -> State.StateT
-       (Map.Map CompressedWord Word32le)
-       DecodeBits
-       [Frame]
-decodeFramesBits version count limit classes = if count <= 0
-  then pure []
-  else
-    (:)
-    <$> decodeFrameBits version limit classes
-    <*> decodeFramesBits version (count - 1) limit classes
-
-decodeFrameBits
-  :: (Int, Int, Int)
-  -> Word
-  -> ClassAttributeMap
-  -> State.StateT (Map.Map CompressedWord Word32le) DecodeBits Frame
-decodeFrameBits version limit classes =
-  Frame
-    <$> Trans.lift decodeFloat32leBits
-    <*> Trans.lift decodeFloat32leBits
-    <*> decodeReplicationsBits version limit classes
diff --git a/library/Rattletrap/Decode/GameModeAttribute.hs b/library/Rattletrap/Decode/GameModeAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/GameModeAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Decode.GameModeAttribute
-  ( decodeGameModeAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.GameModeAttribute
-
-decodeGameModeAttributeBits :: (Int, Int, Int) -> DecodeBits GameModeAttribute
-decodeGameModeAttributeBits version =
-  GameModeAttribute <$> pure (numBits version) <*> getWord8Bits
-    (numBits version)
-
-numBits :: (Int, Int, Int) -> Int
-numBits version = if version >= (868, 12, 0) then 8 else 2
diff --git a/library/Rattletrap/Decode/Header.hs b/library/Rattletrap/Decode/Header.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Header.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Rattletrap.Decode.Header
-  ( decodeHeader
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Dictionary
-import Rattletrap.Decode.Property
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.Header
-import Rattletrap.Type.Word32le
-
-decodeHeader :: Decode Header
-decodeHeader = do
-  (major, minor) <- (,) <$> decodeWord32le <*> decodeWord32le
-  Header major minor
-    <$> decodeWhen
-          (major >= Word32le 868 && minor >= Word32le 18)
-          decodeWord32le
-    <*> decodeStr
-    <*> decodeDictionary decodeProperty
diff --git a/library/Rattletrap/Decode/Initialization.hs b/library/Rattletrap/Decode/Initialization.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Initialization.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.Initialization
-  ( decodeInitializationBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int8Vector
-import Rattletrap.Decode.Vector
-import Rattletrap.Type.Initialization
-
-decodeInitializationBits
-  :: (Int, Int, Int) -> Bool -> Bool -> DecodeBits Initialization
-decodeInitializationBits version hasLocation hasRotation =
-  Initialization
-    <$> decodeWhen hasLocation (decodeVectorBits version)
-    <*> decodeWhen hasRotation decodeInt8VectorBits
diff --git a/library/Rattletrap/Decode/Int32le.hs b/library/Rattletrap/Decode/Int32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Int32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Int32le
-  ( decodeInt32le
-  , decodeInt32leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Int32le
-
-decodeInt32le :: Decode Int32le
-decodeInt32le = Int32le <$> getInt32le
-
-decodeInt32leBits :: DecodeBits Int32le
-decodeInt32leBits = toBits decodeInt32le 4
diff --git a/library/Rattletrap/Decode/Int64Attribute.hs b/library/Rattletrap/Decode/Int64Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Int64Attribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.Int64Attribute
-  ( decodeInt64AttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int64le
-import Rattletrap.Type.Int64Attribute
-
-decodeInt64AttributeBits :: DecodeBits Int64Attribute
-decodeInt64AttributeBits = Int64Attribute <$> decodeInt64leBits
diff --git a/library/Rattletrap/Decode/Int64le.hs b/library/Rattletrap/Decode/Int64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Int64le.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Decode.Int64le
-  ( decodeInt64leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Int64le
-
-decodeInt64le :: Decode Int64le
-decodeInt64le = Int64le <$> getInt64le
-
-decodeInt64leBits :: DecodeBits Int64le
-decodeInt64leBits = toBits decodeInt64le 8
diff --git a/library/Rattletrap/Decode/Int8Vector.hs b/library/Rattletrap/Decode/Int8Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Int8Vector.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Decode.Int8Vector
-  ( decodeInt8VectorBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int8le
-import Rattletrap.Type.Int8Vector
-import Rattletrap.Type.Int8le
-
-decodeInt8VectorBits :: DecodeBits Int8Vector
-decodeInt8VectorBits =
-  Int8Vector <$> decodeFieldBits <*> decodeFieldBits <*> decodeFieldBits
-
-decodeFieldBits :: DecodeBits (Maybe Int8le)
-decodeFieldBits = do
-  hasField <- getBool
-  decodeWhen hasField decodeInt8leBits
diff --git a/library/Rattletrap/Decode/Int8le.hs b/library/Rattletrap/Decode/Int8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Int8le.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Decode.Int8le
-  ( decodeInt8leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Int8le
-
-decodeInt8le :: Decode Int8le
-decodeInt8le = Int8le <$> getInt8
-
-decodeInt8leBits :: DecodeBits Int8le
-decodeInt8leBits = toBits decodeInt8le 1
diff --git a/library/Rattletrap/Decode/IntAttribute.hs b/library/Rattletrap/Decode/IntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/IntAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.IntAttribute
-  ( decodeIntAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Type.IntAttribute
-
-decodeIntAttributeBits :: DecodeBits IntAttribute
-decodeIntAttributeBits = IntAttribute <$> decodeInt32leBits
diff --git a/library/Rattletrap/Decode/KeyFrame.hs b/library/Rattletrap/Decode/KeyFrame.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/KeyFrame.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Decode.KeyFrame
-  ( decodeKeyFrame
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Float32le
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.KeyFrame
-
-decodeKeyFrame :: Decode KeyFrame
-decodeKeyFrame =
-  KeyFrame <$> decodeFloat32le <*> decodeWord32le <*> decodeWord32le
diff --git a/library/Rattletrap/Decode/List.hs b/library/Rattletrap/Decode/List.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/List.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.List
-  ( decodeList
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.List
-import Rattletrap.Type.Word32le
-
-import qualified Control.Monad as Monad
-
-decodeList :: Decode a -> Decode (List a)
-decodeList decodeElement = do
-  size <- decodeWord32le
-  List <$> Monad.replicateM (fromIntegral (word32leValue size)) decodeElement
diff --git a/library/Rattletrap/Decode/LoadoutAttribute.hs b/library/Rattletrap/Decode/LoadoutAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/LoadoutAttribute.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-module Rattletrap.Decode.LoadoutAttribute
-  ( decodeLoadoutAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.LoadoutAttribute
-import Rattletrap.Type.Word8le
-
-decodeLoadoutAttributeBits :: DecodeBits LoadoutAttribute
-decodeLoadoutAttributeBits = do
-  version <- decodeWord8leBits
-  LoadoutAttribute version
-    <$> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 11) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 17) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 19) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
-    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
diff --git a/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs b/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Rattletrap.Decode.LoadoutOnlineAttribute
-  ( decodeLoadoutOnlineAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.ProductAttribute
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.LoadoutOnlineAttribute
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-import Rattletrap.Type.Word8le
-
-import qualified Control.Monad as Monad
-import qualified Data.Map as Map
-
-decodeLoadoutOnlineAttributeBits
-  :: (Int, Int, Int)
-  -> Map.Map Word32le Str
-  -> DecodeBits LoadoutOnlineAttribute
-decodeLoadoutOnlineAttributeBits version objectMap = do
-  size <- decodeWord8leBits
-  LoadoutOnlineAttribute <$> Monad.replicateM
-    (fromIntegral (word8leValue size))
-    (decodeProductAttributesBits version objectMap)
diff --git a/library/Rattletrap/Decode/LoadoutsAttribute.hs b/library/Rattletrap/Decode/LoadoutsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/LoadoutsAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.LoadoutsAttribute
-  ( decodeLoadoutsAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.LoadoutAttribute
-import Rattletrap.Type.LoadoutsAttribute
-
-decodeLoadoutsAttributeBits :: DecodeBits LoadoutsAttribute
-decodeLoadoutsAttributeBits =
-  LoadoutsAttribute
-    <$> decodeLoadoutAttributeBits
-    <*> decodeLoadoutAttributeBits
diff --git a/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs b/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Rattletrap.Decode.LoadoutsOnlineAttribute
-  ( decodeLoadoutsOnlineAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.LoadoutOnlineAttribute
-import Rattletrap.Type.LoadoutsOnlineAttribute
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-import qualified Data.Map as Map
-
-decodeLoadoutsOnlineAttributeBits
-  :: (Int, Int, Int)
-  -> Map.Map Word32le Str
-  -> DecodeBits LoadoutsOnlineAttribute
-decodeLoadoutsOnlineAttributeBits version objectMap =
-  LoadoutsOnlineAttribute
-    <$> decodeLoadoutOnlineAttributeBits version objectMap
-    <*> decodeLoadoutOnlineAttributeBits version objectMap
-    <*> getBool
-    <*> getBool
diff --git a/library/Rattletrap/Decode/LocationAttribute.hs b/library/Rattletrap/Decode/LocationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/LocationAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.LocationAttribute
-  ( decodeLocationAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Vector
-import Rattletrap.Type.LocationAttribute
-
-decodeLocationAttributeBits :: (Int, Int, Int) -> DecodeBits LocationAttribute
-decodeLocationAttributeBits version =
-  LocationAttribute <$> decodeVectorBits version
diff --git a/library/Rattletrap/Decode/Mark.hs b/library/Rattletrap/Decode/Mark.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Mark.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.Mark
-  ( decodeMark
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.Mark
-
-decodeMark :: Decode Mark
-decodeMark = Mark <$> decodeStr <*> decodeWord32le
diff --git a/library/Rattletrap/Decode/Message.hs b/library/Rattletrap/Decode/Message.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Message.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.Message
-  ( decodeMessage
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.Message
-
-decodeMessage :: Decode Message
-decodeMessage = Message <$> decodeWord32le <*> decodeStr <*> decodeStr
diff --git a/library/Rattletrap/Decode/MusicStingerAttribute.hs b/library/Rattletrap/Decode/MusicStingerAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/MusicStingerAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.MusicStingerAttribute
-  ( decodeMusicStingerAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.MusicStingerAttribute
-
-decodeMusicStingerAttributeBits :: DecodeBits MusicStingerAttribute
-decodeMusicStingerAttributeBits =
-  MusicStingerAttribute
-    <$> getBool
-    <*> decodeWord32leBits
-    <*> decodeWord8leBits
diff --git a/library/Rattletrap/Decode/PartyLeaderAttribute.hs b/library/Rattletrap/Decode/PartyLeaderAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/PartyLeaderAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Decode.PartyLeaderAttribute
-  ( decodePartyLeaderAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.RemoteId
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.PartyLeaderAttribute
-import Rattletrap.Type.Word8le
-
-decodePartyLeaderAttributeBits
-  :: (Int, Int, Int) -> DecodeBits PartyLeaderAttribute
-decodePartyLeaderAttributeBits version = do
-  systemId <- decodeWord8leBits
-  PartyLeaderAttribute systemId <$> decodeWhen
-    (systemId /= Word8le 0)
-    ((,) <$> decodeRemoteIdBits version systemId <*> decodeWord8leBits)
diff --git a/library/Rattletrap/Decode/PickupAttribute.hs b/library/Rattletrap/Decode/PickupAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/PickupAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Decode.PickupAttribute
-  ( decodePickupAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.PickupAttribute
-
-decodePickupAttributeBits :: DecodeBits PickupAttribute
-decodePickupAttributeBits = do
-  instigator <- getBool
-  PickupAttribute <$> decodeWhen instigator decodeWord32leBits <*> getBool
diff --git a/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs b/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.PlayerHistoryKeyAttribute
-  ( decodePlayerHistoryKeyAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.PlayerHistoryKeyAttribute
-
-decodePlayerHistoryKeyAttributeBits :: DecodeBits PlayerHistoryKeyAttribute
-decodePlayerHistoryKeyAttributeBits =
-  PlayerHistoryKeyAttribute <$> getBitsLE 14
diff --git a/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs b/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Decode.PrivateMatchSettingsAttribute
-  ( decodePrivateMatchSettingsAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.PrivateMatchSettingsAttribute
-
-decodePrivateMatchSettingsAttributeBits
-  :: DecodeBits PrivateMatchSettingsAttribute
-decodePrivateMatchSettingsAttributeBits =
-  PrivateMatchSettingsAttribute
-    <$> decodeStrBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeStrBits
-    <*> decodeStrBits
-    <*> getBool
diff --git a/library/Rattletrap/Decode/ProductAttribute.hs b/library/Rattletrap/Decode/ProductAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ProductAttribute.hs
+++ /dev/null
@@ -1,72 +0,0 @@
-module Rattletrap.Decode.ProductAttribute
-  ( decodeProductAttributesBits
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Decode.Word32le
-import Rattletrap.Decode.Word8le
-import Rattletrap.Decode.Str
-import Rattletrap.Type.Common
-import Rattletrap.Type.ProductAttribute
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-import Rattletrap.Type.Word8le
-
-import qualified Control.Monad as Monad
-import qualified Data.Map as Map
-
-decodeProductAttributesBits
-  :: (Int, Int, Int) -> Map Word32le Str -> DecodeBits [ProductAttribute]
-decodeProductAttributesBits version objectMap = do
-  size <- decodeWord8leBits
-  Monad.replicateM
-    (fromIntegral (word8leValue size))
-    (decodeProductAttributeBits version objectMap)
-
-decodeProductAttributeBits
-  :: (Int, Int, Int) -> Map Word32le Str -> DecodeBits ProductAttribute
-decodeProductAttributeBits version objectMap = do
-  flag <- getBool
-  objectId <- decodeWord32leBits
-  let maybeObjectName = Map.lookup objectId objectMap
-  value <- case fromStr <$> maybeObjectName of
-    Just "TAGame.ProductAttribute_Painted_TA" -> decodePainted version
-    Just "TAGame.ProductAttribute_SpecialEdition_TA" -> decodeSpecialEdition
-    Just "TAGame.ProductAttribute_TeamEdition_TA" -> decodeTeamEdition version
-    Just "TAGame.ProductAttribute_TitleID_TA" -> decodeTitle
-    Just "TAGame.ProductAttribute_UserColor_TA" -> decodeColor version
-    Just objectName ->
-      fail
-        ("unknown object name "
-        <> show objectName
-        <> " for ID "
-        <> show objectId
-        )
-    Nothing -> fail ("missing object name for ID " <> show objectId)
-  pure (ProductAttribute flag objectId maybeObjectName value)
-
-decodeSpecialEdition :: DecodeBits ProductAttributeValue
-decodeSpecialEdition = ProductAttributeValueSpecialEdition <$> getBitsLE 31
-
-decodePainted :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
-decodePainted version = if version >= (868, 18, 0)
-  then ProductAttributeValuePaintedNew <$> getBitsLE 31
-  else ProductAttributeValuePaintedOld <$> decodeCompressedWordBits 13
-
-decodeTeamEdition :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
-decodeTeamEdition version = if version >= (868, 18, 0)
-  then ProductAttributeValueTeamEditionNew <$> getBitsLE 31
-  else ProductAttributeValueTeamEditionOld <$> decodeCompressedWordBits 13
-
-decodeColor :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
-decodeColor version = if version >= (868, 23, 8)
-  then ProductAttributeValueUserColorNew <$> decodeWord32leBits
-  else do
-    hasValue <- getBool
-    ProductAttributeValueUserColorOld <$> decodeWhen hasValue (getBitsLE 31)
-
-decodeTitle :: DecodeBits ProductAttributeValue
-decodeTitle = ProductAttributeValueTitleId <$> decodeStrBits
diff --git a/library/Rattletrap/Decode/Property.hs b/library/Rattletrap/Decode/Property.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Property.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Decode.Property
-  ( decodeProperty
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.PropertyValue
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word64le
-import Rattletrap.Type.Property
-
-decodeProperty :: Decode Property
-decodeProperty = do
-  kind <- decodeStr
-  Property kind <$> decodeWord64le <*> decodePropertyValue decodeProperty kind
diff --git a/library/Rattletrap/Decode/PropertyValue.hs b/library/Rattletrap/Decode/PropertyValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/PropertyValue.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-module Rattletrap.Decode.PropertyValue
-  ( decodePropertyValue
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Dictionary
-import Rattletrap.Decode.Float32le
-import Rattletrap.Decode.Int32le
-import Rattletrap.Decode.List
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.Word64le
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.PropertyValue
-import Rattletrap.Type.Str
-
-decodePropertyValue :: Decode a -> Str -> Decode (PropertyValue a)
-decodePropertyValue getProperty kind = case fromStr kind of
-  "ArrayProperty" ->
-    PropertyValueArray <$> decodeList (decodeDictionary getProperty)
-  "BoolProperty" -> PropertyValueBool <$> decodeWord8le
-  "ByteProperty" -> do
-    k <- decodeStr
-    PropertyValueByte k
-      <$> decodeWhen (fromStr k /= "OnlinePlatform_Steam") decodeStr
-  "FloatProperty" -> PropertyValueFloat <$> decodeFloat32le
-  "IntProperty" -> PropertyValueInt <$> decodeInt32le
-  "NameProperty" -> PropertyValueName <$> decodeStr
-  "QWordProperty" -> PropertyValueQWord <$> decodeWord64le
-  "StrProperty" -> PropertyValueStr <$> decodeStr
-  _ -> fail ("don't know how to read property value " <> show kind)
diff --git a/library/Rattletrap/Decode/QWordAttribute.hs b/library/Rattletrap/Decode/QWordAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/QWordAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.QWordAttribute
-  ( decodeQWordAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word64le
-import Rattletrap.Type.QWordAttribute
-
-decodeQWordAttributeBits :: DecodeBits QWordAttribute
-decodeQWordAttributeBits = QWordAttribute <$> decodeWord64leBits
diff --git a/library/Rattletrap/Decode/Quaternion.hs b/library/Rattletrap/Decode/Quaternion.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Quaternion.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rattletrap.Decode.Quaternion
-  ( decodeQuaternionBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Quaternion
-
-decodeQuaternionBits :: DecodeBits Quaternion
-decodeQuaternionBits =
-  toQuaternion <$> decodeComponent <*> decodePart <*> decodePart <*> decodePart
-
-decodeComponent :: DecodeBits Component
-decodeComponent = do
-  x <- decodeCompressedWordBits 3
-  case compressedWordValue x of
-    0 -> pure ComponentX
-    1 -> pure ComponentY
-    2 -> pure ComponentZ
-    3 -> pure ComponentW
-    y -> fail ("invalid component: " <> show y)
-
-decodePart :: DecodeBits Double
-decodePart = decompressPart <$> decodeCompressedWordBits maxCompressedValue
diff --git a/library/Rattletrap/Decode/RemoteId.hs b/library/Rattletrap/Decode/RemoteId.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/RemoteId.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-module Rattletrap.Decode.RemoteId
-  ( decodeRemoteIdBits
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word64le
-import Rattletrap.Type.RemoteId
-import Rattletrap.Type.Word64le
-import Rattletrap.Type.Word8le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.ByteString as Bytes
-import qualified Data.Text as Text
-import qualified Data.Text.Encoding as Text
-import qualified Data.Word as Word
-
-decodeRemoteIdBits :: (Int, Int, Int) -> Word8le -> DecodeBits RemoteId
-decodeRemoteIdBits version systemId = case word8leValue systemId of
-  0 -> RemoteIdSplitscreen <$> getBitsLE 24
-  1 -> RemoteIdSteam <$> decodeWord64leBits
-  2 -> RemoteIdPlayStation <$> decodePsName <*> decodePsBytes version
-  4 -> RemoteIdXbox <$> decodeWord64leBits
-  6 -> do
-    (a, b, c, d) <- getWord256
-    pure $ RemoteIdSwitch a b c d
-  7 -> if version >= (868, 24, 10)
-    then RemoteIdPsyNet . Left <$> decodeWord64leBits
-    else RemoteIdPsyNet . Right <$> getWord256
-  _ -> fail ("unknown system id " <> show systemId)
-
-decodePsName :: DecodeBits Text.Text
-decodePsName = fmap
-  (Text.dropWhileEnd (== '\x00') . Text.decodeLatin1 . reverseBytes)
-  (getByteStringBits 16)
-
-decodePsBytes :: (Int, Int, Int) -> DecodeBits [Word.Word8]
-decodePsBytes version = Bytes.unpack
-  <$> getByteStringBits (if version >= (868, 20, 1) then 24 else 16)
-
-getWord256 :: DecodeBits (Word64le, Word64le, Word64le, Word64le)
-getWord256 = do
-  a <- decodeWord64leBits
-  b <- decodeWord64leBits
-  c <- decodeWord64leBits
-  d <- decodeWord64leBits
-  pure (a, b, c, d)
diff --git a/library/Rattletrap/Decode/Replay.hs b/library/Rattletrap/Decode/Replay.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Replay.hs
+++ /dev/null
@@ -1,66 +0,0 @@
-module Rattletrap.Decode.Replay
-  ( decodeReplay
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Content
-import Rattletrap.Decode.Header
-import Rattletrap.Decode.Section
-import Rattletrap.Encode.Content
-import Rattletrap.Type.Content
-import Rattletrap.Type.Dictionary
-import Rattletrap.Type.Header
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Property
-import Rattletrap.Type.PropertyValue
-import Rattletrap.Type.Replay
-import Rattletrap.Type.Section
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-decodeReplay :: Bool -> Decode Replay
-decodeReplay fast = do
-  header <- decodeSection decodeHeader
-  content <- if fast
-    then pure $ toSection putContent defaultContent
-    else
-      let body = sectionBody header
-      in
-        decodeSection $ decodeContent
-          (getVersion body)
-          (getNumFrames body)
-          (getMaxChannels body)
-  pure $ Replay header content
-
-getVersion :: Header -> (Int, Int, Int)
-getVersion header =
-  ( fromIntegral (word32leValue (headerEngineVersion header))
-  , fromIntegral (word32leValue (headerLicenseeVersion header))
-  , getPatchVersion header
-  )
-
-getPatchVersion :: Header -> Int
-getPatchVersion header = case headerPatchVersion header of
-  Just version -> fromIntegral (word32leValue version)
-  Nothing ->
-    case dictionaryLookup (toStr "MatchType") (headerProperties header) of
-      -- This is an ugly, ugly hack to handle replays from season 2 of RLCS.
-      -- See `decodeSpawnedReplicationBits` and #85.
-      Just Property { propertyValue = PropertyValueName str }
-        | fromStr str == "Lan" -> -1
-      _ -> 0
-
-getNumFrames :: Header -> Int
-getNumFrames header =
-  case dictionaryLookup (toStr "NumFrames") (headerProperties header) of
-    Just (Property _ _ (PropertyValueInt numFrames)) ->
-      fromIntegral (int32leValue numFrames)
-    _ -> 0
-
-getMaxChannels :: Header -> Word
-getMaxChannels header =
-  case dictionaryLookup (toStr "MaxChannels") (headerProperties header) of
-    Just (Property _ _ (PropertyValueInt numFrames)) ->
-      fromIntegral (int32leValue numFrames)
-    _ -> 1023
diff --git a/library/Rattletrap/Decode/Replication.hs b/library/Rattletrap/Decode/Replication.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Replication.hs
+++ /dev/null
@@ -1,45 +0,0 @@
-module Rattletrap.Decode.Replication
-  ( decodeReplicationsBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Decode.ReplicationValue
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Replication
-import Rattletrap.Type.Word32le
-
-import qualified Control.Monad.Trans.Class as Trans
-import qualified Control.Monad.Trans.State as State
-import qualified Data.Map as Map
-
-decodeReplicationsBits
-  :: (Int, Int, Int)
-  -> Word
-  -> ClassAttributeMap
-  -> State.StateT
-       (Map.Map CompressedWord Word32le)
-       DecodeBits
-       [Replication]
-decodeReplicationsBits version limit classes = do
-  hasReplication <- Trans.lift getBool
-  if hasReplication
-    then
-      (:)
-      <$> decodeReplicationBits version limit classes
-      <*> decodeReplicationsBits version limit classes
-    else pure []
-
-decodeReplicationBits
-  :: (Int, Int, Int)
-  -> Word
-  -> ClassAttributeMap
-  -> State.StateT
-       (Map.Map CompressedWord Word32le)
-       DecodeBits
-       Replication
-decodeReplicationBits version limit classes = do
-  actor <- Trans.lift (decodeCompressedWordBits limit)
-  Replication actor <$> decodeReplicationValueBits version classes actor
diff --git a/library/Rattletrap/Decode/ReplicationValue.hs b/library/Rattletrap/Decode/ReplicationValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ReplicationValue.hs
+++ /dev/null
@@ -1,45 +0,0 @@
-module Rattletrap.Decode.ReplicationValue
-  ( decodeReplicationValueBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.DestroyedReplication
-import Rattletrap.Decode.SpawnedReplication
-import Rattletrap.Decode.UpdatedReplication
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.ReplicationValue
-import Rattletrap.Type.Word32le
-
-import qualified Control.Monad.Trans.Class as Trans
-import qualified Control.Monad.Trans.State as State
-import qualified Data.Map as Map
-
-decodeReplicationValueBits
-  :: (Int, Int, Int)
-  -> ClassAttributeMap
-  -> CompressedWord
-  -> State.StateT
-       (Map.Map CompressedWord Word32le)
-       DecodeBits
-       ReplicationValue
-decodeReplicationValueBits version classAttributeMap actorId = do
-  actorMap <- State.get
-  isOpen <- Trans.lift getBool
-  if isOpen
-    then do
-      isNew <- Trans.lift getBool
-      if isNew
-        then
-          ReplicationValueSpawned
-            <$> decodeSpawnedReplicationBits version classAttributeMap actorId
-        else ReplicationValueUpdated <$> Trans.lift
-          (decodeUpdatedReplicationBits
-            version
-            classAttributeMap
-            actorMap
-            actorId
-          )
-    else ReplicationValueDestroyed
-      <$> Trans.lift decodeDestroyedReplicationBits
diff --git a/library/Rattletrap/Decode/ReservationAttribute.hs b/library/Rattletrap/Decode/ReservationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/ReservationAttribute.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Rattletrap.Decode.ReservationAttribute
-  ( decodeReservationAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Decode.Str
-import Rattletrap.Decode.UniqueIdAttribute
-import Rattletrap.Type.ReservationAttribute
-import Rattletrap.Type.UniqueIdAttribute
-import Rattletrap.Type.Word8le
-
-decodeReservationAttributeBits
-  :: (Int, Int, Int) -> DecodeBits ReservationAttribute
-decodeReservationAttributeBits version = do
-  number <- decodeCompressedWordBits 7
-  uniqueId <- decodeUniqueIdAttributeBits version
-  ReservationAttribute number uniqueId
-    <$> decodeWhen
-          (uniqueIdAttributeSystemId uniqueId /= Word8le 0)
-          decodeStrBits
-    <*> getBool
-    <*> getBool
-    <*> decodeWhen (version >= (868, 12, 0)) (getWord8Bits 6)
diff --git a/library/Rattletrap/Decode/RigidBodyStateAttribute.hs b/library/Rattletrap/Decode/RigidBodyStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/RigidBodyStateAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Decode.RigidBodyStateAttribute
-  ( decodeRigidBodyStateAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Rotation
-import Rattletrap.Decode.Vector
-import Rattletrap.Type.RigidBodyStateAttribute
-
-decodeRigidBodyStateAttributeBits
-  :: (Int, Int, Int) -> DecodeBits RigidBodyStateAttribute
-decodeRigidBodyStateAttributeBits version = do
-  sleeping <- getBool
-  RigidBodyStateAttribute sleeping
-    <$> decodeVectorBits version
-    <*> decodeRotationBits version
-    <*> decodeWhen (not sleeping) (decodeVectorBits version)
-    <*> decodeWhen (not sleeping) (decodeVectorBits version)
diff --git a/library/Rattletrap/Decode/Rotation.hs b/library/Rattletrap/Decode/Rotation.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Rotation.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Rotation
-  ( decodeRotationBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWordVector
-import Rattletrap.Decode.Quaternion
-import Rattletrap.Type.Rotation
-
-decodeRotationBits :: (Int, Int, Int) -> DecodeBits Rotation
-decodeRotationBits version = if version >= (868, 22, 7)
-  then RotationQuaternion <$> decodeQuaternionBits
-  else RotationCompressedWordVector <$> decodeCompressedWordVectorBits
diff --git a/library/Rattletrap/Decode/Section.hs b/library/Rattletrap/Decode/Section.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Section.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rattletrap.Decode.Section
-  ( decodeSection
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.Section
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Crc
-
-import qualified Control.Monad as Monad
-
-decodeSection :: Decode a -> Decode (Section a)
-decodeSection getBody = do
-  size <- decodeWord32le
-  crc <- decodeWord32le
-  rawBody <- getByteString (fromIntegral (word32leValue size))
-  let actualCrc = Word32le (getCrc32 rawBody)
-  Monad.when (actualCrc /= crc) (fail (crcMessage actualCrc crc))
-  body <- either fail pure (runDecode getBody rawBody)
-  pure (Section size crc body)
-
-crcMessage :: Word32le -> Word32le -> String
-crcMessage actual expected = unwords
-  ["actual CRC", show actual, "does not match expected CRC", show expected]
diff --git a/library/Rattletrap/Decode/SpawnedReplication.hs b/library/Rattletrap/Decode/SpawnedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/SpawnedReplication.hs
+++ /dev/null
@@ -1,70 +0,0 @@
-module Rattletrap.Decode.SpawnedReplication
-  ( decodeSpawnedReplicationBits
-  )
-where
-
-import Data.Semigroup ((<>))
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Initialization
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.SpawnedReplication
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-import qualified Control.Monad.Trans.Class as Trans
-import qualified Control.Monad.Trans.State as State
-import qualified Data.Map as Map
-
-decodeSpawnedReplicationBits
-  :: (Int, Int, Int)
-  -> ClassAttributeMap
-  -> CompressedWord
-  -> State.StateT
-       (Map.Map CompressedWord Word32le)
-       DecodeBits
-       SpawnedReplication
-decodeSpawnedReplicationBits version classAttributeMap actorId = do
-  flag <- Trans.lift getBool
-  nameIndex <- decodeWhen
-    (version >= (868, 14, 0))
-    (Trans.lift decodeWord32leBits)
-  name <- either fail pure (lookupName classAttributeMap nameIndex)
-  objectId <- Trans.lift decodeWord32leBits
-  State.modify (Map.insert actorId objectId)
-  objectName <- either fail pure (lookupObjectName classAttributeMap objectId)
-  className <- either fail pure (lookupClassName objectName)
-  let hasLocation = classHasLocation className
-  let hasRotation = classHasRotation className
-  initialization <- Trans.lift
-    (decodeInitializationBits version hasLocation hasRotation)
-  pure
-    (SpawnedReplication
-      flag
-      nameIndex
-      name
-      objectId
-      objectName
-      className
-      initialization
-    )
-
-lookupName :: ClassAttributeMap -> Maybe Word32le -> Either String (Maybe Str)
-lookupName classAttributeMap maybeNameIndex = case maybeNameIndex of
-  Nothing -> Right Nothing
-  Just nameIndex ->
-    case getName (classAttributeMapNameMap classAttributeMap) nameIndex of
-      Nothing -> Left ("could not get name for index " <> show nameIndex)
-      Just name -> Right (Just name)
-
-lookupObjectName :: ClassAttributeMap -> Word32le -> Either String Str
-lookupObjectName classAttributeMap objectId =
-  case getObjectName (classAttributeMapObjectMap classAttributeMap) objectId of
-    Nothing -> Left ("could not get object name for id " <> show objectId)
-    Just objectName -> Right objectName
-
-lookupClassName :: Str -> Either String Str
-lookupClassName objectName = case getClassName objectName of
-  Nothing -> Left ("could not get class name for object " <> show objectName)
-  Just className -> Right className
diff --git a/library/Rattletrap/Decode/StatEventAttribute.hs b/library/Rattletrap/Decode/StatEventAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/StatEventAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Decode.StatEventAttribute
-  ( decodeStatEventAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Type.StatEventAttribute
-
-decodeStatEventAttributeBits :: DecodeBits StatEventAttribute
-decodeStatEventAttributeBits =
-  StatEventAttribute <$> getBool <*> decodeInt32leBits
diff --git a/library/Rattletrap/Decode/Str.hs b/library/Rattletrap/Decode/Str.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Str.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-module Rattletrap.Decode.Str
-  ( decodeStr
-  , decodeStrBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Int32le
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Str
-import Rattletrap.Utility.Bytes
-
-import qualified Data.ByteString as Bytes
-import qualified Data.Text as Text
-import qualified Data.Text.Encoding as Text
-
-decodeStr :: Decode Str
-decodeStr = do
-  rawSize <- decodeInt32le
-  bytes <- getByteString (normalizeTextSize rawSize)
-  pure (Str (dropNull (getTextDecoder rawSize bytes)))
-
-decodeStrBits :: DecodeBits Str
-decodeStrBits = do
-  rawSize <- decodeInt32leBits
-  bytes <- getByteStringBits (normalizeTextSize rawSize)
-  pure (Str (dropNull (getTextDecoder rawSize (reverseBytes bytes))))
-
-normalizeTextSize :: Integral a => Int32le -> a
-normalizeTextSize size = case int32leValue size of
-  0x05000000 -> 8
-  x -> if x < 0 then (-2 * fromIntegral x) else fromIntegral x
-
-getTextDecoder :: Int32le -> Bytes.ByteString -> Text.Text
-getTextDecoder size bytes =
-  let
-    decode =
-      if size < Int32le 0 then Text.decodeUtf16LE else Text.decodeLatin1
-  in decode bytes
-
-dropNull :: Text.Text -> Text.Text
-dropNull = Text.dropWhileEnd (== '\x00')
diff --git a/library/Rattletrap/Decode/StringAttribute.hs b/library/Rattletrap/Decode/StringAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/StringAttribute.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Decode.StringAttribute
-  ( decodeStringAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Str
-import Rattletrap.Type.StringAttribute
-
-decodeStringAttributeBits :: DecodeBits StringAttribute
-decodeStringAttributeBits = StringAttribute <$> decodeStrBits
diff --git a/library/Rattletrap/Decode/TeamPaintAttribute.hs b/library/Rattletrap/Decode/TeamPaintAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/TeamPaintAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Decode.TeamPaintAttribute
-  ( decodeTeamPaintAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.TeamPaintAttribute
-
-decodeTeamPaintAttributeBits :: DecodeBits TeamPaintAttribute
-decodeTeamPaintAttributeBits =
-  TeamPaintAttribute
-    <$> decodeWord8leBits
-    <*> decodeWord8leBits
-    <*> decodeWord8leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
diff --git a/library/Rattletrap/Decode/TitleAttribute.hs b/library/Rattletrap/Decode/TitleAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/TitleAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Decode.TitleAttribute
-  ( decodeTitleAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Word32le
-import Rattletrap.Type.TitleAttribute
-
-decodeTitleAttributeBits :: DecodeBits TitleAttribute
-decodeTitleAttributeBits =
-  TitleAttribute
-    <$> getBool
-    <*> getBool
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> decodeWord32leBits
-    <*> getBool
diff --git a/library/Rattletrap/Decode/UniqueIdAttribute.hs b/library/Rattletrap/Decode/UniqueIdAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/UniqueIdAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Decode.UniqueIdAttribute
-  ( decodeUniqueIdAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.RemoteId
-import Rattletrap.Decode.Word8le
-import Rattletrap.Type.UniqueIdAttribute
-
-decodeUniqueIdAttributeBits :: (Int, Int, Int) -> DecodeBits UniqueIdAttribute
-decodeUniqueIdAttributeBits version = do
-  systemId <- decodeWord8leBits
-  UniqueIdAttribute systemId
-    <$> decodeRemoteIdBits version systemId
-    <*> decodeWord8leBits
diff --git a/library/Rattletrap/Decode/UpdatedReplication.hs b/library/Rattletrap/Decode/UpdatedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/UpdatedReplication.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Rattletrap.Decode.UpdatedReplication
-  ( decodeUpdatedReplicationBits
-  )
-where
-
-import Rattletrap.Decode.Attribute
-import Rattletrap.Decode.Common
-import Rattletrap.Type.ClassAttributeMap
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.UpdatedReplication
-import Rattletrap.Type.Word32le
-
-import qualified Data.Map as Map
-
-decodeUpdatedReplicationBits
-  :: (Int, Int, Int)
-  -> ClassAttributeMap
-  -> Map.Map CompressedWord Word32le
-  -> CompressedWord
-  -> DecodeBits UpdatedReplication
-decodeUpdatedReplicationBits version classes actors actor =
-  UpdatedReplication <$> decodeAttributesBits version classes actors actor
diff --git a/library/Rattletrap/Decode/Vector.hs b/library/Rattletrap/Decode/Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Vector.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module Rattletrap.Decode.Vector
-  ( decodeVectorBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.CompressedWord
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Vector
-
-decodeVectorBits :: (Int, Int, Int) -> DecodeBits Vector
-decodeVectorBits version = do
-  size <- decodeCompressedWordBits (if version >= (868, 22, 7) then 21 else 19)
-  let
-    limit = getLimit size
-    bias = getBias size
-  Vector size bias
-    <$> fmap (fromDelta bias) (decodeCompressedWordBits limit)
-    <*> fmap (fromDelta bias) (decodeCompressedWordBits limit)
-    <*> fmap (fromDelta bias) (decodeCompressedWordBits limit)
-
-getLimit :: CompressedWord -> Word
-getLimit = (2 ^) . (+ 2) . compressedWordValue
-
-getBias :: CompressedWord -> Word
-getBias = (2 ^) . (+ 1) . compressedWordValue
-
-fromDelta :: Word -> CompressedWord -> Int
-fromDelta bias x = fromIntegral (compressedWordValue x) - fromIntegral bias
diff --git a/library/Rattletrap/Decode/WeldedInfoAttribute.hs b/library/Rattletrap/Decode/WeldedInfoAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/WeldedInfoAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Decode.WeldedInfoAttribute
-  ( decodeWeldedInfoAttributeBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Decode.Float32le
-import Rattletrap.Decode.Int32le
-import Rattletrap.Decode.Int8Vector
-import Rattletrap.Decode.Vector
-import Rattletrap.Type.WeldedInfoAttribute
-
-decodeWeldedInfoAttributeBits
-  :: (Int, Int, Int) -> DecodeBits WeldedInfoAttribute
-decodeWeldedInfoAttributeBits version =
-  WeldedInfoAttribute
-    <$> getBool
-    <*> decodeInt32leBits
-    <*> decodeVectorBits version
-    <*> decodeFloat32leBits
-    <*> decodeInt8VectorBits
diff --git a/library/Rattletrap/Decode/Word32le.hs b/library/Rattletrap/Decode/Word32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Word32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Word32le
-  ( decodeWord32le
-  , decodeWord32leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Word32le
-
-decodeWord32le :: Decode Word32le
-decodeWord32le = Word32le <$> getWord32le
-
-decodeWord32leBits :: DecodeBits Word32le
-decodeWord32leBits = toBits decodeWord32le 4
diff --git a/library/Rattletrap/Decode/Word64le.hs b/library/Rattletrap/Decode/Word64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Word64le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Word64le
-  ( decodeWord64le
-  , decodeWord64leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Word64le
-
-decodeWord64le :: Decode Word64le
-decodeWord64le = Word64le <$> getWord64le
-
-decodeWord64leBits :: DecodeBits Word64le
-decodeWord64leBits = toBits decodeWord64le 8
diff --git a/library/Rattletrap/Decode/Word8le.hs b/library/Rattletrap/Decode/Word8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Decode/Word8le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Decode.Word8le
-  ( decodeWord8le
-  , decodeWord8leBits
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Type.Word8le
-
-decodeWord8le :: Decode Word8le
-decodeWord8le = Word8le <$> getWord8
-
-decodeWord8leBits :: DecodeBits Word8le
-decodeWord8leBits = toBits decodeWord8le 1
diff --git a/library/Rattletrap/Encode/AppliedDamageAttribute.hs b/library/Rattletrap/Encode/AppliedDamageAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/AppliedDamageAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Encode.AppliedDamageAttribute
-  ( putAppliedDamageAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Encode.Vector
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.AppliedDamageAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putAppliedDamageAttribute :: AppliedDamageAttribute -> BinaryBits.BitPut ()
-putAppliedDamageAttribute appliedDamageAttribute = do
-  putWord8Bits (appliedDamageAttributeUnknown1 appliedDamageAttribute)
-  putVector (appliedDamageAttributeLocation appliedDamageAttribute)
-  putInt32Bits (appliedDamageAttributeUnknown3 appliedDamageAttribute)
-  putInt32Bits (appliedDamageAttributeUnknown4 appliedDamageAttribute)
diff --git a/library/Rattletrap/Encode/Attribute.hs b/library/Rattletrap/Encode/Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Attribute.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rattletrap.Encode.Attribute
-  ( putAttributes
-  )
-where
-
-import Rattletrap.Encode.AttributeValue
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Type.Attribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putAttributes :: [Attribute] -> BinaryBits.BitPut ()
-putAttributes attributes = case attributes of
-  [] -> BinaryBits.putBool False
-  [attribute] -> do
-    putAttribute attribute
-    BinaryBits.putBool False
-  first : rest -> do
-    putAttribute first
-    putAttributes rest
-
-putAttribute :: Attribute -> BinaryBits.BitPut ()
-putAttribute attribute = do
-  BinaryBits.putBool True
-  putCompressedWord (attributeId attribute)
-  putAttributeValue (attributeValue attribute)
diff --git a/library/Rattletrap/Encode/AttributeMapping.hs b/library/Rattletrap/Encode/AttributeMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/AttributeMapping.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.AttributeMapping
-  ( putAttributeMapping
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.AttributeMapping
-
-import qualified Data.Binary as Binary
-
-putAttributeMapping :: AttributeMapping -> Binary.Put
-putAttributeMapping attributeMapping = do
-  putWord32 (attributeMappingObjectId attributeMapping)
-  putWord32 (attributeMappingStreamId attributeMapping)
diff --git a/library/Rattletrap/Encode/AttributeValue.hs b/library/Rattletrap/Encode/AttributeValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/AttributeValue.hs
+++ /dev/null
@@ -1,81 +0,0 @@
-module Rattletrap.Encode.AttributeValue
-  ( putAttributeValue
-  )
-where
-
-import Rattletrap.Encode.AppliedDamageAttribute
-import Rattletrap.Encode.BooleanAttribute
-import Rattletrap.Encode.ByteAttribute
-import Rattletrap.Encode.CamSettingsAttribute
-import Rattletrap.Encode.ClubColorsAttribute
-import Rattletrap.Encode.DamageStateAttribute
-import Rattletrap.Encode.DemolishAttribute
-import Rattletrap.Encode.EnumAttribute
-import Rattletrap.Encode.ExplosionAttribute
-import Rattletrap.Encode.ExtendedExplosionAttribute
-import Rattletrap.Encode.FlaggedIntAttribute
-import Rattletrap.Encode.FlaggedByteAttribute
-import Rattletrap.Encode.FloatAttribute
-import Rattletrap.Encode.GameModeAttribute
-import Rattletrap.Encode.Int64Attribute
-import Rattletrap.Encode.IntAttribute
-import Rattletrap.Encode.LoadoutAttribute
-import Rattletrap.Encode.LoadoutOnlineAttribute
-import Rattletrap.Encode.LoadoutsAttribute
-import Rattletrap.Encode.LoadoutsOnlineAttribute
-import Rattletrap.Encode.LocationAttribute
-import Rattletrap.Encode.MusicStingerAttribute
-import Rattletrap.Encode.PartyLeaderAttribute
-import Rattletrap.Encode.PickupAttribute
-import Rattletrap.Encode.PlayerHistoryKeyAttribute
-import Rattletrap.Encode.PrivateMatchSettingsAttribute
-import Rattletrap.Encode.QWordAttribute
-import Rattletrap.Encode.ReservationAttribute
-import Rattletrap.Encode.RigidBodyStateAttribute
-import Rattletrap.Encode.StatEventAttribute
-import Rattletrap.Encode.StringAttribute
-import Rattletrap.Encode.TeamPaintAttribute
-import Rattletrap.Encode.TitleAttribute
-import Rattletrap.Encode.UniqueIdAttribute
-import Rattletrap.Encode.WeldedInfoAttribute
-import Rattletrap.Type.AttributeValue
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putAttributeValue :: AttributeValue -> BinaryBits.BitPut ()
-putAttributeValue value = case value of
-  AttributeValueAppliedDamage x -> putAppliedDamageAttribute x
-  AttributeValueBoolean x -> putBooleanAttribute x
-  AttributeValueByte x -> putByteAttribute x
-  AttributeValueCamSettings x -> putCamSettingsAttribute x
-  AttributeValueClubColors x -> putClubColorsAttribute x
-  AttributeValueDamageState x -> putDamageStateAttribute x
-  AttributeValueDemolish x -> putDemolishAttribute x
-  AttributeValueEnum x -> putEnumAttribute x
-  AttributeValueExplosion x -> putExplosionAttribute x
-  AttributeValueExtendedExplosion x -> putExtendedExplosionAttribute x
-  AttributeValueFlaggedInt x -> putFlaggedIntAttribute x
-  AttributeValueFlaggedByte x -> putFlaggedByteAttribute x
-  AttributeValueFloat x -> putFloatAttribute x
-  AttributeValueGameMode x -> putGameModeAttribute x
-  AttributeValueInt x -> putIntAttribute x
-  AttributeValueInt64 x -> putInt64Attribute x
-  AttributeValueLoadout x -> putLoadoutAttribute x
-  AttributeValueLoadoutOnline x -> putLoadoutOnlineAttribute x
-  AttributeValueLoadouts x -> putLoadoutsAttribute x
-  AttributeValueLoadoutsOnline x -> putLoadoutsOnlineAttribute x
-  AttributeValueLocation x -> putLocationAttribute x
-  AttributeValueMusicStinger x -> putMusicStingerAttribute x
-  AttributeValuePartyLeader x -> putPartyLeaderAttribute x
-  AttributeValuePickup x -> putPickupAttribute x
-  AttributeValuePlayerHistoryKey x -> putPlayerHistoryKeyAttribute x
-  AttributeValuePrivateMatchSettings x -> putPrivateMatchSettingsAttribute x
-  AttributeValueQWord x -> putQWordAttribute x
-  AttributeValueReservation x -> putReservationAttribute x
-  AttributeValueRigidBodyState x -> putRigidBodyStateAttribute x
-  AttributeValueStatEvent x -> putStatEventAttribute x
-  AttributeValueString x -> putStringAttribute x
-  AttributeValueTeamPaint x -> putTeamPaintAttribute x
-  AttributeValueTitle x -> putTitleAttribute x
-  AttributeValueUniqueId x -> putUniqueIdAttribute x
-  AttributeValueWeldedInfo x -> putWeldedInfoAttribute x
diff --git a/library/Rattletrap/Encode/BooleanAttribute.hs b/library/Rattletrap/Encode/BooleanAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/BooleanAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Encode.BooleanAttribute
-  ( putBooleanAttribute
-  )
-where
-
-import Rattletrap.Type.BooleanAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putBooleanAttribute :: BooleanAttribute -> BinaryBits.BitPut ()
-putBooleanAttribute booleanAttribute =
-  BinaryBits.putBool (booleanAttributeValue booleanAttribute)
diff --git a/library/Rattletrap/Encode/ByteAttribute.hs b/library/Rattletrap/Encode/ByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ByteAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.ByteAttribute
-  ( putByteAttribute
-  )
-where
-
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.ByteAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putByteAttribute :: ByteAttribute -> BinaryBits.BitPut ()
-putByteAttribute byteAttribute =
-  putWord8Bits (byteAttributeValue byteAttribute)
diff --git a/library/Rattletrap/Encode/Cache.hs b/library/Rattletrap/Encode/Cache.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Cache.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Encode.Cache
-  ( putCache
-  )
-where
-
-import Rattletrap.Encode.AttributeMapping
-import Rattletrap.Encode.List
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Cache
-
-import qualified Data.Binary as Binary
-
-putCache :: Cache -> Binary.Put
-putCache cache = do
-  putWord32 (cacheClassId cache)
-  putWord32 (cacheParentCacheId cache)
-  putWord32 (cacheCacheId cache)
-  putList putAttributeMapping (cacheAttributeMappings cache)
diff --git a/library/Rattletrap/Encode/CamSettingsAttribute.hs b/library/Rattletrap/Encode/CamSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/CamSettingsAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.CamSettingsAttribute
-  ( putCamSettingsAttribute
-  )
-where
-
-import Rattletrap.Encode.Float32le
-import Rattletrap.Type.CamSettingsAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putCamSettingsAttribute :: CamSettingsAttribute -> BinaryBits.BitPut ()
-putCamSettingsAttribute camSettingsAttribute = do
-  putFloat32Bits (camSettingsAttributeFov camSettingsAttribute)
-  putFloat32Bits (camSettingsAttributeHeight camSettingsAttribute)
-  putFloat32Bits (camSettingsAttributeAngle camSettingsAttribute)
-  putFloat32Bits (camSettingsAttributeDistance camSettingsAttribute)
-  putFloat32Bits (camSettingsAttributeStiffness camSettingsAttribute)
-  putFloat32Bits (camSettingsAttributeSwivelSpeed camSettingsAttribute)
-  case camSettingsAttributeTransitionSpeed camSettingsAttribute of
-    Nothing -> pure ()
-    Just transitionSpeed -> putFloat32Bits transitionSpeed
diff --git a/library/Rattletrap/Encode/ClassMapping.hs b/library/Rattletrap/Encode/ClassMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ClassMapping.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Encode.ClassMapping
-  ( putClassMapping
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.ClassMapping
-
-import qualified Data.Binary as Binary
-
-putClassMapping :: ClassMapping -> Binary.Put
-putClassMapping classMapping = do
-  putText (classMappingName classMapping)
-  putWord32 (classMappingStreamId classMapping)
diff --git a/library/Rattletrap/Encode/ClubColorsAttribute.hs b/library/Rattletrap/Encode/ClubColorsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ClubColorsAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.ClubColorsAttribute
-  ( putClubColorsAttribute
-  )
-where
-
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.ClubColorsAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putClubColorsAttribute :: ClubColorsAttribute -> BinaryBits.BitPut ()
-putClubColorsAttribute clubColorsAttribute = do
-  BinaryBits.putBool (clubColorsAttributeBlueFlag clubColorsAttribute)
-  putWord8Bits (clubColorsAttributeBlueColor clubColorsAttribute)
-  BinaryBits.putBool (clubColorsAttributeOrangeFlag clubColorsAttribute)
-  putWord8Bits (clubColorsAttributeOrangeColor clubColorsAttribute)
diff --git a/library/Rattletrap/Encode/Common.hs b/library/Rattletrap/Encode/Common.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Common.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Rattletrap.Encode.Common
-  ( putBitsLE
-  )
-where
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Bits as Bits
-
-putBitsLE :: Bits.Bits a => Int -> a -> BinaryBits.BitPut ()
-putBitsLE size x = mapM_ (BinaryBits.putBool . Bits.testBit x) [0 .. size - 1]
diff --git a/library/Rattletrap/Encode/CompressedWord.hs b/library/Rattletrap/Encode/CompressedWord.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/CompressedWord.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-module Rattletrap.Encode.CompressedWord
-  ( putCompressedWord
-  )
-where
-
-import Rattletrap.Type.CompressedWord
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Bits as Bits
-
-putCompressedWord :: CompressedWord -> BinaryBits.BitPut ()
-putCompressedWord compressedWord =
-  let
-    limit = compressedWordLimit compressedWord
-    value = compressedWordValue compressedWord
-    maxBits = getMaxBits limit
-  in putCompressedWordStep limit value maxBits 0 0
-
-putCompressedWordStep
-  :: Word -> Word -> Int -> Int -> Word -> BinaryBits.BitPut ()
-putCompressedWordStep limit value maxBits position soFar =
-  if position < maxBits
-    then do
-      let x = Bits.shiftL 1 position :: Word
-      if maxBits > 1 && position == maxBits - 1 && soFar + x > limit
-        then pure ()
-        else do
-          let bit = Bits.testBit value position
-          BinaryBits.putBool bit
-          let delta = if bit then x else 0
-          putCompressedWordStep
-            limit
-            value
-            maxBits
-            (position + 1)
-            (soFar + delta)
-    else pure ()
-
-getMaxBits :: Word -> Int
-getMaxBits x =
-  let
-    n :: Int
-    n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
-  in if x < 1024 && x == 2 ^ n then n + 1 else n
diff --git a/library/Rattletrap/Encode/CompressedWordVector.hs b/library/Rattletrap/Encode/CompressedWordVector.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/CompressedWordVector.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Encode.CompressedWordVector
-  ( putCompressedWordVector
-  )
-where
-
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Type.CompressedWordVector
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putCompressedWordVector :: CompressedWordVector -> BinaryBits.BitPut ()
-putCompressedWordVector compressedWordVector = do
-  putCompressedWord (compressedWordVectorX compressedWordVector)
-  putCompressedWord (compressedWordVectorY compressedWordVector)
-  putCompressedWord (compressedWordVectorZ compressedWordVector)
diff --git a/library/Rattletrap/Encode/Content.hs b/library/Rattletrap/Encode/Content.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Content.hs
+++ /dev/null
@@ -1,43 +0,0 @@
-module Rattletrap.Encode.Content
-  ( putContent
-  )
-where
-
-import Rattletrap.Encode.Cache
-import Rattletrap.Encode.ClassMapping
-import Rattletrap.Encode.Frame
-import Rattletrap.Encode.KeyFrame
-import Rattletrap.Encode.List
-import Rattletrap.Encode.Mark
-import Rattletrap.Encode.Message
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Content
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putContent :: Content -> Binary.Put
-putContent content = do
-  putList putText (contentLevels content)
-  putList putKeyFrame (contentKeyFrames content)
-  let streamSize = contentStreamSize content
-  putWord32 streamSize
-  let
-    stream = LazyBytes.toStrict
-      (Binary.runPut (BinaryBits.runBitPut (putFrames (contentFrames content)))
-      )
-  Binary.putByteString
-    (reverseBytes (padBytes (word32leValue 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)
-  mapM_ Binary.putWord8 (contentUnknown content)
diff --git a/library/Rattletrap/Encode/DamageStateAttribute.hs b/library/Rattletrap/Encode/DamageStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/DamageStateAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Encode.DamageStateAttribute
-  ( putDamageStateAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Encode.Vector
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.DamageStateAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putDamageStateAttribute :: DamageStateAttribute -> BinaryBits.BitPut ()
-putDamageStateAttribute damageStateAttribute = do
-  putWord8Bits (damageStateAttributeUnknown1 damageStateAttribute)
-  BinaryBits.putBool (damageStateAttributeUnknown2 damageStateAttribute)
-  putInt32Bits (damageStateAttributeUnknown3 damageStateAttribute)
-  putVector (damageStateAttributeUnknown4 damageStateAttribute)
-  BinaryBits.putBool (damageStateAttributeUnknown5 damageStateAttribute)
-  BinaryBits.putBool (damageStateAttributeUnknown6 damageStateAttribute)
diff --git a/library/Rattletrap/Encode/DemolishAttribute.hs b/library/Rattletrap/Encode/DemolishAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/DemolishAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Encode.DemolishAttribute
-  ( putDemolishAttribute
-  )
-where
-
-import Rattletrap.Encode.Vector
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.DemolishAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putDemolishAttribute :: DemolishAttribute -> BinaryBits.BitPut ()
-putDemolishAttribute demolishAttribute = do
-  BinaryBits.putBool (demolishAttributeAttackerFlag demolishAttribute)
-  putWord32Bits (demolishAttributeAttackerActorId demolishAttribute)
-  BinaryBits.putBool (demolishAttributeVictimFlag demolishAttribute)
-  putWord32Bits (demolishAttributeVictimActorId demolishAttribute)
-  putVector (demolishAttributeAttackerVelocity demolishAttribute)
-  putVector (demolishAttributeVictimVelocity demolishAttribute)
diff --git a/library/Rattletrap/Encode/DestroyedReplication.hs b/library/Rattletrap/Encode/DestroyedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/DestroyedReplication.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Rattletrap.Encode.DestroyedReplication
-  ( putDestroyedReplication
-  )
-where
-
-import Rattletrap.Type.DestroyedReplication
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putDestroyedReplication :: DestroyedReplication -> BinaryBits.BitPut ()
-putDestroyedReplication _ = pure ()
diff --git a/library/Rattletrap/Encode/Dictionary.hs b/library/Rattletrap/Encode/Dictionary.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Dictionary.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Rattletrap.Encode.Dictionary
-  ( putDictionary
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Type.Dictionary
-
-import qualified Data.Binary as Binary
-
-putDictionary :: (a -> Binary.Put) -> Dictionary a -> Binary.Put
-putDictionary f x = case x of
-  DictionaryElement k v y -> do
-    putText k
-    f v
-    putDictionary f y
-  DictionaryEnd y -> putText y
diff --git a/library/Rattletrap/Encode/EnumAttribute.hs b/library/Rattletrap/Encode/EnumAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/EnumAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.EnumAttribute
-  ( putEnumAttribute
-  )
-where
-
-import Rattletrap.Encode.Common
-import Rattletrap.Type.EnumAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putEnumAttribute :: EnumAttribute -> BinaryBits.BitPut ()
-putEnumAttribute enumAttribute =
-  putBitsLE 11 (enumAttributeValue enumAttribute)
diff --git a/library/Rattletrap/Encode/ExplosionAttribute.hs b/library/Rattletrap/Encode/ExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ExplosionAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.ExplosionAttribute
-  ( putExplosionAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Encode.Vector
-import Rattletrap.Type.ExplosionAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putExplosionAttribute :: ExplosionAttribute -> BinaryBits.BitPut ()
-putExplosionAttribute explosionAttribute = do
-  BinaryBits.putBool False
-  putInt32Bits (explosionAttributeActorId explosionAttribute)
-  putVector (explosionAttributeLocation explosionAttribute)
diff --git a/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs b/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.ExtendedExplosionAttribute
-  ( putExtendedExplosionAttribute
-  )
-where
-
-import Rattletrap.Encode.ExplosionAttribute
-import Rattletrap.Encode.FlaggedIntAttribute
-import Rattletrap.Type.ExtendedExplosionAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putExtendedExplosionAttribute
-  :: ExtendedExplosionAttribute -> BinaryBits.BitPut ()
-putExtendedExplosionAttribute x = do
-  putExplosionAttribute (extendedExplosionAttributeExplosion x)
-  putFlaggedIntAttribute (extendedExplosionAttributeUnknown x)
diff --git a/library/Rattletrap/Encode/FlaggedByteAttribute.hs b/library/Rattletrap/Encode/FlaggedByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/FlaggedByteAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.FlaggedByteAttribute
-  ( putFlaggedByteAttribute
-  )
-where
-
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.FlaggedByteAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putFlaggedByteAttribute :: FlaggedByteAttribute -> BinaryBits.BitPut ()
-putFlaggedByteAttribute flaggedByteAttribute = do
-  BinaryBits.putBool (flaggedByteAttributeFlag flaggedByteAttribute)
-  putWord8Bits (flaggedByteAttributeByte flaggedByteAttribute)
diff --git a/library/Rattletrap/Encode/FlaggedIntAttribute.hs b/library/Rattletrap/Encode/FlaggedIntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/FlaggedIntAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.FlaggedIntAttribute
-  ( putFlaggedIntAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Type.FlaggedIntAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putFlaggedIntAttribute :: FlaggedIntAttribute -> BinaryBits.BitPut ()
-putFlaggedIntAttribute flaggedIntAttribute = do
-  BinaryBits.putBool (flaggedIntAttributeFlag flaggedIntAttribute)
-  putInt32Bits (flaggedIntAttributeInt flaggedIntAttribute)
diff --git a/library/Rattletrap/Encode/Float32le.hs b/library/Rattletrap/Encode/Float32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Float32le.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Float32le
-  ( putFloat32
-  , putFloat32Bits
-  )
-where
-
-import Rattletrap.Type.Float32le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putFloat32 :: Float32le -> Binary.Put
-putFloat32 = Binary.putFloatle . float32leValue
-
-putFloat32Bits :: Float32le -> BinaryBits.BitPut ()
-putFloat32Bits float32 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putFloat32 float32))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/FloatAttribute.hs b/library/Rattletrap/Encode/FloatAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/FloatAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.FloatAttribute
-  ( putFloatAttribute
-  )
-where
-
-import Rattletrap.Encode.Float32le
-import Rattletrap.Type.FloatAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putFloatAttribute :: FloatAttribute -> BinaryBits.BitPut ()
-putFloatAttribute floatAttribute =
-  putFloat32Bits (floatAttributeValue floatAttribute)
diff --git a/library/Rattletrap/Encode/Frame.hs b/library/Rattletrap/Encode/Frame.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Frame.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Rattletrap.Encode.Frame
-  ( putFrames
-  )
-where
-
-import Rattletrap.Encode.Float32le
-import Rattletrap.Encode.Replication
-import Rattletrap.Type.Frame
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putFrames :: [Frame] -> BinaryBits.BitPut ()
-putFrames frames = case frames of
-  [] -> pure ()
-  [frame] -> putFrame frame
-  first : rest -> do
-    putFrame first
-    putFrames rest
-
-putFrame :: Frame -> BinaryBits.BitPut ()
-putFrame frame = do
-  putFloat32Bits (frameTime frame)
-  putFloat32Bits (frameDelta frame)
-  putReplications (frameReplications frame)
diff --git a/library/Rattletrap/Encode/GameModeAttribute.hs b/library/Rattletrap/Encode/GameModeAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/GameModeAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.GameModeAttribute
-  ( putGameModeAttribute
-  )
-where
-
-import Rattletrap.Type.GameModeAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putGameModeAttribute :: GameModeAttribute -> BinaryBits.BitPut ()
-putGameModeAttribute gameModeAttribute = do
-  let numBits = gameModeAttributeNumBits gameModeAttribute
-  let word = gameModeAttributeWord gameModeAttribute
-  BinaryBits.putWord8 numBits word
diff --git a/library/Rattletrap/Encode/Header.hs b/library/Rattletrap/Encode/Header.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Header.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Rattletrap.Encode.Header
-  ( putHeader
-  )
-where
-
-import Rattletrap.Encode.Dictionary
-import Rattletrap.Encode.Property
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Header
-
-import qualified Data.Binary as Binary
-
-putHeader :: Header -> Binary.Put
-putHeader header = do
-  putWord32 (headerEngineVersion header)
-  putWord32 (headerLicenseeVersion header)
-  case headerPatchVersion header of
-    Nothing -> pure ()
-    Just patchVersion -> putWord32 patchVersion
-  putText (headerLabel header)
-  putDictionary putProperty (headerProperties header)
diff --git a/library/Rattletrap/Encode/Initialization.hs b/library/Rattletrap/Encode/Initialization.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Initialization.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Encode.Initialization
-  ( putInitialization
-  )
-where
-
-import Rattletrap.Encode.Int8Vector
-import Rattletrap.Encode.Vector
-import Rattletrap.Type.Initialization
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putInitialization :: Initialization -> BinaryBits.BitPut ()
-putInitialization initialization = do
-  case initializationLocation initialization of
-    Nothing -> pure ()
-    Just location -> putVector location
-  case initializationRotation initialization of
-    Nothing -> pure ()
-    Just rotation -> putInt8Vector rotation
diff --git a/library/Rattletrap/Encode/Int32le.hs b/library/Rattletrap/Encode/Int32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Int32le.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Int32le
-  ( putInt32
-  , putInt32Bits
-  )
-where
-
-import Rattletrap.Type.Int32le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putInt32 :: Int32le -> Binary.Put
-putInt32 int32 = Binary.putInt32le (int32leValue int32)
-
-putInt32Bits :: Int32le -> BinaryBits.BitPut ()
-putInt32Bits int32 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putInt32 int32))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/Int64Attribute.hs b/library/Rattletrap/Encode/Int64Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Int64Attribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.Int64Attribute
-  ( putInt64Attribute
-  )
-where
-
-import Rattletrap.Encode.Int64le
-import Rattletrap.Type.Int64Attribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putInt64Attribute :: Int64Attribute -> BinaryBits.BitPut ()
-putInt64Attribute int64Attribute =
-  putInt64Bits (int64AttributeValue int64Attribute)
diff --git a/library/Rattletrap/Encode/Int64le.hs b/library/Rattletrap/Encode/Int64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Int64le.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Encode.Int64le
-  ( putInt64Bits
-  )
-where
-
-import Rattletrap.Type.Int64le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putInt64 :: Int64le -> Binary.Put
-putInt64 int64 = Binary.putInt64le (int64leValue int64)
-
-putInt64Bits :: Int64le -> BinaryBits.BitPut ()
-putInt64Bits int64 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putInt64 int64))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/Int8Vector.hs b/library/Rattletrap/Encode/Int8Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Int8Vector.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Rattletrap.Encode.Int8Vector
-  ( putInt8Vector
-  )
-where
-
-import Rattletrap.Encode.Int8le
-import Rattletrap.Type.Int8Vector
-import Rattletrap.Type.Int8le
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putInt8Vector :: Int8Vector -> BinaryBits.BitPut ()
-putInt8Vector int8Vector = do
-  putInt8VectorField (int8VectorX int8Vector)
-  putInt8VectorField (int8VectorY int8Vector)
-  putInt8VectorField (int8VectorZ int8Vector)
-
-putInt8VectorField :: Maybe Int8le -> BinaryBits.BitPut ()
-putInt8VectorField maybeField = case maybeField of
-  Nothing -> BinaryBits.putBool False
-  Just field -> do
-    BinaryBits.putBool True
-    putInt8Bits field
diff --git a/library/Rattletrap/Encode/Int8le.hs b/library/Rattletrap/Encode/Int8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Int8le.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Encode.Int8le
-  ( putInt8Bits
-  )
-where
-
-import Rattletrap.Type.Int8le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putInt8 :: Int8le -> Binary.Put
-putInt8 int8 = Binary.putInt8 (int8leValue int8)
-
-putInt8Bits :: Int8le -> BinaryBits.BitPut ()
-putInt8Bits int8 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putInt8 int8))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/IntAttribute.hs b/library/Rattletrap/Encode/IntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/IntAttribute.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Rattletrap.Encode.IntAttribute
-  ( putIntAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Type.IntAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putIntAttribute :: IntAttribute -> BinaryBits.BitPut ()
-putIntAttribute intAttribute = putInt32Bits (intAttributeValue intAttribute)
diff --git a/library/Rattletrap/Encode/KeyFrame.hs b/library/Rattletrap/Encode/KeyFrame.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/KeyFrame.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.KeyFrame
-  ( putKeyFrame
-  )
-where
-
-import Rattletrap.Encode.Float32le
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.KeyFrame
-
-import qualified Data.Binary as Binary
-
-putKeyFrame :: KeyFrame -> Binary.Put
-putKeyFrame keyFrame = do
-  putFloat32 (keyFrameTime keyFrame)
-  putWord32 (keyFrameFrame keyFrame)
-  putWord32 (keyFramePosition keyFrame)
diff --git a/library/Rattletrap/Encode/List.hs b/library/Rattletrap/Encode/List.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/List.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.List
-  ( putList
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.List
-import Rattletrap.Type.Word32le
-
-import qualified Data.Binary as Binary
-
-putList :: (a -> Binary.Put) -> List a -> Binary.Put
-putList putElement list = do
-  let elements = listValue list
-  putWord32 (Word32le (fromIntegral (length elements)))
-  mapM_ putElement elements
diff --git a/library/Rattletrap/Encode/LoadoutAttribute.hs b/library/Rattletrap/Encode/LoadoutAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/LoadoutAttribute.hs
+++ /dev/null
@@ -1,35 +0,0 @@
-module Rattletrap.Encode.LoadoutAttribute
-  ( putLoadoutAttribute
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.LoadoutAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putLoadoutAttribute :: LoadoutAttribute -> BinaryBits.BitPut ()
-putLoadoutAttribute loadoutAttribute = do
-  putWord8Bits (loadoutAttributeVersion loadoutAttribute)
-  putWord32Bits (loadoutAttributeBody loadoutAttribute)
-  putWord32Bits (loadoutAttributeDecal loadoutAttribute)
-  putWord32Bits (loadoutAttributeWheels loadoutAttribute)
-  putWord32Bits (loadoutAttributeRocketTrail loadoutAttribute)
-  putWord32Bits (loadoutAttributeAntenna loadoutAttribute)
-  putWord32Bits (loadoutAttributeTopper loadoutAttribute)
-  putWord32Bits (loadoutAttributeUnknown1 loadoutAttribute)
-  putOptional (loadoutAttributeUnknown2 loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeEngineAudio loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeTrail loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeGoalExplosion loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeBanner loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeUnknown3 loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeUnknown4 loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeUnknown5 loadoutAttribute) putWord32Bits
-  putOptional (loadoutAttributeUnknown6 loadoutAttribute) putWord32Bits
-
-putOptional :: Maybe a -> (a -> BinaryBits.BitPut ()) -> BinaryBits.BitPut ()
-putOptional m f = case m of
-  Just x -> f x
-  Nothing -> pure ()
diff --git a/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs b/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Rattletrap.Encode.LoadoutOnlineAttribute
-  ( putLoadoutOnlineAttribute
-  )
-where
-
-import Rattletrap.Encode.ProductAttribute
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.LoadoutOnlineAttribute
-import Rattletrap.Type.Word8le
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putLoadoutOnlineAttribute :: LoadoutOnlineAttribute -> BinaryBits.BitPut ()
-putLoadoutOnlineAttribute loadoutAttribute = do
-  let attributes = loadoutAttributeValue loadoutAttribute
-  putWord8Bits (Word8le (fromIntegral (length attributes)))
-  mapM_ putProductAttributes attributes
diff --git a/library/Rattletrap/Encode/LoadoutsAttribute.hs b/library/Rattletrap/Encode/LoadoutsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/LoadoutsAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.LoadoutsAttribute
-  ( putLoadoutsAttribute
-  )
-where
-
-import Rattletrap.Encode.LoadoutAttribute
-import Rattletrap.Type.LoadoutsAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putLoadoutsAttribute :: LoadoutsAttribute -> BinaryBits.BitPut ()
-putLoadoutsAttribute loadoutsAttribute = do
-  putLoadoutAttribute (loadoutsAttributeBlue loadoutsAttribute)
-  putLoadoutAttribute (loadoutsAttributeOrange loadoutsAttribute)
diff --git a/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs b/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Encode.LoadoutsOnlineAttribute
-  ( putLoadoutsOnlineAttribute
-  )
-where
-
-import Rattletrap.Encode.LoadoutOnlineAttribute
-import Rattletrap.Type.LoadoutsOnlineAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putLoadoutsOnlineAttribute :: LoadoutsOnlineAttribute -> BinaryBits.BitPut ()
-putLoadoutsOnlineAttribute loadoutsOnlineAttribute = do
-  putLoadoutOnlineAttribute
-    (loadoutsOnlineAttributeBlue loadoutsOnlineAttribute)
-  putLoadoutOnlineAttribute
-    (loadoutsOnlineAttributeOrange loadoutsOnlineAttribute)
-  BinaryBits.putBool (loadoutsOnlineAttributeUnknown1 loadoutsOnlineAttribute)
-  BinaryBits.putBool (loadoutsOnlineAttributeUnknown2 loadoutsOnlineAttribute)
diff --git a/library/Rattletrap/Encode/LocationAttribute.hs b/library/Rattletrap/Encode/LocationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/LocationAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.LocationAttribute
-  ( putLocationAttribute
-  )
-where
-
-import Rattletrap.Encode.Vector
-import Rattletrap.Type.LocationAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putLocationAttribute :: LocationAttribute -> BinaryBits.BitPut ()
-putLocationAttribute locationAttribute =
-  putVector (locationAttributeValue locationAttribute)
diff --git a/library/Rattletrap/Encode/Mark.hs b/library/Rattletrap/Encode/Mark.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Mark.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Encode.Mark
-  ( putMark
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Mark
-
-import qualified Data.Binary as Binary
-
-putMark :: Mark -> Binary.Put
-putMark mark = do
-  putText (markValue mark)
-  putWord32 (markFrame mark)
diff --git a/library/Rattletrap/Encode/Message.hs b/library/Rattletrap/Encode/Message.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Message.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.Message
-  ( putMessage
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Message
-
-import qualified Data.Binary as Binary
-
-putMessage :: Message -> Binary.Put
-putMessage message = do
-  putWord32 (messageFrame message)
-  putText (messageName message)
-  putText (messageValue message)
diff --git a/library/Rattletrap/Encode/MusicStingerAttribute.hs b/library/Rattletrap/Encode/MusicStingerAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/MusicStingerAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.MusicStingerAttribute
-  ( putMusicStingerAttribute
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.MusicStingerAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putMusicStingerAttribute :: MusicStingerAttribute -> BinaryBits.BitPut ()
-putMusicStingerAttribute musicStingerAttribute = do
-  BinaryBits.putBool (musicStingerAttributeFlag musicStingerAttribute)
-  putWord32Bits (musicStingerAttributeCue musicStingerAttribute)
-  putWord8Bits (musicStingerAttributeTrigger musicStingerAttribute)
diff --git a/library/Rattletrap/Encode/PartyLeaderAttribute.hs b/library/Rattletrap/Encode/PartyLeaderAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/PartyLeaderAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Encode.PartyLeaderAttribute
-  ( putPartyLeaderAttribute
-  )
-where
-
-import Rattletrap.Encode.RemoteId
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.PartyLeaderAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putPartyLeaderAttribute :: PartyLeaderAttribute -> BinaryBits.BitPut ()
-putPartyLeaderAttribute partyLeaderAttribute = do
-  putWord8Bits (partyLeaderAttributeSystemId partyLeaderAttribute)
-  case partyLeaderAttributeId partyLeaderAttribute of
-    Nothing -> pure ()
-    Just (remoteId, localId) -> do
-      putRemoteId remoteId
-      putWord8Bits localId
diff --git a/library/Rattletrap/Encode/PickupAttribute.hs b/library/Rattletrap/Encode/PickupAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/PickupAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Encode.PickupAttribute
-  ( putPickupAttribute
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.PickupAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putPickupAttribute :: PickupAttribute -> BinaryBits.BitPut ()
-putPickupAttribute pickupAttribute = do
-  case pickupAttributeInstigatorId pickupAttribute of
-    Nothing -> BinaryBits.putBool False
-    Just instigatorId -> do
-      BinaryBits.putBool True
-      putWord32Bits instigatorId
-  BinaryBits.putBool (pickupAttributePickedUp pickupAttribute)
diff --git a/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs b/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.PlayerHistoryKeyAttribute
-  ( putPlayerHistoryKeyAttribute
-  )
-where
-
-import Rattletrap.Encode.Common
-import Rattletrap.Type.PlayerHistoryKeyAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putPlayerHistoryKeyAttribute
-  :: PlayerHistoryKeyAttribute -> BinaryBits.BitPut ()
-putPlayerHistoryKeyAttribute = putBitsLE 14 . playerHistoryKeyAttributeUnknown
diff --git a/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs b/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rattletrap.Encode.PrivateMatchSettingsAttribute
-  ( putPrivateMatchSettingsAttribute
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.PrivateMatchSettingsAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putPrivateMatchSettingsAttribute
-  :: PrivateMatchSettingsAttribute -> BinaryBits.BitPut ()
-putPrivateMatchSettingsAttribute privateMatchSettingsAttribute = do
-  putTextBits
-    (privateMatchSettingsAttributeMutators privateMatchSettingsAttribute)
-  putWord32Bits
-    (privateMatchSettingsAttributeJoinableBy privateMatchSettingsAttribute)
-  putWord32Bits
-    (privateMatchSettingsAttributeMaxPlayers privateMatchSettingsAttribute)
-  putTextBits
-    (privateMatchSettingsAttributeGameName privateMatchSettingsAttribute)
-  putTextBits
-    (privateMatchSettingsAttributePassword privateMatchSettingsAttribute)
-  BinaryBits.putBool
-    (privateMatchSettingsAttributeFlag privateMatchSettingsAttribute)
diff --git a/library/Rattletrap/Encode/ProductAttribute.hs b/library/Rattletrap/Encode/ProductAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ProductAttribute.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Rattletrap.Encode.ProductAttribute
-  ( putProductAttributes
-  )
-where
-
-import Rattletrap.Encode.Common
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Encode.Word32le
-import Rattletrap.Encode.Word8le
-import Rattletrap.Encode.Str
-import Rattletrap.Type.ProductAttribute
-import Rattletrap.Type.Word8le
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putProductAttributes :: [ProductAttribute] -> BinaryBits.BitPut ()
-putProductAttributes attributes = do
-  putWord8Bits (Word8le (fromIntegral (length attributes)))
-  mapM_ putProductAttribute attributes
-
-putProductAttribute :: ProductAttribute -> BinaryBits.BitPut ()
-putProductAttribute attribute = do
-  BinaryBits.putBool (productAttributeUnknown attribute)
-  putWord32Bits (productAttributeObjectId attribute)
-  case productAttributeValue attribute of
-    ProductAttributeValuePaintedOld x -> putCompressedWord x
-    ProductAttributeValuePaintedNew x -> putBitsLE 31 x
-    ProductAttributeValueTeamEditionOld x -> putCompressedWord x
-    ProductAttributeValueTeamEditionNew x -> putBitsLE 31 x
-    ProductAttributeValueSpecialEdition x -> putBitsLE 31 x
-    ProductAttributeValueUserColorOld x -> case x of
-      Nothing -> BinaryBits.putBool False
-      Just y -> do
-        BinaryBits.putBool True
-        putBitsLE 31 y
-    ProductAttributeValueUserColorNew x -> putWord32Bits x
-    ProductAttributeValueTitleId x -> putTextBits x
diff --git a/library/Rattletrap/Encode/Property.hs b/library/Rattletrap/Encode/Property.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Property.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Rattletrap.Encode.Property
-  ( putProperty
-  )
-where
-
-import Rattletrap.Encode.PropertyValue
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word64le
-import Rattletrap.Type.Property
-
-import qualified Data.Binary as Binary
-
-putProperty :: Property -> Binary.Put
-putProperty property = do
-  putText (propertyKind property)
-  putWord64 (propertySize property)
-  putPropertyValue putProperty (propertyValue property)
diff --git a/library/Rattletrap/Encode/PropertyValue.hs b/library/Rattletrap/Encode/PropertyValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/PropertyValue.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-module Rattletrap.Encode.PropertyValue
-  ( putPropertyValue
-  )
-where
-
-import Rattletrap.Encode.Dictionary
-import Rattletrap.Encode.Float32le
-import Rattletrap.Encode.Int32le
-import Rattletrap.Encode.List
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.Word64le
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.PropertyValue
-
-import qualified Data.Binary as Binary
-
-putPropertyValue :: (a -> Binary.Put) -> PropertyValue a -> Binary.Put
-putPropertyValue putProperty value = case value of
-  PropertyValueArray list -> putList (putDictionary putProperty) list
-  PropertyValueBool word8 -> putWord8 word8
-  PropertyValueByte k mv -> do
-    putText k
-    case mv of
-      Nothing -> pure ()
-      Just v -> putText v
-  PropertyValueFloat float32 -> putFloat32 float32
-  PropertyValueInt int32 -> putInt32 int32
-  PropertyValueName text -> putText text
-  PropertyValueQWord word64 -> putWord64 word64
-  PropertyValueStr text -> putText text
diff --git a/library/Rattletrap/Encode/QWordAttribute.hs b/library/Rattletrap/Encode/QWordAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/QWordAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.QWordAttribute
-  ( putQWordAttribute
-  )
-where
-
-import Rattletrap.Encode.Word64le
-import Rattletrap.Type.QWordAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putQWordAttribute :: QWordAttribute -> BinaryBits.BitPut ()
-putQWordAttribute qWordAttribute =
-  putWord64Bits (qWordAttributeValue qWordAttribute)
diff --git a/library/Rattletrap/Encode/Quaternion.hs b/library/Rattletrap/Encode/Quaternion.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Quaternion.hs
+++ /dev/null
@@ -1,41 +0,0 @@
-module Rattletrap.Encode.Quaternion
-  ( putQuaternion
-  )
-where
-
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Quaternion
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putQuaternion :: Quaternion -> BinaryBits.BitPut ()
-putQuaternion q = do
-  let c = maxComponent q
-  putComponent c
-  case c of
-    ComponentX -> putParts (quaternionY q) (quaternionZ q) (quaternionW q)
-    ComponentY -> putParts (quaternionX q) (quaternionZ q) (quaternionW q)
-    ComponentZ -> putParts (quaternionX q) (quaternionY q) (quaternionW q)
-    ComponentW -> putParts (quaternionX q) (quaternionY q) (quaternionZ q)
-
-putComponent :: Component -> BinaryBits.BitPut ()
-putComponent component = putCompressedWord
-  (CompressedWord
-    3
-    (case component of
-      ComponentX -> 0
-      ComponentY -> 1
-      ComponentZ -> 2
-      ComponentW -> 3
-    )
-  )
-
-putParts :: Double -> Double -> Double -> BinaryBits.BitPut ()
-putParts a b c = do
-  putPart a
-  putPart b
-  putPart c
-
-putPart :: Double -> BinaryBits.BitPut ()
-putPart = putCompressedWord . compressPart
diff --git a/library/Rattletrap/Encode/RemoteId.hs b/library/Rattletrap/Encode/RemoteId.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/RemoteId.hs
+++ /dev/null
@@ -1,35 +0,0 @@
-module Rattletrap.Encode.RemoteId
-  ( putRemoteId
-  )
-where
-
-import Rattletrap.Encode.Common
-import Rattletrap.Encode.Word64le
-import Rattletrap.Type.RemoteId
-import Rattletrap.Type.Word64le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.ByteString as Bytes
-
-putRemoteId :: RemoteId -> BinaryBits.BitPut ()
-putRemoteId remoteId = case remoteId of
-  RemoteIdPlayStation name bytes -> do
-    let rawName = reverseBytes (padBytes (16 :: Int) (encodeLatin1 name))
-    BinaryBits.putByteString rawName
-    BinaryBits.putByteString (Bytes.pack bytes)
-  RemoteIdPsyNet e -> case e of
-    Left l -> putWord64Bits l
-    Right (a, b, c, d) -> putWord256 a b c d
-  RemoteIdSplitscreen word24 -> putBitsLE 24 word24
-  RemoteIdSteam word64 -> putWord64Bits word64
-  RemoteIdSwitch a b c d -> putWord256 a b c d
-  RemoteIdXbox word64 -> putWord64Bits word64
-
-putWord256
-  :: Word64le -> Word64le -> Word64le -> Word64le -> BinaryBits.BitPut ()
-putWord256 a b c d = do
-  putWord64Bits a
-  putWord64Bits b
-  putWord64Bits c
-  putWord64Bits d
diff --git a/library/Rattletrap/Encode/Replay.hs b/library/Rattletrap/Encode/Replay.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Replay.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Replay
-  ( putReplay
-  )
-where
-
-import Rattletrap.Encode.Content
-import Rattletrap.Encode.Header
-import Rattletrap.Encode.Section
-import Rattletrap.Type.Replay
-
-import qualified Data.Binary as Binary
-
--- | Generates a raw replay. Use this with 'Data.Binary.Put.runPut'.
---
--- @
--- let bytes = 'Data.Binary.Put.runPut' ('putReplay' replay)
--- @
-putReplay :: Replay -> Binary.Put
-putReplay replay = do
-  putSection putHeader (replayHeader replay)
-  putSection putContent (replayContent replay)
diff --git a/library/Rattletrap/Encode/Replication.hs b/library/Rattletrap/Encode/Replication.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Replication.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rattletrap.Encode.Replication
-  ( putReplications
-  )
-where
-
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Encode.ReplicationValue
-import Rattletrap.Type.Replication
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putReplications :: [Replication] -> BinaryBits.BitPut ()
-putReplications replications = case replications of
-  [] -> BinaryBits.putBool False
-  [replication] -> do
-    putReplication replication
-    BinaryBits.putBool False
-  first : rest -> do
-    putReplication first
-    putReplications rest
-
-putReplication :: Replication -> BinaryBits.BitPut ()
-putReplication replication = do
-  BinaryBits.putBool True
-  putCompressedWord (replicationActorId replication)
-  putReplicationValue (replicationValue replication)
diff --git a/library/Rattletrap/Encode/ReplicationValue.hs b/library/Rattletrap/Encode/ReplicationValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ReplicationValue.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Rattletrap.Encode.ReplicationValue
-  ( putReplicationValue
-  )
-where
-
-import Rattletrap.Encode.DestroyedReplication
-import Rattletrap.Encode.SpawnedReplication
-import Rattletrap.Encode.UpdatedReplication
-import Rattletrap.Type.ReplicationValue
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putReplicationValue :: ReplicationValue -> BinaryBits.BitPut ()
-putReplicationValue value = case value of
-  ReplicationValueSpawned x -> do
-    BinaryBits.putBool True
-    BinaryBits.putBool True
-    putSpawnedReplication x
-  ReplicationValueUpdated x -> do
-    BinaryBits.putBool True
-    BinaryBits.putBool False
-    putUpdatedReplication x
-  ReplicationValueDestroyed x -> do
-    BinaryBits.putBool False
-    putDestroyedReplication x
diff --git a/library/Rattletrap/Encode/ReservationAttribute.hs b/library/Rattletrap/Encode/ReservationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/ReservationAttribute.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Rattletrap.Encode.ReservationAttribute
-  ( putReservationAttribute
-  )
-where
-
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Encode.Str
-import Rattletrap.Encode.UniqueIdAttribute
-import Rattletrap.Type.ReservationAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putReservationAttribute :: ReservationAttribute -> BinaryBits.BitPut ()
-putReservationAttribute reservationAttribute = do
-  putCompressedWord (reservationAttributeNumber reservationAttribute)
-  putUniqueIdAttribute (reservationAttributeUniqueId reservationAttribute)
-  case reservationAttributeName reservationAttribute of
-    Nothing -> pure ()
-    Just name -> putTextBits name
-  BinaryBits.putBool (reservationAttributeUnknown1 reservationAttribute)
-  BinaryBits.putBool (reservationAttributeUnknown2 reservationAttribute)
-  case reservationAttributeUnknown3 reservationAttribute of
-    Nothing -> pure ()
-    Just c -> BinaryBits.putWord8 6 c
diff --git a/library/Rattletrap/Encode/RigidBodyStateAttribute.hs b/library/Rattletrap/Encode/RigidBodyStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/RigidBodyStateAttribute.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Rattletrap.Encode.RigidBodyStateAttribute
-  ( putRigidBodyStateAttribute
-  )
-where
-
-import Rattletrap.Encode.Rotation
-import Rattletrap.Encode.Vector
-import Rattletrap.Type.RigidBodyStateAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putRigidBodyStateAttribute :: RigidBodyStateAttribute -> BinaryBits.BitPut ()
-putRigidBodyStateAttribute rigidBodyStateAttribute = do
-  BinaryBits.putBool (rigidBodyStateAttributeSleeping rigidBodyStateAttribute)
-  putVector (rigidBodyStateAttributeLocation rigidBodyStateAttribute)
-  putRotation (rigidBodyStateAttributeRotation rigidBodyStateAttribute)
-  case rigidBodyStateAttributeLinearVelocity rigidBodyStateAttribute of
-    Nothing -> pure ()
-    Just linearVelocity -> putVector linearVelocity
-  case rigidBodyStateAttributeAngularVelocity rigidBodyStateAttribute of
-    Nothing -> pure ()
-    Just angularVelocity -> putVector angularVelocity
diff --git a/library/Rattletrap/Encode/Rotation.hs b/library/Rattletrap/Encode/Rotation.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Rotation.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Rattletrap.Encode.Rotation
-  ( putRotation
-  )
-where
-
-import Rattletrap.Encode.CompressedWordVector
-import Rattletrap.Encode.Quaternion
-import Rattletrap.Type.Rotation
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putRotation :: Rotation -> BinaryBits.BitPut ()
-putRotation r = case r of
-  RotationCompressedWordVector cwv -> putCompressedWordVector cwv
-  RotationQuaternion q -> putQuaternion q
diff --git a/library/Rattletrap/Encode/Section.hs b/library/Rattletrap/Encode/Section.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Section.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-module Rattletrap.Encode.Section
-  ( putSection
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.Section
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Crc
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Lazy as LazyBytes
-
--- | Given a way to put the 'sectionBody', puts a section. This will also put
--- the size and CRC.
---
--- @
--- let bytes = 'Data.Binary.Put.runPut' ('putSection' 'Rattletrap.Content.putContent' content)
--- @
-putSection :: (a -> Binary.Put) -> Section a -> Binary.Put
-putSection putBody section = do
-  let
-    rawBody =
-      LazyBytes.toStrict (Binary.runPut (putBody (sectionBody section)))
-  let size = Bytes.length rawBody
-  let crc = getCrc32 rawBody
-  putWord32 (Word32le (fromIntegral size))
-  putWord32 (Word32le crc)
-  Binary.putByteString rawBody
diff --git a/library/Rattletrap/Encode/SpawnedReplication.hs b/library/Rattletrap/Encode/SpawnedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/SpawnedReplication.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rattletrap.Encode.SpawnedReplication
-  ( putSpawnedReplication
-  )
-where
-
-import Rattletrap.Encode.Initialization
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.SpawnedReplication
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putSpawnedReplication :: SpawnedReplication -> BinaryBits.BitPut ()
-putSpawnedReplication spawnedReplication = do
-  BinaryBits.putBool (spawnedReplicationFlag spawnedReplication)
-  case spawnedReplicationNameIndex spawnedReplication of
-    Nothing -> pure ()
-    Just nameIndex -> putWord32Bits nameIndex
-  putWord32Bits (spawnedReplicationObjectId spawnedReplication)
-  putInitialization (spawnedReplicationInitialization spawnedReplication)
diff --git a/library/Rattletrap/Encode/StatEventAttribute.hs b/library/Rattletrap/Encode/StatEventAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/StatEventAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Rattletrap.Encode.StatEventAttribute
-  ( putStatEventAttribute
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Type.StatEventAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putStatEventAttribute :: StatEventAttribute -> BinaryBits.BitPut ()
-putStatEventAttribute statEventAttribute = do
-  BinaryBits.putBool (statEventAttributeUnknown statEventAttribute)
-  putInt32Bits (statEventAttributeObjectId statEventAttribute)
diff --git a/library/Rattletrap/Encode/Str.hs b/library/Rattletrap/Encode/Str.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Str.hs
+++ /dev/null
@@ -1,53 +0,0 @@
-module Rattletrap.Encode.Str
-  ( putText
-  , putTextBits
-  )
-where
-
-import Rattletrap.Encode.Int32le
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Str
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString as Bytes
-import qualified Data.Char as Char
-import qualified Data.Text as Text
-import qualified Data.Text.Encoding as Text
-
-putText :: Str -> Binary.Put
-putText text = do
-  let size = getTextSize text
-  let encode = getTextEncoder size
-  putInt32 size
-  Binary.putByteString (encode (addNull (strValue text)))
-
-putTextBits :: Str -> BinaryBits.BitPut ()
-putTextBits text = do
-  let size = getTextSize text
-  let encode = getTextEncoder size
-  putInt32Bits size
-  BinaryBits.putByteString (reverseBytes (encode (addNull (strValue text))))
-
-getTextSize :: Str -> Int32le
-getTextSize text =
-  let
-    value = strValue text
-    scale = if Text.all Char.isLatin1 value then 1 else -1 :: Int32
-    rawSize = if Text.null value
-      then 0
-      else fromIntegral (Text.length value) + 1 :: Int32
-    size = if value == Text.pack "\x00\x00\x00None"
-      then 0x05000000
-      else scale * rawSize :: Int32
-  in Int32le size
-
-getTextEncoder :: Int32le -> Text.Text -> Bytes.ByteString
-getTextEncoder size text =
-  if size < Int32le 0 then Text.encodeUtf16LE text else encodeLatin1 text
-
-addNull :: Text.Text -> Text.Text
-addNull text = if Text.null text then text else Text.snoc text '\x00'
diff --git a/library/Rattletrap/Encode/StringAttribute.hs b/library/Rattletrap/Encode/StringAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/StringAttribute.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.StringAttribute
-  ( putStringAttribute
-  )
-where
-
-import Rattletrap.Encode.Str
-import Rattletrap.Type.StringAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putStringAttribute :: StringAttribute -> BinaryBits.BitPut ()
-putStringAttribute stringAttribute =
-  putTextBits (stringAttributeValue stringAttribute)
diff --git a/library/Rattletrap/Encode/TeamPaintAttribute.hs b/library/Rattletrap/Encode/TeamPaintAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/TeamPaintAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Rattletrap.Encode.TeamPaintAttribute
-  ( putTeamPaintAttribute
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.TeamPaintAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putTeamPaintAttribute :: TeamPaintAttribute -> BinaryBits.BitPut ()
-putTeamPaintAttribute teamPaintAttribute = do
-  putWord8Bits (teamPaintAttributeTeam teamPaintAttribute)
-  putWord8Bits (teamPaintAttributePrimaryColor teamPaintAttribute)
-  putWord8Bits (teamPaintAttributeAccentColor teamPaintAttribute)
-  putWord32Bits (teamPaintAttributePrimaryFinish teamPaintAttribute)
-  putWord32Bits (teamPaintAttributeAccentFinish teamPaintAttribute)
diff --git a/library/Rattletrap/Encode/TitleAttribute.hs b/library/Rattletrap/Encode/TitleAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/TitleAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Encode.TitleAttribute
-  ( putTitleAttribute
-  )
-where
-
-import Rattletrap.Encode.Word32le
-import Rattletrap.Type.TitleAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putTitleAttribute :: TitleAttribute -> BinaryBits.BitPut ()
-putTitleAttribute titleAttribute = do
-  BinaryBits.putBool (titleAttributeUnknown1 titleAttribute)
-  BinaryBits.putBool (titleAttributeUnknown2 titleAttribute)
-  putWord32Bits (titleAttributeUnknown3 titleAttribute)
-  putWord32Bits (titleAttributeUnknown4 titleAttribute)
-  putWord32Bits (titleAttributeUnknown5 titleAttribute)
-  putWord32Bits (titleAttributeUnknown6 titleAttribute)
-  putWord32Bits (titleAttributeUnknown7 titleAttribute)
-  BinaryBits.putBool (titleAttributeUnknown8 titleAttribute)
diff --git a/library/Rattletrap/Encode/UniqueIdAttribute.hs b/library/Rattletrap/Encode/UniqueIdAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/UniqueIdAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rattletrap.Encode.UniqueIdAttribute
-  ( putUniqueIdAttribute
-  )
-where
-
-import Rattletrap.Encode.RemoteId
-import Rattletrap.Encode.Word8le
-import Rattletrap.Type.UniqueIdAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putUniqueIdAttribute :: UniqueIdAttribute -> BinaryBits.BitPut ()
-putUniqueIdAttribute uniqueIdAttribute = do
-  putWord8Bits (uniqueIdAttributeSystemId uniqueIdAttribute)
-  putRemoteId (uniqueIdAttributeRemoteId uniqueIdAttribute)
-  putWord8Bits (uniqueIdAttributeLocalId uniqueIdAttribute)
diff --git a/library/Rattletrap/Encode/UpdatedReplication.hs b/library/Rattletrap/Encode/UpdatedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/UpdatedReplication.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Rattletrap.Encode.UpdatedReplication
-  ( putUpdatedReplication
-  )
-where
-
-import Rattletrap.Encode.Attribute
-import Rattletrap.Type.UpdatedReplication
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putUpdatedReplication :: UpdatedReplication -> BinaryBits.BitPut ()
-putUpdatedReplication updatedReplication =
-  putAttributes (updatedReplicationAttributes updatedReplication)
diff --git a/library/Rattletrap/Encode/Vector.hs b/library/Rattletrap/Encode/Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Vector.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-module Rattletrap.Encode.Vector
-  ( putVector
-  )
-where
-
-import Rattletrap.Encode.CompressedWord
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Vector
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putVector :: Vector -> BinaryBits.BitPut ()
-putVector vector = do
-  let
-    bitSize =
-      round (logBase (2 :: Float) (fromIntegral (vectorBias vector))) - 1 :: Word
-    dx =
-      fromIntegral (vectorX vector + fromIntegral (vectorBias vector)) :: Word
-    dy =
-      fromIntegral (vectorY vector + fromIntegral (vectorBias vector)) :: Word
-    dz =
-      fromIntegral (vectorZ vector + fromIntegral (vectorBias vector)) :: Word
-    limit = 2 ^ (bitSize + 2) :: Word
-  putCompressedWord (vectorSize vector)
-  putCompressedWord (CompressedWord limit dx)
-  putCompressedWord (CompressedWord limit dy)
-  putCompressedWord (CompressedWord limit dz)
diff --git a/library/Rattletrap/Encode/WeldedInfoAttribute.hs b/library/Rattletrap/Encode/WeldedInfoAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/WeldedInfoAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Rattletrap.Encode.WeldedInfoAttribute
-  ( putWeldedInfoAttribute
-  )
-where
-
-import Rattletrap.Encode.Float32le
-import Rattletrap.Encode.Int32le
-import Rattletrap.Encode.Int8Vector
-import Rattletrap.Encode.Vector
-import Rattletrap.Type.WeldedInfoAttribute
-
-import qualified Data.Binary.Bits.Put as BinaryBits
-
-putWeldedInfoAttribute :: WeldedInfoAttribute -> BinaryBits.BitPut ()
-putWeldedInfoAttribute weldedInfoAttribute = do
-  BinaryBits.putBool (weldedInfoAttributeActive weldedInfoAttribute)
-  putInt32Bits (weldedInfoAttributeActorId weldedInfoAttribute)
-  putVector (weldedInfoAttributeOffset weldedInfoAttribute)
-  putFloat32Bits (weldedInfoAttributeMass weldedInfoAttribute)
-  putInt8Vector (weldedInfoAttributeRotation weldedInfoAttribute)
diff --git a/library/Rattletrap/Encode/Word32le.hs b/library/Rattletrap/Encode/Word32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Word32le.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Word32le
-  ( putWord32
-  , putWord32Bits
-  )
-where
-
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putWord32 :: Word32le -> Binary.Put
-putWord32 word32 = Binary.putWord32le (word32leValue word32)
-
-putWord32Bits :: Word32le -> BinaryBits.BitPut ()
-putWord32Bits word32 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putWord32 word32))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/Word64le.hs b/library/Rattletrap/Encode/Word64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Word64le.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Word64le
-  ( putWord64
-  , putWord64Bits
-  )
-where
-
-import Rattletrap.Type.Word64le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putWord64 :: Word64le -> Binary.Put
-putWord64 word64 = Binary.putWord64le (word64leValue word64)
-
-putWord64Bits :: Word64le -> BinaryBits.BitPut ()
-putWord64Bits word64 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putWord64 word64))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Encode/Word8le.hs b/library/Rattletrap/Encode/Word8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Encode/Word8le.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Rattletrap.Encode.Word8le
-  ( putWord8
-  , putWord8Bits
-  )
-where
-
-import Rattletrap.Type.Word8le
-import Rattletrap.Utility.Bytes
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Bits.Put as BinaryBits
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString.Lazy as LazyBytes
-
-putWord8 :: Word8le -> Binary.Put
-putWord8 word8 = Binary.putWord8 (word8leValue word8)
-
-putWord8Bits :: Word8le -> BinaryBits.BitPut ()
-putWord8Bits word8 = do
-  let bytes = LazyBytes.toStrict (Binary.runPut (putWord8 word8))
-  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/library/Rattletrap/Type/AppliedDamageAttribute.hs b/library/Rattletrap/Type/AppliedDamageAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/AppliedDamageAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.AppliedDamageAttribute
-  ( AppliedDamageAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Vector
-import Rattletrap.Type.Word8le
-
-data AppliedDamageAttribute = AppliedDamageAttribute
-  { appliedDamageAttributeUnknown1 :: Word8le
-  , appliedDamageAttributeLocation :: Vector
-  , appliedDamageAttributeUnknown3 :: Int32le
-  , appliedDamageAttributeUnknown4 :: Int32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''AppliedDamageAttribute)
diff --git a/library/Rattletrap/Type/Attribute.hs b/library/Rattletrap/Type/Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Attribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Attribute
-  ( Attribute(..)
-  )
-where
-
-import Rattletrap.Type.AttributeValue
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Str
-
-data Attribute = Attribute
-  { attributeId :: CompressedWord
-  , attributeName :: Str
-  -- ^ Read-only! Changing an attribute's name requires editing the class
-  -- attribute map.
-  , attributeValue :: AttributeValue
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Attribute)
diff --git a/library/Rattletrap/Type/AttributeMapping.hs b/library/Rattletrap/Type/AttributeMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/AttributeMapping.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.AttributeMapping
-  ( AttributeMapping(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-
-data AttributeMapping = AttributeMapping
-  { attributeMappingObjectId :: Word32le
-  , attributeMappingStreamId :: Word32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''AttributeMapping)
diff --git a/library/Rattletrap/Type/AttributeType.hs b/library/Rattletrap/Type/AttributeType.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/AttributeType.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.AttributeType
-  ( AttributeType(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-data AttributeType
-  = AttributeTypeAppliedDamage
-  | AttributeTypeBoolean
-  | AttributeTypeByte
-  | AttributeTypeCamSettings
-  | AttributeTypeClubColors
-  | AttributeTypeDamageState
-  | AttributeTypeDemolish
-  | AttributeTypeEnum
-  | AttributeTypeExplosion
-  | AttributeTypeExtendedExplosion
-  | AttributeTypeFlaggedInt
-  | AttributeTypeFlaggedByte
-  | AttributeTypeFloat
-  | AttributeTypeGameMode
-  | AttributeTypeInt
-  | AttributeTypeInt64
-  | AttributeTypeLoadout
-  | AttributeTypeLoadoutOnline
-  | AttributeTypeLoadouts
-  | AttributeTypeLoadoutsOnline
-  | AttributeTypeLocation
-  | AttributeTypeMusicStinger
-  | AttributeTypePartyLeader
-  | AttributeTypePickup
-  | AttributeTypePlayerHistoryKey
-  | AttributeTypePrivateMatchSettings
-  | AttributeTypeQWord
-  | AttributeTypeReservation
-  | AttributeTypeRigidBodyState
-  | AttributeTypeStatEvent
-  | AttributeTypeString
-  | AttributeTypeTeamPaint
-  | AttributeTypeTitle
-  | AttributeTypeUniqueId
-  | AttributeTypeWeldedInfo
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''AttributeType)
diff --git a/library/Rattletrap/Type/AttributeValue.hs b/library/Rattletrap/Type/AttributeValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/AttributeValue.hs
+++ /dev/null
@@ -1,83 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.AttributeValue
-  ( AttributeValue(..)
-  )
-where
-
-import Rattletrap.Type.AppliedDamageAttribute
-import Rattletrap.Type.BooleanAttribute
-import Rattletrap.Type.ByteAttribute
-import Rattletrap.Type.CamSettingsAttribute
-import Rattletrap.Type.ClubColorsAttribute
-import Rattletrap.Type.Common
-import Rattletrap.Type.DamageStateAttribute
-import Rattletrap.Type.DemolishAttribute
-import Rattletrap.Type.EnumAttribute
-import Rattletrap.Type.ExplosionAttribute
-import Rattletrap.Type.ExtendedExplosionAttribute
-import Rattletrap.Type.FlaggedIntAttribute
-import Rattletrap.Type.FlaggedByteAttribute
-import Rattletrap.Type.FloatAttribute
-import Rattletrap.Type.GameModeAttribute
-import Rattletrap.Type.Int64Attribute
-import Rattletrap.Type.IntAttribute
-import Rattletrap.Type.LoadoutAttribute
-import Rattletrap.Type.LoadoutOnlineAttribute
-import Rattletrap.Type.LoadoutsAttribute
-import Rattletrap.Type.LoadoutsOnlineAttribute
-import Rattletrap.Type.LocationAttribute
-import Rattletrap.Type.MusicStingerAttribute
-import Rattletrap.Type.PartyLeaderAttribute
-import Rattletrap.Type.PickupAttribute
-import Rattletrap.Type.PlayerHistoryKeyAttribute
-import Rattletrap.Type.PrivateMatchSettingsAttribute
-import Rattletrap.Type.QWordAttribute
-import Rattletrap.Type.ReservationAttribute
-import Rattletrap.Type.RigidBodyStateAttribute
-import Rattletrap.Type.StatEventAttribute
-import Rattletrap.Type.StringAttribute
-import Rattletrap.Type.TeamPaintAttribute
-import Rattletrap.Type.TitleAttribute
-import Rattletrap.Type.UniqueIdAttribute
-import Rattletrap.Type.WeldedInfoAttribute
-
-data AttributeValue
-  = AttributeValueAppliedDamage AppliedDamageAttribute
-  | AttributeValueBoolean BooleanAttribute
-  | AttributeValueByte ByteAttribute
-  | AttributeValueCamSettings CamSettingsAttribute
-  | AttributeValueClubColors ClubColorsAttribute
-  | AttributeValueDamageState DamageStateAttribute
-  | AttributeValueDemolish DemolishAttribute
-  | AttributeValueEnum EnumAttribute
-  | AttributeValueExplosion ExplosionAttribute
-  | AttributeValueExtendedExplosion ExtendedExplosionAttribute
-  | AttributeValueFlaggedInt FlaggedIntAttribute
-  | AttributeValueFlaggedByte FlaggedByteAttribute
-  | AttributeValueFloat FloatAttribute
-  | AttributeValueGameMode GameModeAttribute
-  | AttributeValueInt IntAttribute
-  | AttributeValueInt64 Int64Attribute
-  | AttributeValueLoadout LoadoutAttribute
-  | AttributeValueLoadoutOnline LoadoutOnlineAttribute
-  | AttributeValueLoadouts LoadoutsAttribute
-  | AttributeValueLoadoutsOnline LoadoutsOnlineAttribute
-  | AttributeValueLocation LocationAttribute
-  | AttributeValueMusicStinger MusicStingerAttribute
-  | AttributeValuePartyLeader PartyLeaderAttribute
-  | AttributeValuePickup PickupAttribute
-  | AttributeValuePlayerHistoryKey PlayerHistoryKeyAttribute
-  | AttributeValuePrivateMatchSettings PrivateMatchSettingsAttribute
-  | AttributeValueQWord QWordAttribute
-  | AttributeValueReservation ReservationAttribute
-  | AttributeValueRigidBodyState RigidBodyStateAttribute
-  | AttributeValueStatEvent StatEventAttribute
-  | AttributeValueString StringAttribute
-  | AttributeValueTeamPaint TeamPaintAttribute
-  | AttributeValueTitle TitleAttribute
-  | AttributeValueUniqueId UniqueIdAttribute
-  | AttributeValueWeldedInfo WeldedInfoAttribute
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''AttributeValue)
diff --git a/library/Rattletrap/Type/BooleanAttribute.hs b/library/Rattletrap/Type/BooleanAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/BooleanAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.BooleanAttribute
-  ( BooleanAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype BooleanAttribute = BooleanAttribute
-  { booleanAttributeValue :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''BooleanAttribute)
diff --git a/library/Rattletrap/Type/ByteAttribute.hs b/library/Rattletrap/Type/ByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ByteAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ByteAttribute
-  ( ByteAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word8le
-
-newtype ByteAttribute = ByteAttribute
-  { byteAttributeValue :: Word8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ByteAttribute)
diff --git a/library/Rattletrap/Type/Cache.hs b/library/Rattletrap/Type/Cache.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Cache.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Cache
-  ( Cache(..)
-  )
-where
-
-import Rattletrap.Type.AttributeMapping
-import Rattletrap.Type.Common
-import Rattletrap.Type.List
-import Rattletrap.Type.Word32le
-
-data Cache = Cache
-  { cacheClassId :: Word32le
-  , cacheParentCacheId :: Word32le
-  , cacheCacheId :: Word32le
-  , cacheAttributeMappings :: List AttributeMapping
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Cache)
diff --git a/library/Rattletrap/Type/CamSettingsAttribute.hs b/library/Rattletrap/Type/CamSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/CamSettingsAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.CamSettingsAttribute
-  ( CamSettingsAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Float32le
-
-data CamSettingsAttribute = CamSettingsAttribute
-  { camSettingsAttributeFov :: Float32le
-  , camSettingsAttributeHeight :: Float32le
-  , camSettingsAttributeAngle :: Float32le
-  , camSettingsAttributeDistance :: Float32le
-  , camSettingsAttributeStiffness :: Float32le
-  , camSettingsAttributeSwivelSpeed :: Float32le
-  , camSettingsAttributeTransitionSpeed :: Maybe Float32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''CamSettingsAttribute)
diff --git a/library/Rattletrap/Type/ClassAttributeMap.hs b/library/Rattletrap/Type/ClassAttributeMap.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ClassAttributeMap.hs
+++ /dev/null
@@ -1,324 +0,0 @@
-module Rattletrap.Type.ClassAttributeMap
-  ( ClassAttributeMap(..)
-  , classHasLocation
-  , classHasRotation
-  , getAttributeIdLimit
-  , getAttributeMap
-  , getAttributeName
-  , getClassName
-  , getName
-  , getObjectName
-  , makeClassAttributeMap
-  )
-where
-
-import Rattletrap.Data
-import Rattletrap.Type.AttributeMapping
-import Rattletrap.Type.Cache
-import Rattletrap.Type.ClassMapping
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.List
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-import qualified Data.IntMap as IntMap
-import qualified Data.List as List
-import qualified Data.Map as Map
-import qualified Data.Maybe as Maybe
-import qualified Data.Set as Set
-import qualified Data.Text as Text
-import qualified Data.Tuple as Tuple
-
--- | This data structure holds all the information about classes, objects, and
--- attributes in the replay. The class hierarchy is not fixed; it is encoded
--- in the 'Rattletrap.Content.Content'. Similarly, the attributes that belong
--- to each class are not fixed either. Converting the raw data into a usable
--- structure is tedious; see 'makeClassAttributeMap'.
-data ClassAttributeMap = ClassAttributeMap
-  { classAttributeMapObjectMap :: Map Word32le Str
-  -- ^ A map from object IDs to their names.
-  , classAttributeMapObjectClassMap :: Map Word32le Word32le
-  -- ^ A map from object IDs to their class IDs.
-    , classAttributeMapValue :: Map Word32le (Map Word32le Word32le)
-  -- ^ A map from class IDs to a map from attribute stream IDs to attribute
-  -- IDs.
-  , classAttributeMapNameMap :: IntMap.IntMap Str
-  } deriving (Eq, Ord, Show)
-
-type Bimap l r = (Map l r, Map r l)
-
-bimap :: (Ord l, Ord r) => [(l, r)] -> Bimap l r
-bimap xs = (Map.fromList xs, Map.fromList (fmap Tuple.swap xs))
-
-lookupL :: Ord l => l -> Bimap l r -> Maybe r
-lookupL k = Map.lookup k . fst
-
-lookupR :: Ord r => r -> Bimap l r -> Maybe l
-lookupR k = Map.lookup k . snd
-
--- | Makes a 'ClassAttributeMap' given the necessary fields from the
--- 'Rattletrap.Content.Content'.
-makeClassAttributeMap
-  :: List Str
-  -- ^ From 'Rattletrap.Content.contentObjects'.
-  -> List ClassMapping
-  -- ^ From 'Rattletrap.Content.contentClassMappings'.
-  -> List Cache
-  -- ^ From 'Rattletrap.Content.contentCaches'.
-  -> List Str
-  -- ^ From 'Rattletrap.Content.contentNames'.
-  -> ClassAttributeMap
-makeClassAttributeMap objects classMappings caches names =
-  let
-    objectMap = makeObjectMap objects
-    classMap = makeClassMap classMappings
-    objectClassMap = makeObjectClassMap objectMap classMap
-    classCache = makeClassCache classMap caches
-    attributeMap = makeAttributeMap caches
-    classIds = fmap (\(_, classId, _, _) -> classId) classCache
-    parentMap = makeParentMap classCache
-    value = Map.fromList
-      (fmap
-        (\classId ->
-          let
-            ownAttributes =
-              Maybe.fromMaybe Map.empty (Map.lookup classId attributeMap)
-            parentsAttributes = case Map.lookup classId parentMap of
-              Nothing -> []
-              Just parentClassIds -> fmap
-                (\parentClassId -> Maybe.fromMaybe
-                  Map.empty
-                  (Map.lookup parentClassId attributeMap)
-                )
-                parentClassIds
-            attributes = ownAttributes : parentsAttributes
-          in (classId, Map.fromList (concatMap Map.toList attributes))
-        )
-        classIds
-      )
-    nameMap = makeNameMap names
-  in ClassAttributeMap objectMap objectClassMap value nameMap
-
-makeNameMap :: List Str -> IntMap.IntMap Str
-makeNameMap names = IntMap.fromDistinctAscList (zip [0 ..] (listValue names))
-
-getName :: IntMap.IntMap Str -> Word32le -> Maybe Str
-getName nameMap nameIndex =
-  IntMap.lookup (fromIntegral (word32leValue nameIndex)) nameMap
-
-makeObjectClassMap
-  :: Map Word32le Str -> Bimap Word32le Str -> Map Word32le Word32le
-makeObjectClassMap objectMap classMap = do
-  let objectIds = Map.keys objectMap
-  let classIds = fmap (getClassId objectMap classMap) objectIds
-  let rawPairs = zip objectIds classIds
-  let
-    pairs = Maybe.mapMaybe
-      (\(objectId, maybeClassId) -> case maybeClassId of
-        Nothing -> Nothing
-        Just classId -> Just (objectId, classId)
-      )
-      rawPairs
-  Map.fromList pairs
-
-getClassId
-  :: Map Word32le Str -> Bimap Word32le Str -> Word32le -> Maybe Word32le
-getClassId objectMap classMap objectId = do
-  objectName <- getObjectName objectMap objectId
-  className <- getClassName objectName
-  lookupR className classMap
-
-makeClassCache
-  :: Bimap Word32le Str
-  -> List Cache
-  -> [(Maybe Str, Word32le, Word32le, Word32le)]
-makeClassCache classMap caches = fmap
-  (\cache ->
-    let classId = cacheClassId cache
-    in
-      ( lookupL classId classMap
-      , classId
-      , cacheCacheId cache
-      , cacheParentCacheId cache
-      )
-  )
-  (listValue caches)
-
-makeClassMap :: List ClassMapping -> Bimap Word32le Str
-makeClassMap classMappings = bimap
-  (fmap
-    (\classMapping ->
-      (classMappingStreamId classMapping, classMappingName classMapping)
-    )
-    (listValue classMappings)
-  )
-
-makeAttributeMap :: List Cache -> Map Word32le (Map Word32le Word32le)
-makeAttributeMap caches = Map.fromList
-  (fmap
-    (\cache ->
-      ( cacheClassId cache
-      , Map.fromList
-        (fmap
-          (\attributeMapping ->
-            ( attributeMappingStreamId attributeMapping
-            , attributeMappingObjectId attributeMapping
-            )
-          )
-          (listValue (cacheAttributeMappings cache))
-        )
-      )
-    )
-    (listValue caches)
-  )
-
-makeShallowParentMap
-  :: [(Maybe Str, Word32le, Word32le, Word32le)] -> Map Word32le Word32le
-makeShallowParentMap classCache = Map.fromList
-  (Maybe.mapMaybe
-    (\xs -> case xs of
-      [] -> Nothing
-      (maybeClassName, classId, _, parentCacheId) : rest -> do
-        parentClassId <- getParentClass maybeClassName parentCacheId rest
-        pure (classId, parentClassId)
-    )
-    (List.tails (reverse classCache))
-  )
-
-makeParentMap
-  :: [(Maybe Str, Word32le, Word32le, Word32le)] -> Map Word32le [Word32le]
-makeParentMap classCache =
-  let shallowParentMap = makeShallowParentMap classCache
-  in
-    Map.mapWithKey
-      (\classId _ -> getParentClasses shallowParentMap classId)
-      shallowParentMap
-
-getParentClasses :: Map Word32le Word32le -> Word32le -> [Word32le]
-getParentClasses shallowParentMap classId =
-  case Map.lookup classId shallowParentMap of
-    Nothing -> []
-    Just parentClassId ->
-      parentClassId : getParentClasses shallowParentMap parentClassId
-
-getParentClass
-  :: Maybe Str
-  -> Word32le
-  -> [(Maybe Str, Word32le, Word32le, Word32le)]
-  -> Maybe Word32le
-getParentClass maybeClassName parentCacheId xs = case maybeClassName of
-  Nothing -> getParentClassById parentCacheId xs
-  Just className -> getParentClassByName className parentCacheId xs
-
-getParentClassById
-  :: Word32le -> [(Maybe Str, Word32le, Word32le, Word32le)] -> Maybe Word32le
-getParentClassById parentCacheId xs =
-  case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of
-    [] -> if parentCacheId == Word32le 0
-      then Nothing
-      else getParentClassById (Word32le (word32leValue parentCacheId - 1)) xs
-    (_, parentClassId, _, _) : _ -> Just parentClassId
-
-getParentClassByName
-  :: Str
-  -> Word32le
-  -> [(Maybe Str, Word32le, Word32le, Word32le)]
-  -> Maybe Word32le
-getParentClassByName className parentCacheId xs =
-  case Map.lookup className parentClasses of
-    Nothing -> getParentClassById parentCacheId xs
-    Just parentClassName -> Maybe.maybe
-      (getParentClassById parentCacheId xs)
-      Just
-      (Maybe.listToMaybe
-        (fmap
-          (\(_, parentClassId, _, _) -> parentClassId)
-          (filter
-            (\(_, _, cacheId, _) -> cacheId <= parentCacheId)
-            (filter
-              (\(maybeClassName, _, _, _) ->
-                maybeClassName == Just parentClassName
-              )
-              xs
-            )
-          )
-        )
-      )
-
-parentClasses :: Map Str Str
-parentClasses =
-  Map.map toStr (Map.mapKeys toStr (Map.fromList rawParentClasses))
-
-makeObjectMap :: List Str -> Map Word32le Str
-makeObjectMap objects =
-  Map.fromAscList (zip (fmap Word32le [0 ..]) (listValue objects))
-
-getObjectName :: Map Word32le Str -> Word32le -> Maybe Str
-getObjectName objectMap objectId = Map.lookup objectId objectMap
-
-getClassName :: Str -> Maybe Str
-getClassName rawObjectName =
-  Map.lookup (normalizeObjectName rawObjectName) objectClasses
-
-normalizeObjectName :: Str -> Str
-normalizeObjectName objectName =
-  let
-    name = strValue objectName
-    crowdActor = Text.pack "TheWorld:PersistentLevel.CrowdActor_TA"
-    crowdManager = Text.pack "TheWorld:PersistentLevel.CrowdManager_TA"
-    boostPickup = Text.pack "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"
-    mapScoreboard = Text.pack "TheWorld:PersistentLevel.InMapScoreboard_TA"
-    breakout = Text.pack "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"
-  in if Text.isInfixOf crowdActor name
-    then Str crowdActor
-    else if Text.isInfixOf crowdManager name
-      then Str crowdManager
-      else if Text.isInfixOf boostPickup name
-        then Str boostPickup
-        else if Text.isInfixOf mapScoreboard name
-          then Str mapScoreboard
-          else if Text.isInfixOf breakout name
-            then Str breakout
-            else objectName
-
-objectClasses :: Map Str Str
-objectClasses =
-  Map.map toStr (Map.mapKeys toStr (Map.fromList rawObjectClasses))
-
-classHasLocation :: Str -> Bool
-classHasLocation className = Set.member className classesWithLocation
-
-classesWithLocation :: Set.Set Str
-classesWithLocation = Set.fromList (fmap toStr rawClassesWithLocation)
-
-classHasRotation :: Str -> Bool
-classHasRotation className = Set.member className classesWithRotation
-
-classesWithRotation :: Set.Set Str
-classesWithRotation = Set.fromList (fmap toStr rawClassesWithRotation)
-
-getAttributeIdLimit :: Map Word32le Word32le -> Maybe Word
-getAttributeIdLimit attributeMap = do
-  ((streamId, _), _) <- Map.maxViewWithKey attributeMap
-  pure (fromIntegral (word32leValue streamId))
-
-getAttributeName
-  :: ClassAttributeMap -> Map Word32le Word32le -> CompressedWord -> Maybe Str
-getAttributeName classAttributeMap attributeMap streamId = do
-  let key = Word32le (fromIntegral (compressedWordValue streamId))
-  attributeId <- Map.lookup key attributeMap
-  let objectMap = classAttributeMapObjectMap classAttributeMap
-  Map.lookup attributeId objectMap
-
-getAttributeMap
-  :: ClassAttributeMap
-  -> Map CompressedWord Word32le
-  -> CompressedWord
-  -> Maybe (Map Word32le Word32le)
-getAttributeMap classAttributeMap actorMap actorId = do
-  objectId <- Map.lookup actorId actorMap
-  let objectClassMap = classAttributeMapObjectClassMap classAttributeMap
-  classId <- Map.lookup objectId objectClassMap
-  let value = classAttributeMapValue classAttributeMap
-  Map.lookup classId value
diff --git a/library/Rattletrap/Type/ClassMapping.hs b/library/Rattletrap/Type/ClassMapping.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ClassMapping.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ClassMapping
-  ( ClassMapping(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data ClassMapping = ClassMapping
-  { classMappingName :: Str
-  , classMappingStreamId :: Word32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ClassMapping)
diff --git a/library/Rattletrap/Type/ClubColorsAttribute.hs b/library/Rattletrap/Type/ClubColorsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ClubColorsAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ClubColorsAttribute
-  ( ClubColorsAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word8le
-
-data ClubColorsAttribute = ClubColorsAttribute
-  { clubColorsAttributeBlueFlag :: Bool
-  , clubColorsAttributeBlueColor :: Word8le
-  , clubColorsAttributeOrangeFlag :: Bool
-  , clubColorsAttributeOrangeColor :: Word8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ClubColorsAttribute)
diff --git a/library/Rattletrap/Type/Common.hs b/library/Rattletrap/Type/Common.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Common.hs
+++ /dev/null
@@ -1,56 +0,0 @@
-module Rattletrap.Type.Common
-  ( Int.Int8
-  , Int.Int32
-  , Int.Int64
-  , Map.Map
-  , Text.Text
-  , Word.Word8
-  , Word.Word16
-  , Word.Word32
-  , Word.Word64
-  , deriveJson
-  )
-where
-
-import qualified Data.Aeson as Json
-import qualified Data.Aeson.TH as Json
-import qualified Data.Char as Char
-import qualified Data.Int as Int
-import qualified Data.Map as Map
-import qualified Data.Maybe as Maybe
-import qualified Data.Text as Text
-import qualified Data.Word as Word
-import qualified Language.Haskell.TH as TH
-
-deriveJson :: TH.Name -> TH.Q [TH.Dec]
-deriveJson name = Json.deriveJSON (jsonOptions (TH.nameBase name)) name
-
-jsonOptions :: String -> Json.Options
-jsonOptions prefix = Json.defaultOptions
-  { Json.constructorTagModifier = toSnakeCase . partialDropPrefix prefix
-  , Json.fieldLabelModifier = toSnakeCase
-    . partialDropPrefix (lowerFirst prefix)
-  , Json.omitNothingFields = True
-  , Json.sumEncoding = Json.ObjectWithSingleField
-  , Json.unwrapUnaryRecords = True
-  }
-
-lowerFirst :: String -> String
-lowerFirst string = case string of
-  "" -> string
-  first : rest -> Char.toLower first : rest
-
-toSnakeCase :: String -> String
-toSnakeCase = Json.camelTo2 '_'
-
-partialDropPrefix :: (Eq a, Show a) => [a] -> [a] -> [a]
-partialDropPrefix prefix list = Maybe.fromMaybe
-  (error (unwords [show prefix, "is not a prefix of", show list]))
-  (dropPrefix prefix list)
-
-dropPrefix :: Eq a => [a] -> [a] -> Maybe [a]
-dropPrefix prefix list = case prefix of
-  [] -> Just list
-  ph : pt -> case list of
-    [] -> Nothing
-    lh : lt -> if ph == lh then dropPrefix pt lt else Nothing
diff --git a/library/Rattletrap/Type/CompressedWord.hs b/library/Rattletrap/Type/CompressedWord.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/CompressedWord.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.CompressedWord
-  ( CompressedWord(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
--- | Although there's no guarantee that these values will not overflow, it's
--- exceptionally unlikely. Most 'CompressedWord's are very small.
-data CompressedWord = CompressedWord
-  { compressedWordLimit :: Word
-  , compressedWordValue :: Word
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''CompressedWord)
diff --git a/library/Rattletrap/Type/CompressedWordVector.hs b/library/Rattletrap/Type/CompressedWordVector.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/CompressedWordVector.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.CompressedWordVector
-  ( CompressedWordVector(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-
-data CompressedWordVector = CompressedWordVector
-  { compressedWordVectorX :: CompressedWord
-  , compressedWordVectorY :: CompressedWord
-  , compressedWordVectorZ :: CompressedWord
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''CompressedWordVector)
diff --git a/library/Rattletrap/Type/Content.hs b/library/Rattletrap/Type/Content.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Content.hs
+++ /dev/null
@@ -1,71 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Content
-  ( Content(..)
-  , defaultContent
-  )
-where
-
-import Rattletrap.Type.Cache
-import Rattletrap.Type.ClassMapping
-import Rattletrap.Type.Common
-import Rattletrap.Type.Frame
-import Rattletrap.Type.KeyFrame
-import Rattletrap.Type.List
-import Rattletrap.Type.Mark
-import Rattletrap.Type.Message
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
--- | Contains low-level game data about a 'Rattletrap.Replay.Replay'.
-data Content = Content
-  { contentLevels :: List Str
-  -- ^ This typically only has one element, like @stadium_oob_audio_map@.
-  , contentKeyFrames :: List KeyFrame
-  -- ^ A list of which frames are key frames. Although they aren't necessary
-  -- for replay, key frames are frames that replicate every actor. They
-  -- typically happen once every 10 seconds.
-  , contentStreamSize :: Word32le
-  -- ^ The size of the stream in bytes. This is only really necessary because
-  -- the stream has some arbitrary amount of padding at the end.
-  , contentFrames :: [Frame]
-  -- ^ The actual game data. This is where all the interesting information is.
-  , contentMessages :: List Message
-  -- ^ Debugging messages. In newer replays, this is always empty.
-  , contentMarks :: List Mark
-  -- ^ Tick marks shown on the scrubber when watching a replay.
-  , contentPackages :: List Str
-  -- ^ A list of @.upk@ files to load, like
-  -- @..\\..\\TAGame\\CookedPCConsole\\Stadium_P.upk@.
-  , contentObjects :: List Str
-  -- ^ Objects in the stream. Used for the
-  -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
-  , contentNames :: List Str
-  -- ^ It's not clear what these are used for. This list is usually not empty,
-  -- but appears unused otherwise.
-  , contentClassMappings :: List ClassMapping
-  -- ^ A mapping between classes and their ID in the stream. Used for the
-  -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
-  , contentCaches :: List Cache
-  -- ^ A list of classes along with their parent classes and attributes. Used
-  -- for the 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
-  , contentUnknown :: [Word8]
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Content)
-
-defaultContent :: Content
-defaultContent = Content
-  { contentLevels = List []
-  , contentKeyFrames = List []
-  , contentStreamSize = Word32le 0
-  , contentFrames = []
-  , contentMessages = List []
-  , contentMarks = List []
-  , contentPackages = List []
-  , contentObjects = List []
-  , contentNames = List []
-  , contentClassMappings = List []
-  , contentCaches = List []
-  , contentUnknown = []
-  }
diff --git a/library/Rattletrap/Type/DamageStateAttribute.hs b/library/Rattletrap/Type/DamageStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/DamageStateAttribute.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.DamageStateAttribute
-  ( DamageStateAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Vector
-import Rattletrap.Type.Word8le
-
-data DamageStateAttribute = DamageStateAttribute
-  { damageStateAttributeUnknown1 :: Word8le
-  , damageStateAttributeUnknown2 :: Bool
-  , damageStateAttributeUnknown3 :: Int32le
-  , damageStateAttributeUnknown4 :: Vector
-  , damageStateAttributeUnknown5 :: Bool
-  , damageStateAttributeUnknown6 :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''DamageStateAttribute)
diff --git a/library/Rattletrap/Type/DemolishAttribute.hs b/library/Rattletrap/Type/DemolishAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/DemolishAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.DemolishAttribute
-  ( DemolishAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Vector
-import Rattletrap.Type.Word32le
-
-data DemolishAttribute = DemolishAttribute
-  { demolishAttributeAttackerFlag :: Bool
-  , demolishAttributeAttackerActorId :: Word32le
-  , demolishAttributeVictimFlag :: Bool
-  , demolishAttributeVictimActorId :: Word32le
-  , demolishAttributeAttackerVelocity :: Vector
-  , demolishAttributeVictimVelocity :: Vector
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''DemolishAttribute)
diff --git a/library/Rattletrap/Type/DestroyedReplication.hs b/library/Rattletrap/Type/DestroyedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/DestroyedReplication.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.DestroyedReplication
-  ( DestroyedReplication(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
--- | Destroyed replications don't actually contain any extra information. All
--- you need to know is the actor's ID, which is given by the
--- 'Rattletrap.Replication.Replication'.
-data DestroyedReplication = DestroyedReplication
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''DestroyedReplication)
diff --git a/library/Rattletrap/Type/Dictionary.hs b/library/Rattletrap/Type/Dictionary.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Dictionary.hs
+++ /dev/null
@@ -1,69 +0,0 @@
-module Rattletrap.Type.Dictionary
-  ( Dictionary(..)
-  , dictionaryLookup
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-
-import qualified Control.Monad as Monad
-import qualified Data.Aeson as Json
-import qualified Data.Aeson.Types as Json
-import qualified Data.Map as Map
-import qualified Data.Text as Text
-
-data Dictionary a
-  = DictionaryElement Str a (Dictionary a)
-  | DictionaryEnd Str
-  deriving (Eq, Ord, Show)
-
-instance Json.FromJSON a => Json.FromJSON (Dictionary a) where
-  parseJSON = Json.withObject
-    "Dictionary"
-    (\o -> do
-      keys <- get o "keys"
-      lastKey <- get o "last_key"
-      value <- get o "value"
-      Monad.foldM
-        (\d k -> case Map.lookup k value of
-          Nothing -> fail (unwords ["missing key", show k])
-          Just v -> pure (DictionaryElement (Str k) v d)
-        )
-        (DictionaryEnd lastKey)
-        (reverse keys)
-    )
-
-instance Json.ToJSON a => Json.ToJSON (Dictionary a) where
-  toJSON d = Json.object
-    [ pair "keys" (dictionaryKeys d)
-    , pair "last_key" (dictionaryLastKey d)
-    , pair "value" (dictionaryValue d)
-    ]
-
-dictionaryKeys :: Dictionary a -> [Str]
-dictionaryKeys = fmap fst . toList
-
-dictionaryLastKey :: Dictionary a -> Str
-dictionaryLastKey x = case x of
-  DictionaryElement _ _ y -> dictionaryLastKey y
-  DictionaryEnd y -> y
-
-dictionaryLookup :: Str -> Dictionary a -> Maybe a
-dictionaryLookup k x = case x of
-  DictionaryElement j v y -> if k == j then Just v else dictionaryLookup k y
-  DictionaryEnd _ -> Nothing
-
-dictionaryValue :: Dictionary a -> Map Text a
-dictionaryValue = Map.mapKeys strValue . Map.fromList . toList
-
-get :: Json.FromJSON a => Json.Object -> String -> Json.Parser a
-get o k = o Json..: Text.pack k
-
-pair :: Json.ToJSON a => String -> a -> (Text, Json.Value)
-pair k v = (Text.pack k, Json.toJSON v)
-
-toList :: Dictionary a -> [(Str, a)]
-toList x = case x of
-  DictionaryElement k v y -> (k, v) : toList y
-  DictionaryEnd _ -> []
diff --git a/library/Rattletrap/Type/EnumAttribute.hs b/library/Rattletrap/Type/EnumAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/EnumAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.EnumAttribute
-  ( EnumAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype EnumAttribute = EnumAttribute
-  { enumAttributeValue :: Word16
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''EnumAttribute)
diff --git a/library/Rattletrap/Type/ExplosionAttribute.hs b/library/Rattletrap/Type/ExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ExplosionAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ExplosionAttribute
-  ( ExplosionAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Vector
-
-data ExplosionAttribute = ExplosionAttribute
-  { explosionAttributeFlag :: Bool
-  , explosionAttributeActorId :: Int32le
-  , explosionAttributeLocation :: Vector
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ExplosionAttribute)
diff --git a/library/Rattletrap/Type/ExtendedExplosionAttribute.hs b/library/Rattletrap/Type/ExtendedExplosionAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ExtendedExplosionAttribute.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ExtendedExplosionAttribute
-  ( ExtendedExplosionAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.ExplosionAttribute
-import Rattletrap.Type.FlaggedIntAttribute
-
-data ExtendedExplosionAttribute = ExtendedExplosionAttribute
-  { extendedExplosionAttributeExplosion :: ExplosionAttribute
-  , extendedExplosionAttributeUnknown :: FlaggedIntAttribute
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ExtendedExplosionAttribute)
diff --git a/library/Rattletrap/Type/FlaggedByteAttribute.hs b/library/Rattletrap/Type/FlaggedByteAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/FlaggedByteAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.FlaggedByteAttribute
-  ( FlaggedByteAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word8le
-
-data FlaggedByteAttribute = FlaggedByteAttribute
-  { flaggedByteAttributeFlag :: Bool
-  , flaggedByteAttributeByte :: Word8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''FlaggedByteAttribute)
diff --git a/library/Rattletrap/Type/FlaggedIntAttribute.hs b/library/Rattletrap/Type/FlaggedIntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/FlaggedIntAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.FlaggedIntAttribute
-  ( FlaggedIntAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-
-data FlaggedIntAttribute = FlaggedIntAttribute
-  { flaggedIntAttributeFlag :: Bool
-  , flaggedIntAttributeInt :: Int32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''FlaggedIntAttribute)
diff --git a/library/Rattletrap/Type/Float32le.hs b/library/Rattletrap/Type/Float32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Float32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Float32le
-  ( Float32le(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype Float32le = Float32le
-  { float32leValue :: Float
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Float32le)
diff --git a/library/Rattletrap/Type/FloatAttribute.hs b/library/Rattletrap/Type/FloatAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/FloatAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.FloatAttribute
-  ( FloatAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Float32le
-
-newtype FloatAttribute = FloatAttribute
-  { floatAttributeValue :: Float32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''FloatAttribute)
diff --git a/library/Rattletrap/Type/Frame.hs b/library/Rattletrap/Type/Frame.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Frame.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Frame
-  ( Frame(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Float32le
-import Rattletrap.Type.Replication
-
-data Frame = Frame
-  { frameTime :: Float32le
-  -- ^ Time in seconds since the beginning of the match.
-  , frameDelta :: Float32le
-  -- ^ Time in seconds since the last frame. Usually about 0.03 since there
-  -- are 30 frames per second.
-  , frameReplications :: [Replication]
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Frame)
diff --git a/library/Rattletrap/Type/GameModeAttribute.hs b/library/Rattletrap/Type/GameModeAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/GameModeAttribute.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.GameModeAttribute
-  ( GameModeAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-data GameModeAttribute = GameModeAttribute
-  { gameModeAttributeNumBits :: Int
-  -- ^ This field is guaranteed to be small. In other words, it won't overflow.
-  -- It's stored as a regular 'Int' rather than something more precise like an
-  -- 'Int8' because it just gets passed to functions that expect 'Int's.
-  -- There's no reason to do a bunch of conversions.
-  , gameModeAttributeWord :: Word8
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''GameModeAttribute)
diff --git a/library/Rattletrap/Type/Header.hs b/library/Rattletrap/Type/Header.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Header.hs
+++ /dev/null
@@ -1,61 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Header
-  ( Header(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Dictionary
-import Rattletrap.Type.Property
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
--- | Contains high-level metadata about a 'Rattletrap.Replay.Replay'.
-data Header = Header
-  { headerEngineVersion :: Word32le
-  -- ^ The "major" ("engine") version number.
-  , headerLicenseeVersion :: Word32le
-  -- ^ The "minor" ("licensee") version number.
-  , headerPatchVersion :: Maybe Word32le
-  -- ^ The "patch" ("net") version number.
-  , headerLabel :: Str
-  -- ^ Always @TAGame.Replay_Soccar_TA@.
-  , headerProperties :: Dictionary Property
-  -- ^ These properties determine how a replay will look in the list of
-  -- replays in-game. One element is required for the replay to show up:
-  --
-  -- - MapName: This is a 'Rattletrap.PropertyValue.NameProperty' with a
-  --   case-insensitive map identifier, like @Stadium_P@.
-  --
-  -- There are many other properties that affect how the replay looks in the
-  -- list of replays.
-  --
-  -- - Date: A 'Rattletrap.PropertyValue.StrProperty' with the format
-  --   @YYYY-mm-dd:HH-MM@. Dates are not validated, but the month must be
-  --   between 1 and 12 to show up. The hour is shown modulo 12 with AM or PM.
-  -- - MatchType: A 'Rattletrap.PropertyValue.NameProperty'. If this is not
-  --   one of the expected values, nothing will be shown next to the replay's
-  --   map. The expected values are: @Online@, @Offline@, @Private@, and
-  --   @Season@.
-  -- - NumFrames: This 'Rattletrap.PropertyValue.IntProperty' is used to
-  --   calculate the length of the match. There are 30 frames per second,
-  --   a typical 5-minute match has about 9,000 frames.
-  -- - PrimaryPlayerTeam: This is an 'Rattletrap.PropertyValue.IntProperty'.
-  --   It is either 0 (blue) or 1 (orange). Any other value is ignored. If
-  --   this would be 0, you don't have to set it at all.
-  -- - ReplayName: An optional 'Rattletrap.PropertyValue.StrProperty' with a
-  --   user-supplied name for the replay.
-  -- - Team0Score: The blue team's score as an
-  --   'Rattletrap.PropertyValue.IntProperty'. Can be omitted if the score is
-  --   0.
-  -- - Team1Score: The orange team's score as an
-  --   'Rattletrap.PropertyValue.IntProperty'. Can also be omitted if the
-  --   score is 0.
-  -- - TeamSize: An 'Rattletrap.PropertyValue.IntProperty' with the number of
-  --   players per team. This value is not validated, so you can put absurd
-  --   values like 99. To get an "unfair" team size like 1v4, you must set the
-  --   bUnfairBots 'Rattletrap.PropertyValue.BoolProperty' to @True@.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Header)
diff --git a/library/Rattletrap/Type/Initialization.hs b/library/Rattletrap/Type/Initialization.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Initialization.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Initialization
-  ( Initialization(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int8Vector
-import Rattletrap.Type.Vector
-
-data Initialization = Initialization
-  { initializationLocation :: Maybe Vector
-  -- ^ Not every class has an initial location. See
-  -- 'Rattletrap.Data.rawClassesWithLocation'.
-  , initializationRotation :: Maybe Int8Vector
-  -- ^ Only classes with location can have rotation, but not every one does.
-  -- See 'Rattletrap.Data.rawClassesWithRotation'.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Initialization)
diff --git a/library/Rattletrap/Type/Int32le.hs b/library/Rattletrap/Type/Int32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Int32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Int32le
-  ( Int32le(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype Int32le = Int32le
-  { int32leValue :: Int32
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Int32le)
diff --git a/library/Rattletrap/Type/Int64Attribute.hs b/library/Rattletrap/Type/Int64Attribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Int64Attribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Int64Attribute
-  ( Int64Attribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int64le
-
-newtype Int64Attribute = Int64Attribute
-  { int64AttributeValue :: Int64le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Int64Attribute)
diff --git a/library/Rattletrap/Type/Int64le.hs b/library/Rattletrap/Type/Int64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Int64le.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module Rattletrap.Type.Int64le
-  ( Int64le(..)
-  )
-where
-
-import qualified Data.Aeson as Aeson
-import qualified Data.Aeson.Types as Aeson
-import qualified Data.Int as Int
-import qualified Data.Scientific as Scientific
-import qualified Data.Text as Text
-import qualified Text.Read as Read
-
-newtype Int64le = Int64le
-  { int64leValue :: Int.Int64
-  } deriving (Eq, Ord, Show)
-
-instance Aeson.FromJSON Int64le where
-  parseJSON value = case value of
-    Aeson.String text -> case Read.readEither $ Text.unpack text of
-      Left _ -> Aeson.typeMismatch "Int64le" value
-      Right int64 -> pure $ Int64le int64
-    Aeson.Number number -> case Scientific.toBoundedInteger number of
-      Nothing -> Aeson.typeMismatch "Int64le" value
-      Just int64 -> pure $ Int64le int64
-    _ -> Aeson.typeMismatch "Int64le" value
-
-instance Aeson.ToJSON Int64le where
-  toJSON = Aeson.toJSON . show . int64leValue
diff --git a/library/Rattletrap/Type/Int8Vector.hs b/library/Rattletrap/Type/Int8Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Int8Vector.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Int8Vector
-  ( Int8Vector(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int8le
-
-data Int8Vector = Int8Vector
-  { int8VectorX :: Maybe Int8le
-  , int8VectorY :: Maybe Int8le
-  , int8VectorZ :: Maybe Int8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Int8Vector)
diff --git a/library/Rattletrap/Type/Int8le.hs b/library/Rattletrap/Type/Int8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Int8le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Int8le
-  ( Int8le(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype Int8le = Int8le
-  { int8leValue :: Int8
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Int8le)
diff --git a/library/Rattletrap/Type/IntAttribute.hs b/library/Rattletrap/Type/IntAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/IntAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.IntAttribute
-  ( IntAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-
-newtype IntAttribute = IntAttribute
-  { intAttributeValue :: Int32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''IntAttribute)
diff --git a/library/Rattletrap/Type/KeyFrame.hs b/library/Rattletrap/Type/KeyFrame.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/KeyFrame.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.KeyFrame
-  ( KeyFrame(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Float32le
-import Rattletrap.Type.Word32le
-
-data KeyFrame = KeyFrame
-  { keyFrameTime :: Float32le
-  -- ^ When this key frame occurs, in seconds.
-  , keyFrameFrame :: Word32le
-  -- ^ The frame number of this key frame, starting from 0.
-  , keyFramePosition :: Word32le
-  -- ^ The bit position of this key frame in the stream.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''KeyFrame)
diff --git a/library/Rattletrap/Type/List.hs b/library/Rattletrap/Type/List.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/List.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.List
-  ( List(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype List a = List
-  { listValue :: [a]
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''List)
diff --git a/library/Rattletrap/Type/LoadoutAttribute.hs b/library/Rattletrap/Type/LoadoutAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/LoadoutAttribute.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.LoadoutAttribute
-  ( LoadoutAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-import Rattletrap.Type.Word8le
-
-data LoadoutAttribute = LoadoutAttribute
-  { loadoutAttributeVersion :: Word8le
-  , loadoutAttributeBody :: Word32le
-  , loadoutAttributeDecal :: Word32le
-  , loadoutAttributeWheels :: Word32le
-  , loadoutAttributeRocketTrail :: Word32le
-  -- ^ Now known as "rocket boost".
-  , loadoutAttributeAntenna :: Word32le
-  , loadoutAttributeTopper :: Word32le
-  , loadoutAttributeUnknown1 :: Word32le
-  , loadoutAttributeUnknown2 :: Maybe Word32le
-  , loadoutAttributeEngineAudio :: Maybe Word32le
-  , loadoutAttributeTrail :: Maybe Word32le
-  , loadoutAttributeGoalExplosion :: Maybe Word32le
-  , loadoutAttributeBanner :: Maybe Word32le
-  , loadoutAttributeUnknown3 :: Maybe Word32le
-  , loadoutAttributeUnknown4 :: Maybe Word32le
-  , loadoutAttributeUnknown5 :: Maybe Word32le
-  , loadoutAttributeUnknown6 :: Maybe Word32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''LoadoutAttribute)
diff --git a/library/Rattletrap/Type/LoadoutOnlineAttribute.hs b/library/Rattletrap/Type/LoadoutOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/LoadoutOnlineAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.LoadoutOnlineAttribute
-  ( LoadoutOnlineAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.ProductAttribute
-
-newtype LoadoutOnlineAttribute = LoadoutOnlineAttribute
-  { loadoutAttributeValue :: [[ProductAttribute]]
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''LoadoutOnlineAttribute)
diff --git a/library/Rattletrap/Type/LoadoutsAttribute.hs b/library/Rattletrap/Type/LoadoutsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/LoadoutsAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.LoadoutsAttribute
-  ( LoadoutsAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.LoadoutAttribute
-
-data LoadoutsAttribute = LoadoutsAttribute
-  { loadoutsAttributeBlue :: LoadoutAttribute
-  , loadoutsAttributeOrange :: LoadoutAttribute
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''LoadoutsAttribute)
diff --git a/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs b/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.LoadoutsOnlineAttribute
-  ( LoadoutsOnlineAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.LoadoutOnlineAttribute
-
-data LoadoutsOnlineAttribute = LoadoutsOnlineAttribute
-  { loadoutsOnlineAttributeBlue :: LoadoutOnlineAttribute
-  , loadoutsOnlineAttributeOrange :: LoadoutOnlineAttribute
-  , loadoutsOnlineAttributeUnknown1 :: Bool
-  , loadoutsOnlineAttributeUnknown2 :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''LoadoutsOnlineAttribute)
diff --git a/library/Rattletrap/Type/LocationAttribute.hs b/library/Rattletrap/Type/LocationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/LocationAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.LocationAttribute
-  ( LocationAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Vector
-
-newtype LocationAttribute = LocationAttribute
-  { locationAttributeValue :: Vector
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''LocationAttribute)
diff --git a/library/Rattletrap/Type/Mark.hs b/library/Rattletrap/Type/Mark.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Mark.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Mark
-  ( Mark(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data Mark = Mark
-  { markValue :: Str
-  -- ^ Which type of mark this is, like @Team0Goal@.
-  , markFrame :: Word32le
-  -- ^ Which frame this mark belongs to, starting from 0.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Mark)
diff --git a/library/Rattletrap/Type/Message.hs b/library/Rattletrap/Type/Message.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Message.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Message
-  ( Message(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data Message = Message
-  { messageFrame :: Word32le
-  -- ^ Which frame this message belongs to, starting from 0.
-  , messageName :: Str
-  -- ^ The primary player's name.
-  , messageValue :: Str
-  -- ^ The content of the message.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Message)
diff --git a/library/Rattletrap/Type/MusicStingerAttribute.hs b/library/Rattletrap/Type/MusicStingerAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/MusicStingerAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.MusicStingerAttribute
-  ( MusicStingerAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-import Rattletrap.Type.Word8le
-
-data MusicStingerAttribute = MusicStingerAttribute
-  { musicStingerAttributeFlag :: Bool
-  , musicStingerAttributeCue :: Word32le
-  , musicStingerAttributeTrigger :: Word8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''MusicStingerAttribute)
diff --git a/library/Rattletrap/Type/PartyLeaderAttribute.hs b/library/Rattletrap/Type/PartyLeaderAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/PartyLeaderAttribute.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.PartyLeaderAttribute
-  ( PartyLeaderAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.RemoteId
-import Rattletrap.Type.Word8le
-
-data PartyLeaderAttribute = PartyLeaderAttribute
-  { partyLeaderAttributeSystemId :: Word8le
-  , partyLeaderAttributeId :: Maybe (RemoteId, Word8le)
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''PartyLeaderAttribute)
diff --git a/library/Rattletrap/Type/PickupAttribute.hs b/library/Rattletrap/Type/PickupAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/PickupAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.PickupAttribute
-  ( PickupAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-
-data PickupAttribute = PickupAttribute
-  { pickupAttributeInstigatorId :: Maybe Word32le
-  , pickupAttributePickedUp :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''PickupAttribute)
diff --git a/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs b/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.PlayerHistoryKeyAttribute
-  ( PlayerHistoryKeyAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype PlayerHistoryKeyAttribute = PlayerHistoryKeyAttribute
-  { playerHistoryKeyAttributeUnknown :: Word16
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''PlayerHistoryKeyAttribute)
diff --git a/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs b/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.PrivateMatchSettingsAttribute
-  ( PrivateMatchSettingsAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data PrivateMatchSettingsAttribute = PrivateMatchSettingsAttribute
-  { privateMatchSettingsAttributeMutators :: Str
-  , privateMatchSettingsAttributeJoinableBy :: Word32le
-  , privateMatchSettingsAttributeMaxPlayers :: Word32le
-  , privateMatchSettingsAttributeGameName :: Str
-  , privateMatchSettingsAttributePassword :: Str
-  , privateMatchSettingsAttributeFlag :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''PrivateMatchSettingsAttribute)
diff --git a/library/Rattletrap/Type/ProductAttribute.hs b/library/Rattletrap/Type/ProductAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ProductAttribute.hs
+++ /dev/null
@@ -1,35 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ProductAttribute
-  ( ProductAttribute(..)
-  , ProductAttributeValue(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data ProductAttributeValue
-  = ProductAttributeValuePaintedOld CompressedWord
-  | ProductAttributeValuePaintedNew Word32
-  | ProductAttributeValueTeamEditionOld CompressedWord
-  | ProductAttributeValueTeamEditionNew Word32
-  | ProductAttributeValueSpecialEdition Word32
-  | ProductAttributeValueUserColorOld (Maybe Word32)
-  | ProductAttributeValueUserColorNew Word32le
-  | ProductAttributeValueTitleId Str
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''ProductAttributeValue)
-
-data ProductAttribute = ProductAttribute
-  { productAttributeUnknown :: Bool
-  , productAttributeObjectId :: Word32le
-  , productAttributeObjectName :: Maybe Str
-  -- ^ read-only
-  , productAttributeValue :: ProductAttributeValue
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ProductAttribute)
diff --git a/library/Rattletrap/Type/Property.hs b/library/Rattletrap/Type/Property.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Property.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Property
-  ( Property(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.PropertyValue
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word64le
-
-data Property = Property
-  { propertyKind :: Str
-  , propertySize :: Word64le
-  -- ^ Not used.
-  , propertyValue :: PropertyValue Property
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Property)
diff --git a/library/Rattletrap/Type/PropertyValue.hs b/library/Rattletrap/Type/PropertyValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/PropertyValue.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.PropertyValue
-  ( PropertyValue(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Dictionary
-import Rattletrap.Type.Float32le
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.List
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word64le
-import Rattletrap.Type.Word8le
-
-data PropertyValue a
-  = PropertyValueArray (List (Dictionary a))
-  -- ^ Yes, a list of dictionaries. No, it doesn't make sense. These usually
-  -- only have one element.
-  | PropertyValueBool Word8le
-  | PropertyValueByte Str (Maybe Str)
-  -- ^ This is a strange name for essentially a key-value pair.
-  | PropertyValueFloat Float32le
-  | PropertyValueInt Int32le
-  | PropertyValueName Str
-  -- ^ It's unclear how exactly this is different than a 'StrProperty'.
-  | PropertyValueQWord Word64le
-  | PropertyValueStr Str
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''PropertyValue)
diff --git a/library/Rattletrap/Type/QWordAttribute.hs b/library/Rattletrap/Type/QWordAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/QWordAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.QWordAttribute
-  ( QWordAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word64le
-
-newtype QWordAttribute = QWordAttribute
-  { qWordAttributeValue :: Word64le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''QWordAttribute)
diff --git a/library/Rattletrap/Type/Quaternion.hs b/library/Rattletrap/Type/Quaternion.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Quaternion.hs
+++ /dev/null
@@ -1,99 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Quaternion
-  ( Quaternion(..)
-  , Component(..)
-  , toQuaternion
-  , compressPart
-  , decompressPart
-  , maxComponent
-  , maxCompressedValue
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-
-import qualified Data.Maybe as Maybe
-
-data Quaternion = Quaternion
-  { quaternionX :: Double
-  , quaternionY :: Double
-  , quaternionZ :: Double
-  , quaternionW :: Double
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Quaternion)
-
-data Component
-  = ComponentX
-  | ComponentY
-  | ComponentZ
-  | ComponentW
-  deriving (Eq, Ord, Show)
-
-toQuaternion :: Component -> Double -> Double -> Double -> Quaternion
-toQuaternion component a b c =
-  let d = toPart a b c
-  in
-    case component of
-      ComponentX -> Quaternion d a b c
-      ComponentY -> Quaternion a d b c
-      ComponentZ -> Quaternion a b d c
-      ComponentW -> Quaternion a b c d
-
-toPart :: Double -> Double -> Double -> Double
-toPart a b c = sqrt (1 - (a * a) - (b * b) - (c * c))
-
-compressPart :: Double -> CompressedWord
-compressPart =
-  CompressedWord maxCompressedValue
-    . round
-    . (* wordToDouble maxCompressedValue)
-    . (+ 0.5)
-    . (/ 2.0)
-    . (/ maxValue)
-
-decompressPart :: CompressedWord -> Double
-decompressPart x =
-  (* maxValue)
-    . (* 2.0)
-    . subtract 0.5
-    . (/ wordToDouble (compressedWordLimit x))
-    . wordToDouble
-    $ compressedWordValue x
-
-maxComponent :: Quaternion -> Component
-maxComponent quaternion =
-  let
-    x = quaternionX quaternion
-    y = quaternionY quaternion
-    z = quaternionZ quaternion
-    w = quaternionW quaternion
-    parts =
-      [(x, ComponentX), (y, ComponentY), (z, ComponentZ), (w, ComponentW)]
-    biggestPart = maximum parts
-    roundTrip = decompressPart . compressPart
-    computedPart = Maybe.fromMaybe
-      biggestPart
-      (Maybe.listToMaybe
-        (filter (\(value, _) -> value /= roundTrip value) parts)
-      )
-  in snd
-    (if (biggestPart == computedPart)
-        || (abs (fst biggestPart - fst computedPart) > 0.00001)
-      then biggestPart
-      else computedPart
-    )
-
-numBits :: Word
-numBits = 18
-
-wordToDouble :: Word -> Double
-wordToDouble = fromIntegral
-
-maxCompressedValue :: Word
-maxCompressedValue = (2 ^ numBits) - 1
-
-maxValue :: Double
-maxValue = 1.0 / sqrt 2.0
diff --git a/library/Rattletrap/Type/RemoteId.hs b/library/Rattletrap/Type/RemoteId.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/RemoteId.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.RemoteId
-  ( RemoteId(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word64le
-
-data RemoteId
-  = RemoteIdPlayStation Text [Word8]
-  | RemoteIdPsyNet (Either Word64le (Word64le, Word64le, Word64le, Word64le))
-  | RemoteIdSplitscreen Word32
-  -- ^ Really only 24 bits.
-  | RemoteIdSteam Word64le
-  | RemoteIdSwitch Word64le Word64le Word64le Word64le
-  | RemoteIdXbox Word64le
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''RemoteId)
diff --git a/library/Rattletrap/Type/Replay.hs b/library/Rattletrap/Type/Replay.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Replay.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Replay
-  ( Replay(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Content
-import Rattletrap.Type.Header
-import Rattletrap.Type.Section
-
--- | A Rocket League replay.
-data Replay = Replay
-  { replayHeader :: Section Header
-  -- ^ This has most of the high-level metadata.
-  , replayContent :: Section Content
-  -- ^ This has most of the low-level game data.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Replay)
diff --git a/library/Rattletrap/Type/Replication.hs b/library/Rattletrap/Type/Replication.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Replication.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Replication
-  ( Replication(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.ReplicationValue
-
-data Replication = Replication
-  { replicationActorId :: CompressedWord
-  , replicationValue :: ReplicationValue
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Replication)
diff --git a/library/Rattletrap/Type/ReplicationValue.hs b/library/Rattletrap/Type/ReplicationValue.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ReplicationValue.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ReplicationValue
-  ( ReplicationValue(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.DestroyedReplication
-import Rattletrap.Type.SpawnedReplication
-import Rattletrap.Type.UpdatedReplication
-
-data ReplicationValue
-  = ReplicationValueSpawned SpawnedReplication
-  -- ^ Creates a new actor.
-  | ReplicationValueUpdated UpdatedReplication
-  -- ^ Updates an existing actor.
-  | ReplicationValueDestroyed DestroyedReplication
-  -- ^ Destroys an existing actor.
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''ReplicationValue)
diff --git a/library/Rattletrap/Type/ReservationAttribute.hs b/library/Rattletrap/Type/ReservationAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/ReservationAttribute.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.ReservationAttribute
-  ( ReservationAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-import Rattletrap.Type.Str
-import Rattletrap.Type.UniqueIdAttribute
-
-data ReservationAttribute = ReservationAttribute
-  { reservationAttributeNumber :: CompressedWord
-  , reservationAttributeUniqueId :: UniqueIdAttribute
-  , reservationAttributeName :: Maybe Str
-  , reservationAttributeUnknown1 :: Bool
-  , reservationAttributeUnknown2 :: Bool
-  , reservationAttributeUnknown3 :: Maybe Word8
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''ReservationAttribute)
diff --git a/library/Rattletrap/Type/RigidBodyStateAttribute.hs b/library/Rattletrap/Type/RigidBodyStateAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/RigidBodyStateAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.RigidBodyStateAttribute
-  ( RigidBodyStateAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Rotation
-import Rattletrap.Type.Vector
-
-data RigidBodyStateAttribute = RigidBodyStateAttribute
-  { rigidBodyStateAttributeSleeping :: Bool
-  , rigidBodyStateAttributeLocation :: Vector
-  , rigidBodyStateAttributeRotation :: Rotation
-  , rigidBodyStateAttributeLinearVelocity :: Maybe Vector
-  , rigidBodyStateAttributeAngularVelocity :: Maybe Vector
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''RigidBodyStateAttribute)
diff --git a/library/Rattletrap/Type/Rotation.hs b/library/Rattletrap/Type/Rotation.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Rotation.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Rotation
-  ( Rotation(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Quaternion
-import Rattletrap.Type.CompressedWordVector
-
-data Rotation
-  = RotationCompressedWordVector CompressedWordVector
-  | RotationQuaternion Quaternion
-  deriving (Eq, Ord, Show)
-
-$(deriveJson ''Rotation)
diff --git a/library/Rattletrap/Type/Section.hs b/library/Rattletrap/Type/Section.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Section.hs
+++ /dev/null
@@ -1,41 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Section
-  ( Section(..)
-  , toSection
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-import Rattletrap.Utility.Crc
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Lazy as LazyBytes
-
--- | A section is a large piece of a 'Rattletrap.Replay.Replay'. It has a
--- 32-bit size (in bytes), a 32-bit CRC (see "Rattletrap.Utility.Crc"), and then a
--- bunch of data (the body). This interface is provided so that you don't have
--- to think about the size and CRC.
-data Section a = Section
-  { sectionSize :: Word32le
-  -- ^ read only
-  , sectionCrc :: Word32le
-  -- ^ read only
-  , sectionBody :: a
-  -- ^ The actual content in the section.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Section)
-
-toSection :: (a -> Binary.Put) -> a -> Section a
-toSection encode body =
-  let bytes = LazyBytes.toStrict . Binary.runPut $ encode body
-  in
-    Section
-      { sectionSize = Word32le . fromIntegral $ Bytes.length bytes
-      , sectionCrc = Word32le $ getCrc32 bytes
-      , sectionBody = body
-      }
diff --git a/library/Rattletrap/Type/SpawnedReplication.hs b/library/Rattletrap/Type/SpawnedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/SpawnedReplication.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.SpawnedReplication
-  ( SpawnedReplication(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Initialization
-import Rattletrap.Type.Str
-import Rattletrap.Type.Word32le
-
-data SpawnedReplication = SpawnedReplication
-  { spawnedReplicationFlag :: Bool
-  -- ^ Unclear what this is.
-  , spawnedReplicationNameIndex :: Maybe Word32le
-  , spawnedReplicationName :: Maybe Str
-  -- ^ Read-only! Changing a replication's name requires editing the
-  -- 'spawnedReplicationNameIndex' and maybe the class attribute map.
-  , spawnedReplicationObjectId :: Word32le
-  , spawnedReplicationObjectName :: Str
-  -- ^ Read-only! Changing a replication's object requires editing the class
-  -- attribute map.
-  , spawnedReplicationClassName :: Str
-  -- ^ Read-only! Changing a replication's class requires editing the class
-  -- attribute map.
-  , spawnedReplicationInitialization :: Initialization
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''SpawnedReplication)
diff --git a/library/Rattletrap/Type/StatEventAttribute.hs b/library/Rattletrap/Type/StatEventAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/StatEventAttribute.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.StatEventAttribute
-  ( StatEventAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Int32le
-
-data StatEventAttribute = StatEventAttribute
-  { statEventAttributeUnknown :: Bool
-  , statEventAttributeObjectId :: Int32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''StatEventAttribute)
diff --git a/library/Rattletrap/Type/Str.hs b/library/Rattletrap/Type/Str.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Str.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Str
-  ( Str(..)
-  , toStr
-  , fromStr
-  )
-where
-
-import Rattletrap.Type.Common
-
-import qualified Data.Text as Text
-
-newtype Str = Str
-  { strValue :: Text
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Str)
-
-toStr :: String -> Str
-toStr string = Str (Text.pack string)
-
-fromStr :: Str -> String
-fromStr text = Text.unpack (strValue text)
diff --git a/library/Rattletrap/Type/StringAttribute.hs b/library/Rattletrap/Type/StringAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/StringAttribute.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.StringAttribute
-  ( StringAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Str
-
-newtype StringAttribute = StringAttribute
-  { stringAttributeValue :: Str
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''StringAttribute)
diff --git a/library/Rattletrap/Type/TeamPaintAttribute.hs b/library/Rattletrap/Type/TeamPaintAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/TeamPaintAttribute.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.TeamPaintAttribute
-  ( TeamPaintAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-import Rattletrap.Type.Word8le
-
-data TeamPaintAttribute = TeamPaintAttribute
-  { teamPaintAttributeTeam :: Word8le
-  , teamPaintAttributePrimaryColor :: Word8le
-  , teamPaintAttributeAccentColor :: Word8le
-  , teamPaintAttributePrimaryFinish :: Word32le
-  , teamPaintAttributeAccentFinish :: Word32le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''TeamPaintAttribute)
diff --git a/library/Rattletrap/Type/TitleAttribute.hs b/library/Rattletrap/Type/TitleAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/TitleAttribute.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.TitleAttribute
-  ( TitleAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Word32le
-
-data TitleAttribute = TitleAttribute
-  { titleAttributeUnknown1 :: Bool
-  , titleAttributeUnknown2 :: Bool
-  , titleAttributeUnknown3 :: Word32le
-  , titleAttributeUnknown4 :: Word32le
-  , titleAttributeUnknown5 :: Word32le
-  , titleAttributeUnknown6 :: Word32le
-  , titleAttributeUnknown7 :: Word32le
-  , titleAttributeUnknown8 :: Bool
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''TitleAttribute)
diff --git a/library/Rattletrap/Type/UniqueIdAttribute.hs b/library/Rattletrap/Type/UniqueIdAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/UniqueIdAttribute.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.UniqueIdAttribute
-  ( UniqueIdAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.RemoteId
-import Rattletrap.Type.Word8le
-
-data UniqueIdAttribute = UniqueIdAttribute
-  { uniqueIdAttributeSystemId :: Word8le
-  , uniqueIdAttributeRemoteId :: RemoteId
-  , uniqueIdAttributeLocalId :: Word8le
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''UniqueIdAttribute)
diff --git a/library/Rattletrap/Type/UpdatedReplication.hs b/library/Rattletrap/Type/UpdatedReplication.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/UpdatedReplication.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.UpdatedReplication
-  ( UpdatedReplication(..)
-  )
-where
-
-import Rattletrap.Type.Attribute
-import Rattletrap.Type.Common
-
-newtype UpdatedReplication = UpdatedReplication
-  { updatedReplicationAttributes :: [Attribute]
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''UpdatedReplication)
diff --git a/library/Rattletrap/Type/Vector.hs b/library/Rattletrap/Type/Vector.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Vector.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Vector
-  ( Vector(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.CompressedWord
-
-data Vector = Vector
-  { vectorSize :: CompressedWord
-  , vectorBias :: Word
-  -- ^ This field is guaranteed to be small. In other words, it won't overflow.
-  -- It's stored as a regular 'Word' rather than something more precise like a
-  -- 'Word8' because it just gets passed to a functions that expect 'Word's.
-  -- There's no reason to do a bunch of conversions.
-  , vectorX :: Int
-  -- ^ See 'vectorBias'.
-  , vectorY :: Int
-  -- ^ See 'vectorBias'.
-  , vectorZ :: Int
-  -- ^ See 'vectorBias'.
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Vector)
diff --git a/library/Rattletrap/Type/WeldedInfoAttribute.hs b/library/Rattletrap/Type/WeldedInfoAttribute.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/WeldedInfoAttribute.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.WeldedInfoAttribute
-  ( WeldedInfoAttribute(..)
-  )
-where
-
-import Rattletrap.Type.Common
-import Rattletrap.Type.Float32le
-import Rattletrap.Type.Int32le
-import Rattletrap.Type.Int8Vector
-import Rattletrap.Type.Vector
-
-data WeldedInfoAttribute = WeldedInfoAttribute
-  { weldedInfoAttributeActive :: Bool
-  , weldedInfoAttributeActorId :: Int32le
-  , weldedInfoAttributeOffset :: Vector
-  , weldedInfoAttributeMass :: Float32le
-  , weldedInfoAttributeRotation :: Int8Vector
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''WeldedInfoAttribute)
diff --git a/library/Rattletrap/Type/Word32le.hs b/library/Rattletrap/Type/Word32le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Word32le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Word32le
-  ( Word32le(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype Word32le = Word32le
-  { word32leValue :: Word32
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Word32le)
diff --git a/library/Rattletrap/Type/Word64le.hs b/library/Rattletrap/Type/Word64le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Word64le.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module Rattletrap.Type.Word64le
-  ( Word64le(..)
-  )
-where
-
-import qualified Data.Aeson as Aeson
-import qualified Data.Aeson.Types as Aeson
-import qualified Data.Scientific as Scientific
-import qualified Data.Text as Text
-import qualified Data.Word as Word
-import qualified Text.Read as Read
-
-newtype Word64le = Word64le
-  { word64leValue :: Word.Word64
-  } deriving (Eq, Ord, Show)
-
-instance Aeson.FromJSON Word64le where
-  parseJSON value = case value of
-    Aeson.String text -> case Read.readEither $ Text.unpack text of
-      Left _ -> Aeson.typeMismatch "Word64le" value
-      Right word64 -> pure $ Word64le word64
-    Aeson.Number number -> case Scientific.toBoundedInteger number of
-      Nothing -> Aeson.typeMismatch "Word64le" value
-      Just word64 -> pure $ Word64le word64
-    _ -> Aeson.typeMismatch "Word64le" value
-
-instance Aeson.ToJSON Word64le where
-  toJSON = Aeson.toJSON . show . word64leValue
diff --git a/library/Rattletrap/Type/Word8le.hs b/library/Rattletrap/Type/Word8le.hs
deleted file mode 100644
--- a/library/Rattletrap/Type/Word8le.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Rattletrap.Type.Word8le
-  ( Word8le(..)
-  )
-where
-
-import Rattletrap.Type.Common
-
-newtype Word8le = Word8le
-  { word8leValue :: Word8
-  } deriving (Eq, Ord, Show)
-
-$(deriveJson ''Word8le)
diff --git a/library/Rattletrap/Utility/Bytes.hs b/library/Rattletrap/Utility/Bytes.hs
deleted file mode 100644
--- a/library/Rattletrap/Utility/Bytes.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-module Rattletrap.Utility.Bytes
-  ( encodeLatin1
-  , padBytes
-  , reverseBytes
-  )
-where
-
-import qualified Data.Bits as Bits
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Char8 as Bytes8
-import qualified Data.Text as Text
-import qualified Data.Word as Word
-
-encodeLatin1 :: Text.Text -> Bytes.ByteString
-encodeLatin1 text = Bytes8.pack (Text.unpack text)
-
-padBytes :: Integral a => a -> Bytes.ByteString -> Bytes.ByteString
-padBytes size bytes =
-  bytes <> Bytes.replicate (fromIntegral size - Bytes.length bytes) 0x00
-
-reverseByte :: Word.Word8 -> Word.Word8
-reverseByte byte =
-  Bits.shiftR (byte Bits..&. Bits.bit 7) 7
-    + Bits.shiftR (byte Bits..&. Bits.bit 6) 5
-    + Bits.shiftR (byte Bits..&. Bits.bit 5) 3
-    + Bits.shiftR (byte Bits..&. Bits.bit 4) 1
-    + Bits.shiftL (byte Bits..&. Bits.bit 3) 1
-    + Bits.shiftL (byte Bits..&. Bits.bit 2) 3
-    + Bits.shiftL (byte Bits..&. Bits.bit 1) 5
-    + Bits.shiftL (byte Bits..&. Bits.bit 0) 7
-
-reverseBytes :: Bytes.ByteString -> Bytes.ByteString
-reverseBytes = Bytes.map reverseByte
diff --git a/library/Rattletrap/Utility/Crc.hs b/library/Rattletrap/Utility/Crc.hs
deleted file mode 100644
--- a/library/Rattletrap/Utility/Crc.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-module Rattletrap.Utility.Crc
-  ( getCrc32
-  )
-where
-
-import Rattletrap.Data
-
-import qualified Data.Bits as Bits
-import qualified Data.ByteString as Bytes
-import qualified Data.IntMap as IntMap
-import qualified Data.Word as Word
-
--- | Computes the CRC32 of some bytes. This is done to ensure that the bytes
--- are valid before trying to parse them.
---
--- @
--- getCrc32 ('Data.ByteString.Lazy.pack' [0x00])
--- @
---
--- This CRC uses an initial value of @0xefcbf201@ and a polynomial of
--- @0x04c11db7@.
-getCrc32 :: Bytes.ByteString -> Word.Word32
-getCrc32 bytes = do
-  let
-    update = crc32Update crc32Table
-    initial = Bits.complement crc32Initial
-    crc = Bytes.foldl update initial bytes
-  Bits.complement crc
-
-crc32Update
-  :: IntMap.IntMap Word.Word32 -> Word.Word32 -> Word.Word8 -> Word.Word32
-crc32Update table crc byte = do
-  let
-    toWord8 :: (Integral a) => a -> Word.Word8
-    toWord8 = fromIntegral
-    toInt :: (Integral a) => a -> Int
-    toInt = fromIntegral
-    index = toInt (Bits.xor byte (toWord8 (Bits.shiftR crc 24)))
-    left = table IntMap.! index
-    right = Bits.shiftL crc 8
-  Bits.xor left right
-
-crc32Initial :: Word.Word32
-crc32Initial = 0xefcbf201
-
-crc32Table :: IntMap.IntMap Word.Word32
-crc32Table = IntMap.fromDistinctAscList (zip [0 ..] rawCrc32Table)
diff --git a/library/Rattletrap/Utility/Helper.hs b/library/Rattletrap/Utility/Helper.hs
deleted file mode 100644
--- a/library/Rattletrap/Utility/Helper.hs
+++ /dev/null
@@ -1,46 +0,0 @@
--- | This module provides helper functions for converting replays to and from
--- both their binary format and JSON.
-module Rattletrap.Utility.Helper
-  ( decodeReplayFile
-  , encodeReplayJson
-  , decodeReplayJson
-  , encodeReplayFile
-  )
-where
-
-import Rattletrap.Decode.Common
-import Rattletrap.Encode.Content
-import Rattletrap.Decode.Replay
-import Rattletrap.Encode.Replay
-import Rattletrap.Type.Replay
-import Rattletrap.Type.Section
-import Rattletrap.Type.Content
-
-import qualified Data.Aeson as Json
-import qualified Data.Aeson.Encode.Pretty as Json
-import qualified Data.Binary.Put as Binary
-import qualified Data.ByteString as Bytes
-import qualified Data.ByteString.Lazy as LazyBytes
-
--- | Parses a raw replay.
-decodeReplayFile :: Bool -> Bytes.ByteString -> Either String Replay
-decodeReplayFile fast = runDecode $ decodeReplay fast
-
--- | Encodes a replay as JSON.
-encodeReplayJson :: Replay -> Bytes.ByteString
-encodeReplayJson = LazyBytes.toStrict . Json.encodePretty' Json.defConfig
-  { Json.confCompare = compare
-  , Json.confIndent = Json.Spaces 2
-  , Json.confTrailingNewline = True
-  }
-
--- | Parses a JSON replay.
-decodeReplayJson :: Bytes.ByteString -> Either String Replay
-decodeReplayJson = Json.eitherDecodeStrict'
-
--- | Encodes a raw replay.
-encodeReplayFile :: Bool -> Replay -> Bytes.ByteString
-encodeReplayFile fast replay =
-  LazyBytes.toStrict . Binary.runPut . putReplay $ if fast
-    then replay { replayContent = toSection putContent defaultContent }
-    else replay
diff --git a/rattletrap.cabal b/rattletrap.cabal
--- a/rattletrap.cabal
+++ b/rattletrap.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.2
 
 name: rattletrap
-version: 9.0.2
+version: 9.0.3
 
 category: Game
 description: Rattletrap parses and generates Rocket League replays.
-extra-source-files: CHANGELOG.markdown README.markdown stack.yaml
+extra-source-files: CHANGELOG.markdown README.markdown
 license-file: LICENSE.markdown
 license: MIT
 maintainer: Taylor Fausak
@@ -21,20 +21,20 @@
 
 common basics
   build-depends:
-    aeson ^>= 1.4.2,
+    aeson ^>= 1.3.1,
     aeson-pretty ^>= 0.8.7,
-    base ^>= 4.11.1 || ^>= 4.12.0,
-    binary ^>= 0.8.6,
+    base ^>= 4.11.1,
+    binary ^>= 0.8.5,
     binary-bits ^>= 0.5,
     bytestring ^>= 0.10.8,
-    containers ^>= 0.6.0,
+    containers ^>= 0.5.11,
     filepath ^>= 1.4.2,
-    http-client ^>= 0.6.4,
+    http-client ^>= 0.5.13,
     http-client-tls ^>= 0.3.5,
     scientific ^>= 0.3.6,
-    template-haskell ^>= 2.13.0 || ^>= 2.14.0,
+    template-haskell ^>= 2.13.0,
     text ^>= 1.2.3,
-    transformers ^>= 0.5.6,
+    transformers ^>= 0.5.5,
   default-language: Haskell2010
   ghc-options:
     -optP -Wno-nonportable-include-path
@@ -51,7 +51,7 @@
 
   autogen-modules: Paths_rattletrap
   exposed-modules: Rattletrap
-  hs-source-dirs: library
+  hs-source-dirs: source/library
   other-modules:
     Paths_rattletrap
     Rattletrap.Console.Main
@@ -103,6 +103,7 @@
     Rattletrap.Decode.MusicStingerAttribute
     Rattletrap.Decode.PartyLeaderAttribute
     Rattletrap.Decode.PickupAttribute
+    Rattletrap.Decode.PickupAttributeNew
     Rattletrap.Decode.PlayerHistoryKeyAttribute
     Rattletrap.Decode.PrivateMatchSettingsAttribute
     Rattletrap.Decode.ProductAttribute
@@ -178,6 +179,7 @@
     Rattletrap.Encode.MusicStingerAttribute
     Rattletrap.Encode.PartyLeaderAttribute
     Rattletrap.Encode.PickupAttribute
+    Rattletrap.Encode.PickupAttributeNew
     Rattletrap.Encode.PlayerHistoryKeyAttribute
     Rattletrap.Encode.PrivateMatchSettingsAttribute
     Rattletrap.Encode.ProductAttribute
@@ -255,6 +257,7 @@
     Rattletrap.Type.MusicStingerAttribute
     Rattletrap.Type.PartyLeaderAttribute
     Rattletrap.Type.PickupAttribute
+    Rattletrap.Type.PickupAttributeNew
     Rattletrap.Type.PlayerHistoryKeyAttribute
     Rattletrap.Type.PrivateMatchSettingsAttribute
     Rattletrap.Type.ProductAttribute
@@ -292,7 +295,7 @@
 
   build-depends: rattletrap
   ghc-options: -rtsopts -threaded
-  hs-source-dirs: executables
+  hs-source-dirs: source/executable
   main-is: Main.hs
 
   if flag(static)
@@ -306,6 +309,6 @@
     rattletrap,
     temporary ^>= 1.3,
   ghc-options: -rtsopts -threaded
-  hs-source-dirs: tests
+  hs-source-dirs: source/test-suite
   main-is: Main.hs
   type: exitcode-stdio-1.0
diff --git a/source/executable/Main.hs b/source/executable/Main.hs
new file mode 100644
--- /dev/null
+++ b/source/executable/Main.hs
@@ -0,0 +1,6 @@
+module Main
+  ( main
+  )
+where
+
+import Rattletrap (main)
diff --git a/source/library/Rattletrap.hs b/source/library/Rattletrap.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap.hs
@@ -0,0 +1,162 @@
+module Rattletrap
+  ( Rattletrap.Console.Main.main
+  , Rattletrap.Console.Main.rattletrap
+  , Rattletrap.Utility.Helper.decodeReplayFile
+  , Rattletrap.Utility.Helper.encodeReplayJson
+  , Rattletrap.Utility.Helper.decodeReplayJson
+  , Rattletrap.Utility.Helper.encodeReplayFile
+  , Rattletrap.Type.AppliedDamageAttribute.AppliedDamageAttribute(..)
+  , Rattletrap.Type.Attribute.Attribute(..)
+  , Rattletrap.Type.AttributeMapping.AttributeMapping(..)
+  , Rattletrap.Type.AttributeType.AttributeType(..)
+  , Rattletrap.Type.AttributeValue.AttributeValue(..)
+  , Rattletrap.Type.BooleanAttribute.BooleanAttribute(..)
+  , Rattletrap.Type.ByteAttribute.ByteAttribute(..)
+  , Rattletrap.Type.Cache.Cache(..)
+  , Rattletrap.Type.CamSettingsAttribute.CamSettingsAttribute(..)
+  , Rattletrap.Type.ClassAttributeMap.ClassAttributeMap(..)
+  , Rattletrap.Type.ClassMapping.ClassMapping(..)
+  , Rattletrap.Type.ClubColorsAttribute.ClubColorsAttribute(..)
+  , Rattletrap.Type.CompressedWord.CompressedWord(..)
+  , Rattletrap.Type.CompressedWordVector.CompressedWordVector(..)
+  , Rattletrap.Type.Content.Content(..)
+  , Rattletrap.Type.DamageStateAttribute.DamageStateAttribute(..)
+  , Rattletrap.Type.DemolishAttribute.DemolishAttribute(..)
+  , Rattletrap.Type.DestroyedReplication.DestroyedReplication(..)
+  , Rattletrap.Type.Dictionary.Dictionary(..)
+  , Rattletrap.Type.EnumAttribute.EnumAttribute(..)
+  , Rattletrap.Type.ExplosionAttribute.ExplosionAttribute(..)
+  , Rattletrap.Type.ExtendedExplosionAttribute.ExtendedExplosionAttribute(..)
+  , Rattletrap.Type.FlaggedIntAttribute.FlaggedIntAttribute(..)
+  , Rattletrap.Type.Float32le.Float32le(..)
+  , Rattletrap.Type.FloatAttribute.FloatAttribute(..)
+  , Rattletrap.Type.Frame.Frame(..)
+  , Rattletrap.Type.GameModeAttribute.GameModeAttribute(..)
+  , Rattletrap.Type.Header.Header(..)
+  , Rattletrap.Type.Initialization.Initialization(..)
+  , Rattletrap.Type.Int32le.Int32le(..)
+  , Rattletrap.Type.Int64Attribute.Int64Attribute(..)
+  , Rattletrap.Type.Int64le.Int64le(..)
+  , Rattletrap.Type.Int8le.Int8le(..)
+  , Rattletrap.Type.Int8Vector.Int8Vector(..)
+  , Rattletrap.Type.IntAttribute.IntAttribute(..)
+  , Rattletrap.Type.KeyFrame.KeyFrame(..)
+  , Rattletrap.Type.List.List(..)
+  , Rattletrap.Type.LoadoutAttribute.LoadoutAttribute(..)
+  , Rattletrap.Type.LoadoutOnlineAttribute.LoadoutOnlineAttribute(..)
+  , Rattletrap.Type.LoadoutsAttribute.LoadoutsAttribute(..)
+  , Rattletrap.Type.LoadoutsOnlineAttribute.LoadoutsOnlineAttribute(..)
+  , Rattletrap.Type.LocationAttribute.LocationAttribute(..)
+  , Rattletrap.Type.Mark.Mark(..)
+  , Rattletrap.Type.Message.Message(..)
+  , Rattletrap.Type.MusicStingerAttribute.MusicStingerAttribute(..)
+  , Rattletrap.Type.PartyLeaderAttribute.PartyLeaderAttribute(..)
+  , Rattletrap.Type.PickupAttribute.PickupAttribute(..)
+  , Rattletrap.Type.PlayerHistoryKeyAttribute.PlayerHistoryKeyAttribute(..)
+  , Rattletrap.Type.PrivateMatchSettingsAttribute.PrivateMatchSettingsAttribute(..)
+  , Rattletrap.Type.ProductAttribute.ProductAttribute(..)
+  , Rattletrap.Type.Property.Property(..)
+  , Rattletrap.Type.PropertyValue.PropertyValue(..)
+  , Rattletrap.Type.Quaternion.Quaternion(..)
+  , Rattletrap.Type.QWordAttribute.QWordAttribute(..)
+  , Rattletrap.Type.RemoteId.RemoteId(..)
+  , Rattletrap.Type.Replay.Replay(..)
+  , Rattletrap.Type.Replication.Replication(..)
+  , Rattletrap.Type.ReplicationValue.ReplicationValue(..)
+  , Rattletrap.Type.ReservationAttribute.ReservationAttribute(..)
+  , Rattletrap.Type.RigidBodyStateAttribute.RigidBodyStateAttribute(..)
+  , Rattletrap.Type.Rotation.Rotation(..)
+  , Rattletrap.Type.Section.Section(..)
+  , Rattletrap.Type.SpawnedReplication.SpawnedReplication(..)
+  , Rattletrap.Type.StatEventAttribute.StatEventAttribute(..)
+  , Rattletrap.Type.Str.Str(..)
+  , Rattletrap.Type.StringAttribute.StringAttribute(..)
+  , Rattletrap.Type.TeamPaintAttribute.TeamPaintAttribute(..)
+  , Rattletrap.Type.TitleAttribute.TitleAttribute(..)
+  , Rattletrap.Type.UniqueIdAttribute.UniqueIdAttribute(..)
+  , Rattletrap.Type.UpdatedReplication.UpdatedReplication(..)
+  , Rattletrap.Type.Vector.Vector(..)
+  , Rattletrap.Type.WeldedInfoAttribute.WeldedInfoAttribute(..)
+  , Rattletrap.Type.Word32le.Word32le(..)
+  , Rattletrap.Type.Word64le.Word64le(..)
+  , Rattletrap.Type.Word8le.Word8le(..)
+  )
+where
+
+import qualified Rattletrap.Console.Main
+import qualified Rattletrap.Type.AppliedDamageAttribute
+import qualified Rattletrap.Type.Attribute
+import qualified Rattletrap.Type.AttributeMapping
+import qualified Rattletrap.Type.AttributeType
+import qualified Rattletrap.Type.AttributeValue
+import qualified Rattletrap.Type.BooleanAttribute
+import qualified Rattletrap.Type.ByteAttribute
+import qualified Rattletrap.Type.Cache
+import qualified Rattletrap.Type.CamSettingsAttribute
+import qualified Rattletrap.Type.ClassAttributeMap
+import qualified Rattletrap.Type.ClassMapping
+import qualified Rattletrap.Type.ClubColorsAttribute
+import qualified Rattletrap.Type.CompressedWord
+import qualified Rattletrap.Type.CompressedWordVector
+import qualified Rattletrap.Type.Content
+import qualified Rattletrap.Type.DamageStateAttribute
+import qualified Rattletrap.Type.DemolishAttribute
+import qualified Rattletrap.Type.DestroyedReplication
+import qualified Rattletrap.Type.Dictionary
+import qualified Rattletrap.Type.EnumAttribute
+import qualified Rattletrap.Type.ExplosionAttribute
+import qualified Rattletrap.Type.ExtendedExplosionAttribute
+import qualified Rattletrap.Type.FlaggedIntAttribute
+import qualified Rattletrap.Type.Float32le
+import qualified Rattletrap.Type.FloatAttribute
+import qualified Rattletrap.Type.Frame
+import qualified Rattletrap.Type.GameModeAttribute
+import qualified Rattletrap.Type.Header
+import qualified Rattletrap.Type.Initialization
+import qualified Rattletrap.Type.Int32le
+import qualified Rattletrap.Type.Int64Attribute
+import qualified Rattletrap.Type.Int64le
+import qualified Rattletrap.Type.Int8le
+import qualified Rattletrap.Type.Int8Vector
+import qualified Rattletrap.Type.IntAttribute
+import qualified Rattletrap.Type.KeyFrame
+import qualified Rattletrap.Type.List
+import qualified Rattletrap.Type.LoadoutAttribute
+import qualified Rattletrap.Type.LoadoutOnlineAttribute
+import qualified Rattletrap.Type.LoadoutsAttribute
+import qualified Rattletrap.Type.LoadoutsOnlineAttribute
+import qualified Rattletrap.Type.LocationAttribute
+import qualified Rattletrap.Type.Mark
+import qualified Rattletrap.Type.Message
+import qualified Rattletrap.Type.MusicStingerAttribute
+import qualified Rattletrap.Type.PartyLeaderAttribute
+import qualified Rattletrap.Type.PickupAttribute
+import qualified Rattletrap.Type.PlayerHistoryKeyAttribute
+import qualified Rattletrap.Type.PrivateMatchSettingsAttribute
+import qualified Rattletrap.Type.ProductAttribute
+import qualified Rattletrap.Type.Property
+import qualified Rattletrap.Type.PropertyValue
+import qualified Rattletrap.Type.Quaternion
+import qualified Rattletrap.Type.QWordAttribute
+import qualified Rattletrap.Type.RemoteId
+import qualified Rattletrap.Type.Replay
+import qualified Rattletrap.Type.Replication
+import qualified Rattletrap.Type.ReplicationValue
+import qualified Rattletrap.Type.ReservationAttribute
+import qualified Rattletrap.Type.RigidBodyStateAttribute
+import qualified Rattletrap.Type.Rotation
+import qualified Rattletrap.Type.Section
+import qualified Rattletrap.Type.SpawnedReplication
+import qualified Rattletrap.Type.StatEventAttribute
+import qualified Rattletrap.Type.Str
+import qualified Rattletrap.Type.StringAttribute
+import qualified Rattletrap.Type.TeamPaintAttribute
+import qualified Rattletrap.Type.TitleAttribute
+import qualified Rattletrap.Type.UniqueIdAttribute
+import qualified Rattletrap.Type.UpdatedReplication
+import qualified Rattletrap.Type.Vector
+import qualified Rattletrap.Type.WeldedInfoAttribute
+import qualified Rattletrap.Type.Word32le
+import qualified Rattletrap.Type.Word64le
+import qualified Rattletrap.Type.Word8le
+import qualified Rattletrap.Utility.Helper
diff --git a/source/library/Rattletrap/Console/Main.hs b/source/library/Rattletrap/Console/Main.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Console/Main.hs
@@ -0,0 +1,240 @@
+module Rattletrap.Console.Main
+  ( main
+  , rattletrap
+  )
+where
+
+import qualified Control.Monad as Monad
+import qualified Data.Aeson as Json
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Lazy as LazyBytes
+import qualified Data.Version as Version
+import qualified Network.HTTP.Client as Client
+import qualified Network.HTTP.Client.TLS as Client
+import qualified Paths_rattletrap as This
+import qualified Rattletrap.Type.Replay as Rattletrap
+import qualified Rattletrap.Utility.Helper as Rattletrap
+import qualified System.Console.GetOpt as Console
+import qualified System.Environment as Environment
+import qualified System.Exit as Exit
+import qualified System.FilePath as Path
+import qualified System.IO as IO
+import qualified Text.Printf as Printf
+
+main :: IO ()
+main = do
+  name <- Environment.getProgName
+  arguments <- Environment.getArgs
+  rattletrap name arguments
+
+rattletrap :: String -> [String] -> IO ()
+rattletrap name arguments = do
+  config <- getConfig arguments
+  Monad.when (configHelp config) (printHelp name *> Exit.exitFailure)
+  Monad.when (configVersion config) (printVersion *> Exit.exitFailure)
+  input <- getInput config
+  let decode = getDecoder config
+  replay <- either fail pure (decode input)
+  let encode = getEncoder config
+  putOutput config (encode replay)
+
+getDecoder :: Config -> Bytes.ByteString -> Either String Rattletrap.Replay
+getDecoder config = case getMode config of
+  ModeDecode -> Rattletrap.decodeReplayFile $ configFast config
+  ModeEncode -> Rattletrap.decodeReplayJson
+
+getEncoder :: Config -> Rattletrap.Replay -> Bytes.ByteString
+getEncoder config = case getMode config of
+  ModeDecode -> if configCompact config
+    then LazyBytes.toStrict . Json.encode
+    else Rattletrap.encodeReplayJson
+  ModeEncode -> Rattletrap.encodeReplayFile $ configFast config
+
+getInput :: Config -> IO Bytes.ByteString
+getInput config = case configInput config of
+  Nothing -> Bytes.getContents
+  Just fileOrUrl -> case Client.parseUrlThrow fileOrUrl of
+    Nothing -> Bytes.readFile fileOrUrl
+    Just request -> do
+      manager <- Client.newTlsManager
+      response <- Client.httpLbs request manager
+      pure (LazyBytes.toStrict (Client.responseBody response))
+
+putOutput :: Config -> Bytes.ByteString -> IO ()
+putOutput config = case configOutput config of
+  Nothing -> Bytes.putStr
+  Just file -> Bytes.writeFile file
+
+getConfig :: [String] -> IO Config
+getConfig arguments = do
+  let
+    (updates, unexpectedArguments, unknownOptions, problems) =
+      Console.getOpt' Console.Permute options arguments
+  printUnexpectedArguments unexpectedArguments
+  printUnknownOptions unknownOptions
+  printProblems problems
+  Monad.unless (null problems) Exit.exitFailure
+  either fail pure (Monad.foldM applyUpdate defaultConfig updates)
+
+type Option = Console.OptDescr Update
+
+type Update = Config -> Either String Config
+
+options :: [Option]
+options =
+  [ compactOption
+  , fastOption
+  , helpOption
+  , inputOption
+  , modeOption
+  , outputOption
+  , versionOption
+  ]
+
+compactOption :: Option
+compactOption = Console.Option
+  ['c']
+  ["compact"]
+  (Console.NoArg (\config -> pure config { configCompact = True }))
+  "minify JSON output"
+
+fastOption :: Option
+fastOption = Console.Option
+  ['f']
+  ["fast"]
+  (Console.NoArg (\config -> pure config { configFast = True }))
+  "only encode or decode the header"
+
+helpOption :: Option
+helpOption = Console.Option
+  ['h']
+  ["help"]
+  (Console.NoArg (\config -> pure config { configHelp = True }))
+  "show the help"
+
+inputOption :: Option
+inputOption = Console.Option
+  ['i']
+  ["input"]
+  (Console.ReqArg
+    (\input config -> pure config { configInput = Just input })
+    "FILE|URL"
+  )
+  "input file or URL"
+
+modeOption :: Option
+modeOption = Console.Option
+  ['m']
+  ["mode"]
+  (Console.ReqArg
+    (\rawMode config -> do
+      mode <- parseMode rawMode
+      pure config { configMode = Just mode }
+    )
+    "MODE"
+  )
+  "decode or encode"
+
+outputOption :: Option
+outputOption = Console.Option
+  ['o']
+  ["output"]
+  (Console.ReqArg
+    (\output config -> pure config { configOutput = Just output })
+    "FILE"
+  )
+  "output file"
+
+versionOption :: Option
+versionOption = Console.Option
+  ['v']
+  ["version"]
+  (Console.NoArg (\config -> pure config { configVersion = True }))
+  "show the version"
+
+applyUpdate :: Config -> Update -> Either String Config
+applyUpdate config update = update config
+
+data Config = Config
+  { configCompact :: Bool
+  , configFast :: Bool
+  , configHelp :: Bool
+  , configInput :: Maybe String
+  , configMode :: Maybe Mode
+  , configOutput :: Maybe String
+  , configVersion :: Bool
+  } deriving (Show)
+
+defaultConfig :: Config
+defaultConfig = Config
+  { configCompact = False
+  , configFast = False
+  , configHelp = False
+  , configInput = Nothing
+  , configMode = Nothing
+  , configOutput = Nothing
+  , configVersion = False
+  }
+
+getMode :: Config -> Mode
+getMode config = case getExtension (configInput config) of
+  ".json" -> ModeEncode
+  ".replay" -> ModeDecode
+  _ -> case getExtension (configOutput config) of
+    ".json" -> ModeDecode
+    ".replay" -> ModeEncode
+    _ -> ModeDecode
+
+getExtension :: Maybe String -> String
+getExtension = maybe "" Path.takeExtension
+
+data Mode
+  = ModeDecode
+  | ModeEncode
+  deriving (Show)
+
+parseMode :: String -> Either String Mode
+parseMode mode = case mode of
+  "decode" -> Right ModeDecode
+  "encode" -> Right ModeEncode
+  _ -> Left (Printf.printf "invalid mode: %s" (show mode))
+
+printUnexpectedArguments :: [String] -> IO ()
+printUnexpectedArguments = mapM_ printUnexpectedArgument
+
+printUnexpectedArgument :: String -> IO ()
+printUnexpectedArgument =
+  warnLn . Printf.printf "WARNING: unexpected argument `%s'"
+
+printUnknownOptions :: [String] -> IO ()
+printUnknownOptions = mapM_ printUnknownOption
+
+printUnknownOption :: String -> IO ()
+printUnknownOption = warnLn . Printf.printf "WARNING: unknown option `%s'"
+
+printProblems :: [String] -> IO ()
+printProblems = mapM_ printProblem
+
+printProblem :: String -> IO ()
+printProblem = warn . Printf.printf "ERROR: %s"
+
+printHelp :: String -> IO ()
+printHelp = warn . help
+
+help :: String -> String
+help name = Console.usageInfo (header name) options
+
+header :: String -> String
+header name = unwords [name, "version", version]
+
+version :: String
+version = Version.showVersion This.version
+
+printVersion :: IO ()
+printVersion = warnLn version
+
+warn :: String -> IO ()
+warn = IO.hPutStr IO.stderr
+
+warnLn :: String -> IO ()
+warnLn = IO.hPutStrLn IO.stderr
diff --git a/source/library/Rattletrap/Data.hs b/source/library/Rattletrap/Data.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Data.hs
@@ -0,0 +1,643 @@
+-- brittany --columns 120
+module Rattletrap.Data
+  ( rawParentClasses
+  , rawClassesWithLocation
+  , rawClassesWithRotation
+  , rawObjectClasses
+  , rawAttributeTypes
+  , rawCrc32Table
+  )
+where
+
+import Rattletrap.Type.AttributeType
+
+rawParentClasses :: [(String, String)]
+rawParentClasses =
+  [ ("Engine.Actor", "Core.Object")
+  , ("Engine.GameReplicationInfo", "Engine.ReplicationInfo")
+  , ("Engine.Info", "Engine.Actor")
+  , ("Engine.Pawn", "Engine.Actor")
+  , ("Engine.PlayerReplicationInfo", "Engine.ReplicationInfo")
+  , ("Engine.ReplicationInfo", "Engine.Info")
+  , ("Engine.TeamInfo", "Engine.ReplicationInfo")
+  , ("ProjectX.GRI_X", "Engine.GameReplicationInfo")
+  , ("ProjectX.Pawn_X", "Engine.Pawn")
+  , ("ProjectX.PRI_X", "Engine.PlayerReplicationInfo")
+  , ("TAGame.Ball_TA", "TAGame.RBActor_TA")
+  , ("TAGame.CameraSettingsActor_TA", "Engine.ReplicationInfo")
+  , ("TAGame.Car_Season_TA", "TAGame.PRI_TA")
+  , ("TAGame.Car_TA", "TAGame.Vehicle_TA")
+  , ("TAGame.CarComponent_Boost_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.CarComponent_Dodge_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.CarComponent_DoubleJump_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.CarComponent_FlipCar_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.CarComponent_Jump_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.CarComponent_TA", "Engine.ReplicationInfo")
+  , ("TAGame.CrowdActor_TA", "Engine.ReplicationInfo")
+  , ("TAGame.CrowdManager_TA", "Engine.ReplicationInfo")
+  , ("TAGame.GameEvent_Season_TA", "TAGame.GameEvent_Soccar_TA")
+  , ("TAGame.GameEvent_Soccar_TA", "TAGame.GameEvent_Team_TA")
+  , ("TAGame.GameEvent_SoccarPrivate_TA", "TAGame.GameEvent_Soccar_TA")
+  , ("TAGame.GameEvent_SoccarSplitscreen_TA", "TAGame.GameEvent_SoccarPrivate_TA")
+  , ("TAGame.GameEvent_TA", "Engine.ReplicationInfo")
+  , ("TAGame.GameEvent_Team_TA", "TAGame.GameEvent_TA")
+  , ("TAGame.GRI_TA", "ProjectX.GRI_X")
+  , ("TAGame.InMapScoreboard_TA", "Engine.Actor")
+  , ("TAGame.PRI_TA", "ProjectX.PRI_X")
+  , ("TAGame.RBActor_TA", "ProjectX.Pawn_X")
+  , ("TAGame.SpecialPickup_BallCarSpring_TA", "TAGame.SpecialPickup_Spring_TA")
+  , ("TAGame.SpecialPickup_BallFreeze_TA", "TAGame.SpecialPickup_Targeted_TA")
+  , ("TAGame.SpecialPickup_BallGravity_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.SpecialPickup_BallLasso_TA", "TAGame.SpecialPickup_GrapplingHook_TA")
+  , ("TAGame.SpecialPickup_BallVelcro_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.SpecialPickup_Batarang_TA", "TAGame.SpecialPickup_BallLasso_TA")
+  , ("TAGame.SpecialPickup_BoostOverride_TA", "TAGame.SpecialPickup_Targeted_TA")
+  , ("TAGame.SpecialPickup_GrapplingHook_TA", "TAGame.SpecialPickup_Targeted_TA")
+  , ("TAGame.SpecialPickup_HitForce_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.SpecialPickup_Spring_TA", "TAGame.SpecialPickup_Targeted_TA")
+  , ("TAGame.SpecialPickup_Swapper_TA", "TAGame.SpecialPickup_Targeted_TA")
+  , ("TAGame.SpecialPickup_TA", "TAGame.CarComponent_TA")
+  , ("TAGame.SpecialPickup_Targeted_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.SpecialPickup_Tornado_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.SpecialPickup_HauntedBallBeam_TA", "TAGame.SpecialPickup_TA")
+  , ("TAGame.Team_Soccar_TA", "TAGame.Team_TA")
+  , ("TAGame.Team_TA", "Engine.TeamInfo")
+  , ("TAGame.Vehicle_TA", "TAGame.RBActor_TA")
+  , ("TAGame.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_TA")
+  , ("TAGame.VehiclePickup_TA", "Engine.ReplicationInfo")
+  ]
+
+rawClassesWithLocation :: [String]
+rawClassesWithLocation =
+  [ "TAGame.Ball_Breakout_TA"
+  , "Archetypes.Ball.Ball_Breakout"
+  , "TAGame.Ball_TA"
+  , "Archetypes.Ball.Ball_BasketBall_Mutator"
+  , "Archetypes.Ball.Ball_BasketBall"
+  , "Archetypes.Ball.Ball_Basketball"
+  , "Archetypes.Ball.Ball_Default"
+  , "Archetypes.Ball.Ball_Puck"
+  , "Archetypes.Ball.CubeBall"
+  , "Archetypes.Ball.Ball_Haunted"
+  , "TAGame.Ball_Haunted_TA"
+  , "TAGame.Car_Season_TA"
+  , "TAGame.Car_TA"
+  , "Archetypes.Car.Car_Default"
+  , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
+  , "Archetypes.SpecialPickups.SpecialPickup_HauntedBallBeam"
+  , "Archetypes.SpecialPickups.SpecialPickup_Rugby"
+  , "TAGame.CameraSettingsActor_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_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"
+  , "TAGame.Default__CameraSettingsActor_TA"
+  , "TAGame.Default__PRI_TA"
+  , "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"
+  , "TheWorld:PersistentLevel.CrowdActor_TA"
+  , "TheWorld:PersistentLevel.CrowdManager_TA"
+  , "TheWorld:PersistentLevel.InMapScoreboard_TA"
+  , "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"
+  , "TAGame.HauntedBallTrapTrigger_TA"
+  , "ProjectX.NetModeReplicator"
+  ]
+
+rawClassesWithRotation :: [String]
+rawClassesWithRotation =
+  [ "TAGame.Ball_Breakout_TA"
+  , "Archetypes.Ball.Ball_Breakout"
+  , "TAGame.Ball_TA"
+  , "Archetypes.Ball.Ball_BasketBall_Mutator"
+  , "Archetypes.Ball.Ball_BasketBall"
+  , "Archetypes.Ball.Ball_Basketball"
+  , "Archetypes.Ball.Ball_Default"
+  , "Archetypes.Ball.Ball_Puck"
+  , "Archetypes.Ball.CubeBall"
+  , "Archetypes.Ball.Ball_Haunted"
+  , "TAGame.Ball_Haunted_TA"
+  , "TAGame.Car_Season_TA"
+  , "TAGame.Car_TA"
+  , "Archetypes.Car.Car_Default"
+  , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
+  , "Archetypes.SpecialPickups.SpecialPickup_HauntedBallBeam"
+  , "Archetypes.SpecialPickups.SpecialPickup_Rugby"
+  -- This comments forces Brittany to use a multi-line layout for this list.
+  ]
+
+rawObjectClasses :: [(String, String)]
+rawObjectClasses =
+  [ ("Archetypes.Ball.Ball_BasketBall_Mutator", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.Ball_Basketball", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.Ball_BasketBall", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.Ball_Beachball", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.Ball_Breakout", "TAGame.Ball_Breakout_TA")
+  , ("Archetypes.Ball.Ball_Default", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.Ball_Haunted", "TAGame.Ball_Haunted_TA")
+  , ("Archetypes.Ball.Ball_Puck", "TAGame.Ball_TA")
+  , ("Archetypes.Ball.CubeBall", "TAGame.Ball_TA")
+  , ("Archetypes.Car.Car_Default", "TAGame.Car_TA")
+  , ("Archetypes.CarComponents.CarComponent_Boost", "TAGame.CarComponent_Boost_TA")
+  , ("Archetypes.CarComponents.CarComponent_Dodge", "TAGame.CarComponent_Dodge_TA")
+  , ("Archetypes.CarComponents.CarComponent_DoubleJump", "TAGame.CarComponent_DoubleJump_TA")
+  , ("Archetypes.CarComponents.CarComponent_FlipCar", "TAGame.CarComponent_FlipCar_TA")
+  , ("Archetypes.CarComponents.CarComponent_Jump", "TAGame.CarComponent_Jump_TA")
+  , ("Archetypes.GameEvent.GameEvent_Basketball", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_BasketballPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
+  , ("Archetypes.GameEvent.GameEvent_BasketballSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
+  , ("Archetypes.GameEvent.GameEvent_Breakout", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_Hockey", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_HockeyPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
+  , ("Archetypes.GameEvent.GameEvent_HockeySplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
+  , ("Archetypes.GameEvent.GameEvent_Items", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_Season", "TAGame.GameEvent_Season_TA")
+  , ("Archetypes.GameEvent.GameEvent_Season:CarArchetype", "TAGame.Car_TA")
+  , ("Archetypes.GameEvent.GameEvent_Soccar", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_SoccarLan", "TAGame.GameEvent_Soccar_TA")
+  , ("Archetypes.GameEvent.GameEvent_SoccarPrivate", "TAGame.GameEvent_SoccarPrivate_TA")
+  , ("Archetypes.GameEvent.GameEvent_SoccarSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BallFreeze", "TAGame.SpecialPickup_BallFreeze_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BallGrapplingHook", "TAGame.SpecialPickup_GrapplingHook_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BallLasso", "TAGame.SpecialPickup_BallLasso_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BallSpring", "TAGame.SpecialPickup_BallCarSpring_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BallVelcro", "TAGame.SpecialPickup_BallVelcro_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_Batarang", "TAGame.SpecialPickup_Batarang_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_BoostOverride", "TAGame.SpecialPickup_BoostOverride_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_CarSpring", "TAGame.SpecialPickup_BallCarSpring_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_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.SpecialPickups.SpecialPickup_HauntedBallBeam", "TAGame.SpecialPickup_HauntedBallBeam_TA")
+  , ("Archetypes.SpecialPickups.SpecialPickup_Rugby", "TAGame.SpecialPickup_Rugby_TA")
+  , ("Archetypes.Teams.Team0", "TAGame.Team_Soccar_TA")
+  , ("Archetypes.Teams.Team1", "TAGame.Team_Soccar_TA")
+  , ("GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("GameInfo_Breakout.GameInfo.GameInfo_Breakout:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("GameInfo_Items.GameInfo.GameInfo_Items:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype", "TAGame.GRI_TA")
+  , ("ProjectX.Default__NetModeReplicator_X", "ProjectX.NetModeReplicator")
+  , ("TAGame.Default__CameraSettingsActor_TA", "TAGame.CameraSettingsActor_TA")
+  , ("TAGame.Default__PRI_TA", "TAGame.PRI_TA")
+  , ("TheWorld:PersistentLevel.BreakOutActor_Platform_TA", "TAGame.BreakOutActor_Platform_TA")
+  , ("TheWorld:PersistentLevel.CrowdActor_TA", "TAGame.CrowdActor_TA")
+  , ("TheWorld:PersistentLevel.CrowdManager_TA", "TAGame.CrowdManager_TA")
+  , ("TheWorld:PersistentLevel.InMapScoreboard_TA", "TAGame.InMapScoreboard_TA")
+  , ("TheWorld:PersistentLevel.VehiclePickup_Boost_TA", "TAGame.VehiclePickup_Boost_TA")
+  , ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_1", "TAGame.HauntedBallTrapTrigger_TA")
+  , ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_0", "TAGame.HauntedBallTrapTrigger_TA")
+  ]
+
+rawAttributeTypes :: [(String, AttributeType)]
+rawAttributeTypes =
+  [ ("Engine.Actor:bBlockActors", AttributeTypeBoolean)
+  , ("Engine.Actor:bCollideActors", AttributeTypeBoolean)
+  , ("Engine.Actor:bHidden", AttributeTypeBoolean)
+  , ("Engine.Actor:DrawScale", AttributeTypeFloat)
+  , ("Engine.Actor:RemoteRole", AttributeTypeEnum)
+  , ("Engine.Actor:Role", AttributeTypeEnum)
+  , ("Engine.GameReplicationInfo:bMatchIsOver", AttributeTypeBoolean)
+  , ("Engine.GameReplicationInfo:GameClass", AttributeTypeFlaggedInt)
+  , ("Engine.GameReplicationInfo:ServerName", AttributeTypeString)
+  , ("Engine.Pawn:PlayerReplicationInfo", AttributeTypeFlaggedInt)
+  , ("Engine.PlayerReplicationInfo:bBot", AttributeTypeBoolean)
+  , ("Engine.PlayerReplicationInfo:bIsSpectator", AttributeTypeBoolean)
+  , ("Engine.PlayerReplicationInfo:bReadyToPlay", AttributeTypeBoolean)
+  , ("Engine.PlayerReplicationInfo:bTimedOut", AttributeTypeBoolean)
+  , ("Engine.PlayerReplicationInfo:bWaitingPlayer", AttributeTypeBoolean)
+  , ("Engine.PlayerReplicationInfo:Ping", AttributeTypeByte)
+  , ("Engine.PlayerReplicationInfo:PlayerID", AttributeTypeInt)
+  , ("Engine.PlayerReplicationInfo:PlayerName", AttributeTypeString)
+  , ("Engine.PlayerReplicationInfo:RemoteUserData", AttributeTypeString)
+  , ("Engine.PlayerReplicationInfo:Score", AttributeTypeInt)
+  , ("Engine.PlayerReplicationInfo:Team", AttributeTypeFlaggedInt)
+  , ("Engine.PlayerReplicationInfo:UniqueId", AttributeTypeUniqueId)
+  , ("Engine.TeamInfo:Score", AttributeTypeInt)
+  , ("ProjectX.GRI_X:bGameStarted", AttributeTypeBoolean)
+  , ("ProjectX.GRI_X:GameServerID", AttributeTypeQWord)
+  , ("ProjectX.GRI_X:MatchGUID", AttributeTypeString)
+  , ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", AttributeTypeInt)
+  , ("ProjectX.GRI_X:ReplicatedGamePlaylist", AttributeTypeInt)
+  , ("ProjectX.GRI_X:Reservations", AttributeTypeReservation)
+  , ("TAGame.Ball_Breakout_TA:AppliedDamage", AttributeTypeAppliedDamage)
+  , ("TAGame.Ball_Breakout_TA:DamageIndex", AttributeTypeInt)
+  , ("TAGame.Ball_Breakout_TA:LastTeamTouch", AttributeTypeByte)
+  , ("TAGame.Ball_TA:GameEvent", AttributeTypeFlaggedInt)
+  , ("TAGame.Ball_TA:HitTeamNum", AttributeTypeByte)
+  , ("TAGame.Ball_TA:ReplicatedAddedCarBounceScale", AttributeTypeFloat)
+  , ("TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale", AttributeTypeFloat)
+  , ("TAGame.Ball_TA:ReplicatedBallScale", AttributeTypeFloat)
+  , ("TAGame.Ball_TA:ReplicatedExplosionData", AttributeTypeExplosion)
+  , ("TAGame.Ball_TA:ReplicatedExplosionDataExtended", AttributeTypeExtendedExplosion)
+  , ("TAGame.Ball_TA:ReplicatedWorldBounceScale", AttributeTypeFloat)
+  , ("TAGame.BreakOutActor_Platform_TA:DamageState", AttributeTypeDamageState)
+  , ("TAGame.CameraSettingsActor_TA:bUsingBehindView", AttributeTypeBoolean)
+  , ("TAGame.CameraSettingsActor_TA:bMouseCameraToggleEnabled", AttributeTypeBoolean)
+  , ("TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera", AttributeTypeBoolean)
+  , ("TAGame.CameraSettingsActor_TA:bUsingSwivel", AttributeTypeBoolean)
+  , ("TAGame.CameraSettingsActor_TA:CameraPitch", AttributeTypeByte)
+  , ("TAGame.CameraSettingsActor_TA:CameraYaw", AttributeTypeByte)
+  , ("TAGame.CameraSettingsActor_TA:PRI", AttributeTypeFlaggedInt)
+  , ("TAGame.CameraSettingsActor_TA:ProfileSettings", AttributeTypeCamSettings)
+  , ("TAGame.Car_TA:AddedBallForceMultiplier", AttributeTypeFloat)
+  , ("TAGame.Car_TA:AddedCarForceMultiplier", AttributeTypeFloat)
+  , ("TAGame.Car_TA:AttachedPickup", AttributeTypeFlaggedInt)
+  , ("TAGame.Car_TA:ClubColors", AttributeTypeClubColors)
+  , ("TAGame.Car_TA:ReplicatedDemolish", AttributeTypeDemolish)
+  , ("TAGame.Car_TA:TeamPaint", AttributeTypeTeamPaint)
+  , ("TAGame.CarComponent_Boost_TA:bNoBoost", AttributeTypeBoolean)
+  , ("TAGame.CarComponent_Boost_TA:BoostModifier", AttributeTypeFloat)
+  , ("TAGame.CarComponent_Boost_TA:bUnlimitedBoost", AttributeTypeBoolean)
+  , ("TAGame.CarComponent_Boost_TA:RechargeDelay", AttributeTypeFloat)
+  , ("TAGame.CarComponent_Boost_TA:RechargeRate", AttributeTypeFloat)
+  , ("TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount", AttributeTypeByte)
+  , ("TAGame.CarComponent_Boost_TA:UnlimitedBoostRefCount", AttributeTypeInt)
+  , ("TAGame.CarComponent_Dodge_TA:DodgeTorque", AttributeTypeLocation)
+  , ("TAGame.CarComponent_FlipCar_TA:bFlipRight", AttributeTypeBoolean)
+  , ("TAGame.CarComponent_FlipCar_TA:FlipCarTime", AttributeTypeFloat)
+  , ("TAGame.CarComponent_TA:ReplicatedActive", AttributeTypeByte)
+  , ("TAGame.CarComponent_TA:ReplicatedActivityTime", AttributeTypeFloat)
+  , ("TAGame.CarComponent_TA:Vehicle", AttributeTypeFlaggedInt)
+  , ("TAGame.CrowdActor_TA:GameEvent", AttributeTypeFlaggedInt)
+  , ("TAGame.CrowdActor_TA:ModifiedNoise", AttributeTypeFloat)
+  , ("TAGame.CrowdActor_TA:ReplicatedCountDownNumber", AttributeTypeInt)
+  , ("TAGame.CrowdActor_TA:ReplicatedOneShotSound", AttributeTypeFlaggedInt)
+  , ("TAGame.CrowdActor_TA:ReplicatedRoundCountDownNumber", AttributeTypeInt)
+  , ("TAGame.CrowdManager_TA:GameEvent", AttributeTypeFlaggedInt)
+  , ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", AttributeTypeFlaggedInt)
+  , ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_Soccar_TA:bClubMatch", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_Soccar_TA:bOverTime", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_Soccar_TA:bUnlimitedTime", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_Soccar_TA:GameTime", AttributeTypeInt)
+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", AttributeTypeMusicStinger)
+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", AttributeTypeByte)
+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedServerPerformanceState", AttributeTypeByte)
+  , ("TAGame.GameEvent_Soccar_TA:ReplicatedStatEvent", AttributeTypeStatEvent)
+  , ("TAGame.GameEvent_Soccar_TA:RoundNum", AttributeTypeInt)
+  , ("TAGame.GameEvent_Soccar_TA:SecondsRemaining", AttributeTypeInt)
+  , ("TAGame.GameEvent_Soccar_TA:SeriesLength", AttributeTypeInt)
+  , ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", AttributeTypeFlaggedInt)
+  , ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", AttributeTypePrivateMatchSettings)
+  , ("TAGame.GameEvent_TA:bCanVoteToForfeit", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_TA:BotSkill", AttributeTypeInt)
+  , ("TAGame.GameEvent_TA:GameMode", AttributeTypeGameMode)
+  , ("TAGame.GameEvent_TA:MatchTypeClass", AttributeTypeFlaggedInt)
+  , ("TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining", AttributeTypeInt)
+  , ("TAGame.GameEvent_TA:ReplicatedRoundCountDownNumber", AttributeTypeInt)
+  , ("TAGame.GameEvent_TA:ReplicatedStateIndex", AttributeTypeByte)
+  , ("TAGame.GameEvent_TA:ReplicatedStateName", AttributeTypeInt)
+  , ("TAGame.GameEvent_Team_TA:bForfeit", AttributeTypeBoolean)
+  , ("TAGame.GameEvent_Team_TA:MaxTeamSize", AttributeTypeInt)
+  , ("TAGame.GRI_TA:NewDedicatedServerIP", AttributeTypeString)
+  , ("TAGame.PRI_TA:bIsInSplitScreen", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bMatchMVP", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bOnlineLoadoutSet", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bOnlineLoadoutsSet", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:BotProductName", AttributeTypeInt)
+  , ("TAGame.PRI_TA:bReady", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bUsingBehindView", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bUsingItems", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:bUsingSecondaryCamera", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:CameraPitch", AttributeTypeByte)
+  , ("TAGame.PRI_TA:CameraSettings", AttributeTypeCamSettings)
+  , ("TAGame.PRI_TA:CameraYaw", AttributeTypeByte)
+  , ("TAGame.PRI_TA:ClientLoadout", AttributeTypeLoadout)
+  , ("TAGame.PRI_TA:ClientLoadoutOnline", AttributeTypeLoadoutOnline)
+  , ("TAGame.PRI_TA:ClientLoadouts", AttributeTypeLoadouts)
+  , ("TAGame.PRI_TA:ClientLoadoutsOnline", AttributeTypeLoadoutsOnline)
+  , ("TAGame.PRI_TA:ClubID", AttributeTypeInt64)
+  , ("TAGame.PRI_TA:MatchAssists", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MatchBreakoutDamage", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MatchGoals", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MatchSaves", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MatchScore", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MatchShots", AttributeTypeInt)
+  , ("TAGame.PRI_TA:MaxTimeTillItem", AttributeTypeInt)
+  , ("TAGame.PRI_TA:PartyLeader", AttributeTypePartyLeader)
+  , ("TAGame.PRI_TA:PawnType", AttributeTypeByte)
+  , ("TAGame.PRI_TA:PersistentCamera", AttributeTypeFlaggedInt)
+  , ("TAGame.PRI_TA:PlayerHistoryKey", AttributeTypePlayerHistoryKey)
+  , ("TAGame.PRI_TA:PlayerHistoryValid", AttributeTypeBoolean)
+  , ("TAGame.PRI_TA:ReplicatedGameEvent", AttributeTypeFlaggedInt)
+  , ("TAGame.PRI_TA:ReplicatedWorstNetQualityBeyondLatency", AttributeTypeByte)
+  , ("TAGame.PRI_TA:SteeringSensitivity", AttributeTypeFloat)
+  , ("TAGame.PRI_TA:SkillTier", AttributeTypeFlaggedByte)
+  , ("TAGame.PRI_TA:TimeTillItem", AttributeTypeInt)
+  , ("TAGame.PRI_TA:Title", AttributeTypeInt)
+  , ("TAGame.PRI_TA:TotalXP", AttributeTypeInt)
+  , ("TAGame.PRI_TA:PrimaryTitle", AttributeTypeTitle)
+  , ("TAGame.PRI_TA:SecondaryTitle", AttributeTypeTitle)
+  , ("TAGame.PRI_TA:SpectatorShortcut", AttributeTypeInt)
+  , ("TAGame.RBActor_TA:bFrozen", AttributeTypeBoolean)
+  , ("TAGame.RBActor_TA:bIgnoreSyncing", AttributeTypeBoolean)
+  , ("TAGame.RBActor_TA:bReplayActor", AttributeTypeBoolean)
+  , ("TAGame.RBActor_TA:ReplicatedRBState", AttributeTypeRigidBodyState)
+  , ("TAGame.RBActor_TA:WeldedInfo", AttributeTypeWeldedInfo)
+  , ("TAGame.SpecialPickup_BallFreeze_TA:RepOrigSpeed", AttributeTypeFloat)
+  , ("TAGame.SpecialPickup_BallVelcro_TA:AttachTime", AttributeTypeFloat)
+  , ("TAGame.SpecialPickup_BallVelcro_TA:bBroken", AttributeTypeBoolean)
+  , ("TAGame.SpecialPickup_BallVelcro_TA:bHit", AttributeTypeBoolean)
+  , ("TAGame.SpecialPickup_BallVelcro_TA:BreakTime", AttributeTypeFloat)
+  , ("TAGame.SpecialPickup_Targeted_TA:Targeted", AttributeTypeFlaggedInt)
+  , ("TAGame.Team_Soccar_TA:GameScore", AttributeTypeInt)
+  , ("TAGame.Team_TA:ClubColors", AttributeTypeClubColors)
+  , ("TAGame.Team_TA:ClubID", AttributeTypeInt64)
+  , ("TAGame.Team_TA:CustomTeamName", AttributeTypeString)
+  , ("TAGame.Team_TA:GameEvent", AttributeTypeFlaggedInt)
+  , ("TAGame.Team_TA:LogoData", AttributeTypeFlaggedInt)
+  , ("TAGame.Vehicle_TA:bDriving", AttributeTypeBoolean)
+  , ("TAGame.Vehicle_TA:bReplicatedHandbrake", AttributeTypeBoolean)
+  , ("TAGame.Vehicle_TA:ReplicatedSteer", AttributeTypeByte)
+  , ("TAGame.Vehicle_TA:ReplicatedThrottle", AttributeTypeByte)
+  , ("TAGame.VehiclePickup_TA:bNoPickup", AttributeTypeBoolean)
+  , ("TAGame.VehiclePickup_TA:ReplicatedPickupData", AttributeTypePickup)
+  , ("TAGame.VehiclePickup_TA:NewReplicatedPickupData", AttributeTypePickupNew)
+  , ("TAGame.Ball_Haunted_TA:LastTeamTouch", AttributeTypeByte)
+  , ("TAGame.Ball_Haunted_TA:TotalActiveBeams", AttributeTypeByte)
+  , ("TAGame.Ball_Haunted_TA:DeactivatedGoalIndex", AttributeTypeByte)
+  , ("TAGame.Ball_Haunted_TA:ReplicatedBeamBrokenValue", AttributeTypeByte)
+  , ("TAGame.Ball_Haunted_TA:bIsBallBeamed", AttributeTypeBoolean)
+  , ("TAGame.SpecialPickup_Rugby_TA:bBallWelded", AttributeTypeBoolean)
+  ]
+
+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
+  ]
diff --git a/source/library/Rattletrap/Decode/AppliedDamageAttribute.hs b/source/library/Rattletrap/Decode/AppliedDamageAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/AppliedDamageAttribute.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Decode.AppliedDamageAttribute
+  ( decodeAppliedDamageAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Decode.Vector
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.AppliedDamageAttribute
+
+decodeAppliedDamageAttributeBits
+  :: (Int, Int, Int) -> DecodeBits AppliedDamageAttribute
+decodeAppliedDamageAttributeBits version =
+  AppliedDamageAttribute
+    <$> decodeWord8leBits
+    <*> decodeVectorBits version
+    <*> decodeInt32leBits
+    <*> decodeInt32leBits
diff --git a/source/library/Rattletrap/Decode/Attribute.hs b/source/library/Rattletrap/Decode/Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Attribute.hs
@@ -0,0 +1,74 @@
+module Rattletrap.Decode.Attribute
+  ( decodeAttributesBits
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Decode.AttributeValue
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Type.Attribute
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+decodeAttributesBits
+  :: (Int, Int, Int)
+  -> ClassAttributeMap
+  -> Map CompressedWord Word32le
+  -> CompressedWord
+  -> DecodeBits [Attribute]
+decodeAttributesBits version classes actors actor = do
+  hasAttribute <- getBool
+  if hasAttribute
+    then
+      (:)
+      <$> decodeAttributeBits version classes actors actor
+      <*> decodeAttributesBits version classes actors actor
+    else pure []
+
+decodeAttributeBits
+  :: (Int, Int, Int)
+  -> ClassAttributeMap
+  -> Map CompressedWord Word32le
+  -> CompressedWord
+  -> DecodeBits Attribute
+decodeAttributeBits version classes actors actor = do
+  attributes <- lookupAttributeMap classes actors actor
+  limit <- lookupAttributeIdLimit attributes actor
+  attribute <- decodeCompressedWordBits limit
+  name <- lookupAttributeName classes attributes attribute
+  Attribute attribute name
+    <$> decodeAttributeValueBits
+          version
+          (classAttributeMapObjectMap classes)
+          name
+
+lookupAttributeMap
+  :: ClassAttributeMap
+  -> Map CompressedWord Word32le
+  -> CompressedWord
+  -> DecodeBits (Map Word32le Word32le)
+lookupAttributeMap classes actors actor = fromMaybe
+  ("could not get attribute map for " <> show actor)
+  (getAttributeMap classes actors actor)
+
+lookupAttributeIdLimit
+  :: Map Word32le Word32le -> CompressedWord -> DecodeBits Word
+lookupAttributeIdLimit attributes actor = fromMaybe
+  ("could not get attribute ID limit for " <> show actor)
+  (getAttributeIdLimit attributes)
+
+lookupAttributeName
+  :: ClassAttributeMap
+  -> Map Word32le Word32le
+  -> CompressedWord
+  -> DecodeBits Str
+lookupAttributeName classes attributes attribute = fromMaybe
+  ("could not get attribute name for " <> show attribute)
+  (getAttributeName classes attributes attribute)
+
+fromMaybe :: String -> Maybe a -> DecodeBits a
+fromMaybe message = maybe (fail message) pure
diff --git a/source/library/Rattletrap/Decode/AttributeMapping.hs b/source/library/Rattletrap/Decode/AttributeMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/AttributeMapping.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.AttributeMapping
+  ( decodeAttributeMapping
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.AttributeMapping
+
+decodeAttributeMapping :: Decode AttributeMapping
+decodeAttributeMapping =
+  AttributeMapping <$> decodeWord32le <*> decodeWord32le
diff --git a/source/library/Rattletrap/Decode/AttributeValue.hs b/source/library/Rattletrap/Decode/AttributeValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/AttributeValue.hs
@@ -0,0 +1,128 @@
+module Rattletrap.Decode.AttributeValue
+  ( decodeAttributeValueBits
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Data
+import Rattletrap.Decode.AppliedDamageAttribute
+import Rattletrap.Decode.BooleanAttribute
+import Rattletrap.Decode.ByteAttribute
+import Rattletrap.Decode.CamSettingsAttribute
+import Rattletrap.Decode.ClubColorsAttribute
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.DamageStateAttribute
+import Rattletrap.Decode.DemolishAttribute
+import Rattletrap.Decode.EnumAttribute
+import Rattletrap.Decode.ExplosionAttribute
+import Rattletrap.Decode.ExtendedExplosionAttribute
+import Rattletrap.Decode.FlaggedIntAttribute
+import Rattletrap.Decode.FlaggedByteAttribute
+import Rattletrap.Decode.FloatAttribute
+import Rattletrap.Decode.GameModeAttribute
+import Rattletrap.Decode.Int64Attribute
+import Rattletrap.Decode.IntAttribute
+import Rattletrap.Decode.LoadoutAttribute
+import Rattletrap.Decode.LoadoutOnlineAttribute
+import Rattletrap.Decode.LoadoutsAttribute
+import Rattletrap.Decode.LoadoutsOnlineAttribute
+import Rattletrap.Decode.LocationAttribute
+import Rattletrap.Decode.MusicStingerAttribute
+import Rattletrap.Decode.PartyLeaderAttribute
+import Rattletrap.Decode.PickupAttribute
+import Rattletrap.Decode.PickupAttributeNew
+import Rattletrap.Decode.PlayerHistoryKeyAttribute
+import Rattletrap.Decode.PrivateMatchSettingsAttribute
+import Rattletrap.Decode.QWordAttribute
+import Rattletrap.Decode.ReservationAttribute
+import Rattletrap.Decode.RigidBodyStateAttribute
+import Rattletrap.Decode.StatEventAttribute
+import Rattletrap.Decode.StringAttribute
+import Rattletrap.Decode.TeamPaintAttribute
+import Rattletrap.Decode.TitleAttribute
+import Rattletrap.Decode.UniqueIdAttribute
+import Rattletrap.Decode.WeldedInfoAttribute
+import Rattletrap.Type.AttributeType
+import Rattletrap.Type.AttributeValue
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+import qualified Data.Map as Map
+
+decodeAttributeValueBits
+  :: (Int, Int, Int) -> Map Word32le Str -> Str -> DecodeBits AttributeValue
+decodeAttributeValueBits version objectMap name = do
+  constructor <- maybe
+    (fail ("don't know how to get attribute value " <> show name))
+    pure
+    (Map.lookup name attributeTypes)
+  case constructor of
+    AttributeTypeAppliedDamage ->
+      AttributeValueAppliedDamage <$> decodeAppliedDamageAttributeBits version
+    AttributeTypeBoolean ->
+      AttributeValueBoolean <$> decodeBooleanAttributeBits
+    AttributeTypeByte -> AttributeValueByte <$> decodeByteAttributeBits
+    AttributeTypeCamSettings ->
+      AttributeValueCamSettings <$> decodeCamSettingsAttributeBits version
+    AttributeTypeClubColors ->
+      AttributeValueClubColors <$> decodeClubColorsAttributeBits
+    AttributeTypeDamageState ->
+      AttributeValueDamageState <$> decodeDamageStateAttributeBits version
+    AttributeTypeDemolish ->
+      AttributeValueDemolish <$> decodeDemolishAttributeBits version
+    AttributeTypeEnum -> AttributeValueEnum <$> decodeEnumAttributeBits
+    AttributeTypeExplosion ->
+      AttributeValueExplosion <$> decodeExplosionAttributeBits version
+    AttributeTypeExtendedExplosion -> AttributeValueExtendedExplosion
+      <$> decodeExtendedExplosionAttributeBits version
+    AttributeTypeFlaggedInt ->
+      AttributeValueFlaggedInt <$> decodeFlaggedIntAttributeBits
+    AttributeTypeFlaggedByte ->
+      AttributeValueFlaggedByte <$> decodeFlaggedByteAttributeBits
+    AttributeTypeFloat -> AttributeValueFloat <$> decodeFloatAttributeBits
+    AttributeTypeGameMode ->
+      AttributeValueGameMode <$> decodeGameModeAttributeBits version
+    AttributeTypeInt -> AttributeValueInt <$> decodeIntAttributeBits
+    AttributeTypeInt64 -> AttributeValueInt64 <$> decodeInt64AttributeBits
+    AttributeTypeLoadout ->
+      AttributeValueLoadout <$> decodeLoadoutAttributeBits
+    AttributeTypeLoadoutOnline ->
+      AttributeValueLoadoutOnline
+        <$> decodeLoadoutOnlineAttributeBits version objectMap
+    AttributeTypeLoadouts ->
+      AttributeValueLoadouts <$> decodeLoadoutsAttributeBits
+    AttributeTypeLoadoutsOnline ->
+      AttributeValueLoadoutsOnline
+        <$> decodeLoadoutsOnlineAttributeBits version objectMap
+    AttributeTypeLocation ->
+      AttributeValueLocation <$> decodeLocationAttributeBits version
+    AttributeTypeMusicStinger ->
+      AttributeValueMusicStinger <$> decodeMusicStingerAttributeBits
+    AttributeTypePartyLeader ->
+      AttributeValuePartyLeader <$> decodePartyLeaderAttributeBits version
+    AttributeTypePickup -> AttributeValuePickup <$> decodePickupAttributeBits
+    AttributeTypePickupNew -> AttributeValuePickupNew <$> decodePickupAttributeNewBits
+    AttributeTypePlayerHistoryKey ->
+      AttributeValuePlayerHistoryKey <$> decodePlayerHistoryKeyAttributeBits
+    AttributeTypePrivateMatchSettings ->
+      AttributeValuePrivateMatchSettings
+        <$> decodePrivateMatchSettingsAttributeBits
+    AttributeTypeQWord -> AttributeValueQWord <$> decodeQWordAttributeBits
+    AttributeTypeReservation ->
+      AttributeValueReservation <$> decodeReservationAttributeBits version
+    AttributeTypeRigidBodyState -> AttributeValueRigidBodyState
+      <$> decodeRigidBodyStateAttributeBits version
+    AttributeTypeStatEvent ->
+      AttributeValueStatEvent <$> decodeStatEventAttributeBits
+    AttributeTypeString -> AttributeValueString <$> decodeStringAttributeBits
+    AttributeTypeTeamPaint ->
+      AttributeValueTeamPaint <$> decodeTeamPaintAttributeBits
+    AttributeTypeTitle -> AttributeValueTitle <$> decodeTitleAttributeBits
+    AttributeTypeUniqueId ->
+      AttributeValueUniqueId <$> decodeUniqueIdAttributeBits version
+    AttributeTypeWeldedInfo ->
+      AttributeValueWeldedInfo <$> decodeWeldedInfoAttributeBits version
+
+attributeTypes :: Map Str AttributeType
+attributeTypes = Map.mapKeys toStr (Map.fromList rawAttributeTypes)
diff --git a/source/library/Rattletrap/Decode/BooleanAttribute.hs b/source/library/Rattletrap/Decode/BooleanAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/BooleanAttribute.hs
@@ -0,0 +1,10 @@
+module Rattletrap.Decode.BooleanAttribute
+  ( decodeBooleanAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.BooleanAttribute
+
+decodeBooleanAttributeBits :: DecodeBits BooleanAttribute
+decodeBooleanAttributeBits = BooleanAttribute <$> getBool
diff --git a/source/library/Rattletrap/Decode/ByteAttribute.hs b/source/library/Rattletrap/Decode/ByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ByteAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.ByteAttribute
+  ( decodeByteAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.ByteAttribute
+
+decodeByteAttributeBits :: DecodeBits ByteAttribute
+decodeByteAttributeBits = ByteAttribute <$> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/Cache.hs b/source/library/Rattletrap/Decode/Cache.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Cache.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Decode.Cache
+  ( decodeCache
+  )
+where
+
+import Rattletrap.Decode.AttributeMapping
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.List
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.Cache
+
+decodeCache :: Decode Cache
+decodeCache =
+  Cache
+    <$> decodeWord32le
+    <*> decodeWord32le
+    <*> decodeWord32le
+    <*> decodeList decodeAttributeMapping
diff --git a/source/library/Rattletrap/Decode/CamSettingsAttribute.hs b/source/library/Rattletrap/Decode/CamSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/CamSettingsAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Decode.CamSettingsAttribute
+  ( decodeCamSettingsAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Float32le
+import Rattletrap.Type.CamSettingsAttribute
+
+decodeCamSettingsAttributeBits
+  :: (Int, Int, Int) -> DecodeBits CamSettingsAttribute
+decodeCamSettingsAttributeBits version =
+  CamSettingsAttribute
+    <$> decodeFloat32leBits
+    <*> decodeFloat32leBits
+    <*> decodeFloat32leBits
+    <*> decodeFloat32leBits
+    <*> decodeFloat32leBits
+    <*> decodeFloat32leBits
+    <*> decodeWhen (version >= (868, 20, 0)) decodeFloat32leBits
diff --git a/source/library/Rattletrap/Decode/ClassMapping.hs b/source/library/Rattletrap/Decode/ClassMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ClassMapping.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.ClassMapping
+  ( decodeClassMapping
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.ClassMapping
+
+decodeClassMapping :: Decode ClassMapping
+decodeClassMapping = ClassMapping <$> decodeStr <*> decodeWord32le
diff --git a/source/library/Rattletrap/Decode/ClubColorsAttribute.hs b/source/library/Rattletrap/Decode/ClubColorsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ClubColorsAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.ClubColorsAttribute
+  ( decodeClubColorsAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.ClubColorsAttribute
+
+decodeClubColorsAttributeBits :: DecodeBits ClubColorsAttribute
+decodeClubColorsAttributeBits =
+  ClubColorsAttribute
+    <$> getBool
+    <*> decodeWord8leBits
+    <*> getBool
+    <*> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/Common.hs b/source/library/Rattletrap/Decode/Common.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Common.hs
@@ -0,0 +1,70 @@
+module Rattletrap.Decode.Common
+  ( Decode
+  , DecodeBits
+  , decodeWhen
+  , getBitsLE
+  , getByteStringBits
+  , getWord8Bits
+  , runDecode
+  , runDecodeBits
+  , toBits
+  , Binary.getFloatle
+  , Binary.getByteString
+  , Binary.getInt8
+  , Binary.getInt32le
+  , Binary.getInt64le
+  , Binary.getWord8
+  , Binary.getWord32le
+  , Binary.getWord64le
+  , BinaryBits.getBool
+  )
+where
+
+import qualified Control.Applicative as Applicative
+import qualified Control.Monad as Monad
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Get as BinaryBits
+import qualified Data.Binary.Get as Binary
+import qualified Data.Bits as Bits
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Lazy as LazyBytes
+import qualified Data.Word as Word
+import qualified Rattletrap.Utility.Bytes as Utility
+
+type Decode = Binary.Get
+
+type DecodeBits = BinaryBits.BitGet
+
+decodeWhen
+  :: (Applicative m, Applicative.Alternative f) => Bool -> m a -> m (f a)
+decodeWhen p f = if p then fmap pure f else pure Applicative.empty
+
+getByteStringBits :: Int -> DecodeBits Bytes.ByteString
+getByteStringBits = BinaryBits.getByteString
+
+getWord8Bits :: Int -> DecodeBits Word.Word8
+getWord8Bits = BinaryBits.getWord8
+
+runDecode :: Decode a -> Bytes.ByteString -> Either String a
+runDecode decode bytes =
+  case Binary.runGetOrFail decode (LazyBytes.fromStrict bytes) of
+    Left (_, _, x) -> Left x
+    Right (_, _, x) -> Right x
+
+runDecodeBits :: DecodeBits a -> Bytes.ByteString -> Either String a
+runDecodeBits = runDecode . BinaryBits.runBitGet
+
+toBits :: Decode a -> Int -> DecodeBits a
+toBits decode size = do
+  bytes <- BinaryBits.getByteString size
+  case runDecode decode (Utility.reverseBytes bytes) of
+    Left problem -> fail problem
+    Right result -> pure result
+
+getBitsLE :: Bits.Bits a => Int -> BinaryBits.BitGet a
+getBitsLE size = do
+  bits <- Monad.replicateM size BinaryBits.getBool
+  pure $ foldr
+    (\bit x -> let y = Bits.shiftL x 1 in if bit then Bits.setBit y 0 else y)
+    Bits.zeroBits
+    bits
diff --git a/source/library/Rattletrap/Decode/CompressedWord.hs b/source/library/Rattletrap/Decode/CompressedWord.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/CompressedWord.hs
@@ -0,0 +1,30 @@
+module Rattletrap.Decode.CompressedWord
+  ( decodeCompressedWordBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.CompressedWord
+
+import qualified Data.Bits as Bits
+
+decodeCompressedWordBits :: Word -> DecodeBits CompressedWord
+decodeCompressedWordBits limit =
+  CompressedWord limit <$> step limit (getMaxBits limit) 0 0
+
+getMaxBits :: Word -> Word
+getMaxBits x = do
+  let
+    n :: Word
+    n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
+  if x < 1024 && x == 2 ^ n then n + 1 else n
+
+step :: Word -> Word -> Word -> Word -> DecodeBits Word
+step limit maxBits position value = do
+  let x = Bits.shiftL 1 (fromIntegral position) :: Word
+  if position < maxBits && value + x <= limit
+    then do
+      bit <- getBool
+      let newValue = if bit then value + x else value
+      step limit maxBits (position + 1) newValue
+    else pure value
diff --git a/source/library/Rattletrap/Decode/CompressedWordVector.hs b/source/library/Rattletrap/Decode/CompressedWordVector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/CompressedWordVector.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Decode.CompressedWordVector
+  ( decodeCompressedWordVectorBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Type.CompressedWordVector
+
+decodeCompressedWordVectorBits :: DecodeBits CompressedWordVector
+decodeCompressedWordVectorBits =
+  CompressedWordVector
+    <$> decodeCompressedWordBits limit
+    <*> decodeCompressedWordBits limit
+    <*> decodeCompressedWordBits limit
+
+limit :: Word
+limit = 65536
diff --git a/source/library/Rattletrap/Decode/Content.hs b/source/library/Rattletrap/Decode/Content.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Content.hs
@@ -0,0 +1,73 @@
+module Rattletrap.Decode.Content
+  ( decodeContent
+  )
+where
+
+import Rattletrap.Decode.Cache
+import Rattletrap.Decode.ClassMapping
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Frame
+import Rattletrap.Decode.KeyFrame
+import Rattletrap.Decode.List
+import Rattletrap.Decode.Mark
+import Rattletrap.Decode.Message
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.Content
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Bytes
+
+import qualified Control.Monad.Trans.State as State
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+decodeContent
+  :: (Int, Int, Int)
+  -- ^ Version numbers, usually from 'Rattletrap.Header.getVersion'.
+  -> Int
+  -- ^ The number of frames in the stream, usually from
+  -- 'Rattletrap.Header.getNumFrames'.
+  -> Word
+  -- ^ The maximum number of channels in the stream, usually from
+  -- 'Rattletrap.Header.getMaxChannels'.
+  -> Decode Content
+decodeContent version numFrames maxChannels = do
+  (levels, keyFrames, streamSize) <-
+    (,,)
+    <$> decodeList decodeStr
+    <*> decodeList decodeKeyFrame
+    <*> decodeWord32le
+  (stream, messages, marks, packages, objects, names, classMappings, caches) <-
+    (,,,,,,,)
+    <$> getByteString (fromIntegral (word32leValue streamSize))
+    <*> decodeList decodeMessage
+    <*> decodeList decodeMark
+    <*> decodeList decodeStr
+    <*> decodeList decodeStr
+    <*> decodeList decodeStr
+    <*> decodeList decodeClassMapping
+    <*> decodeList decodeCache
+  let
+    classAttributeMap =
+      makeClassAttributeMap objects classMappings caches names
+    bitGet = State.evalStateT
+      (decodeFramesBits version numFrames maxChannels classAttributeMap)
+      mempty
+  frames <- either fail pure (runDecodeBits bitGet (reverseBytes stream))
+  unknown <- Binary.getRemainingLazyByteString
+  pure
+    (Content
+      levels
+      keyFrames
+      streamSize
+      frames
+      messages
+      marks
+      packages
+      objects
+      names
+      classMappings
+      caches
+      (LazyBytes.unpack unknown)
+    )
diff --git a/source/library/Rattletrap/Decode/DamageStateAttribute.hs b/source/library/Rattletrap/Decode/DamageStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/DamageStateAttribute.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Decode.DamageStateAttribute
+  ( decodeDamageStateAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Decode.Vector
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.DamageStateAttribute
+
+decodeDamageStateAttributeBits
+  :: (Int, Int, Int) -> DecodeBits DamageStateAttribute
+decodeDamageStateAttributeBits version =
+  DamageStateAttribute
+    <$> decodeWord8leBits
+    <*> getBool
+    <*> decodeInt32leBits
+    <*> decodeVectorBits version
+    <*> getBool
+    <*> getBool
diff --git a/source/library/Rattletrap/Decode/DemolishAttribute.hs b/source/library/Rattletrap/Decode/DemolishAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/DemolishAttribute.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Decode.DemolishAttribute
+  ( decodeDemolishAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Vector
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.DemolishAttribute
+
+decodeDemolishAttributeBits :: (Int, Int, Int) -> DecodeBits DemolishAttribute
+decodeDemolishAttributeBits version =
+  DemolishAttribute
+    <$> getBool
+    <*> decodeWord32leBits
+    <*> getBool
+    <*> decodeWord32leBits
+    <*> decodeVectorBits version
+    <*> decodeVectorBits version
diff --git a/source/library/Rattletrap/Decode/DestroyedReplication.hs b/source/library/Rattletrap/Decode/DestroyedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/DestroyedReplication.hs
@@ -0,0 +1,10 @@
+module Rattletrap.Decode.DestroyedReplication
+  ( decodeDestroyedReplicationBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.DestroyedReplication
+
+decodeDestroyedReplicationBits :: DecodeBits DestroyedReplication
+decodeDestroyedReplicationBits = pure DestroyedReplication
diff --git a/source/library/Rattletrap/Decode/Dictionary.hs b/source/library/Rattletrap/Decode/Dictionary.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Dictionary.hs
@@ -0,0 +1,17 @@
+module Rattletrap.Decode.Dictionary
+  ( decodeDictionary
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Type.Dictionary
+import Rattletrap.Type.Str
+
+decodeDictionary :: Decode a -> Decode (Dictionary a)
+decodeDictionary decodeValue = do
+  key <- decodeStr
+  case filter (/= '\x00') (fromStr key) of
+    "None" -> pure (DictionaryEnd key)
+    _ ->
+      DictionaryElement key <$> decodeValue <*> decodeDictionary decodeValue
diff --git a/source/library/Rattletrap/Decode/EnumAttribute.hs b/source/library/Rattletrap/Decode/EnumAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/EnumAttribute.hs
@@ -0,0 +1,10 @@
+module Rattletrap.Decode.EnumAttribute
+  ( decodeEnumAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.EnumAttribute
+
+decodeEnumAttributeBits :: DecodeBits EnumAttribute
+decodeEnumAttributeBits = EnumAttribute <$> getBitsLE 11
diff --git a/source/library/Rattletrap/Decode/ExplosionAttribute.hs b/source/library/Rattletrap/Decode/ExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ExplosionAttribute.hs
@@ -0,0 +1,17 @@
+module Rattletrap.Decode.ExplosionAttribute
+  ( decodeExplosionAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Decode.Vector
+import Rattletrap.Type.ExplosionAttribute
+
+decodeExplosionAttributeBits
+  :: (Int, Int, Int) -> DecodeBits ExplosionAttribute
+decodeExplosionAttributeBits version =
+  ExplosionAttribute
+    <$> getBool
+    <*> decodeInt32leBits
+    <*> decodeVectorBits version
diff --git a/source/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs b/source/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ExtendedExplosionAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.ExtendedExplosionAttribute
+  ( decodeExtendedExplosionAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.ExplosionAttribute
+import Rattletrap.Decode.FlaggedIntAttribute
+import Rattletrap.Type.ExtendedExplosionAttribute
+
+decodeExtendedExplosionAttributeBits
+  :: (Int, Int, Int) -> DecodeBits ExtendedExplosionAttribute
+decodeExtendedExplosionAttributeBits version =
+  ExtendedExplosionAttribute
+    <$> decodeExplosionAttributeBits version
+    <*> decodeFlaggedIntAttributeBits
diff --git a/source/library/Rattletrap/Decode/FlaggedByteAttribute.hs b/source/library/Rattletrap/Decode/FlaggedByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/FlaggedByteAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.FlaggedByteAttribute
+  ( decodeFlaggedByteAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.FlaggedByteAttribute
+
+
+decodeFlaggedByteAttributeBits :: DecodeBits FlaggedByteAttribute
+decodeFlaggedByteAttributeBits = FlaggedByteAttribute <$> getBool <*> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/FlaggedIntAttribute.hs b/source/library/Rattletrap/Decode/FlaggedIntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/FlaggedIntAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.FlaggedIntAttribute
+  ( decodeFlaggedIntAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Type.FlaggedIntAttribute
+
+decodeFlaggedIntAttributeBits :: DecodeBits FlaggedIntAttribute
+decodeFlaggedIntAttributeBits =
+  FlaggedIntAttribute <$> getBool <*> decodeInt32leBits
diff --git a/source/library/Rattletrap/Decode/Float32le.hs b/source/library/Rattletrap/Decode/Float32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Float32le.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Float32le
+  ( decodeFloat32le
+  , decodeFloat32leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Float32le
+
+decodeFloat32le :: Decode Float32le
+decodeFloat32le = Float32le <$> getFloatle
+
+decodeFloat32leBits :: DecodeBits Float32le
+decodeFloat32leBits = toBits decodeFloat32le 4
diff --git a/source/library/Rattletrap/Decode/FloatAttribute.hs b/source/library/Rattletrap/Decode/FloatAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/FloatAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.FloatAttribute
+  ( decodeFloatAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Float32le
+import Rattletrap.Type.FloatAttribute
+
+decodeFloatAttributeBits :: DecodeBits FloatAttribute
+decodeFloatAttributeBits = FloatAttribute <$> decodeFloat32leBits
diff --git a/source/library/Rattletrap/Decode/Frame.hs b/source/library/Rattletrap/Decode/Frame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Frame.hs
@@ -0,0 +1,43 @@
+module Rattletrap.Decode.Frame
+  ( decodeFramesBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Float32le
+import Rattletrap.Decode.Replication
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Frame
+import Rattletrap.Type.Word32le
+
+import qualified Control.Monad.Trans.Class as Trans
+import qualified Control.Monad.Trans.State as State
+import qualified Data.Map as Map
+
+decodeFramesBits
+  :: (Int, Int, Int)
+  -> Int
+  -> Word
+  -> ClassAttributeMap
+  -> State.StateT
+       (Map.Map CompressedWord Word32le)
+       DecodeBits
+       [Frame]
+decodeFramesBits version count limit classes = if count <= 0
+  then pure []
+  else
+    (:)
+    <$> decodeFrameBits version limit classes
+    <*> decodeFramesBits version (count - 1) limit classes
+
+decodeFrameBits
+  :: (Int, Int, Int)
+  -> Word
+  -> ClassAttributeMap
+  -> State.StateT (Map.Map CompressedWord Word32le) DecodeBits Frame
+decodeFrameBits version limit classes =
+  Frame
+    <$> Trans.lift decodeFloat32leBits
+    <*> Trans.lift decodeFloat32leBits
+    <*> decodeReplicationsBits version limit classes
diff --git a/source/library/Rattletrap/Decode/GameModeAttribute.hs b/source/library/Rattletrap/Decode/GameModeAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/GameModeAttribute.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Decode.GameModeAttribute
+  ( decodeGameModeAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.GameModeAttribute
+
+decodeGameModeAttributeBits :: (Int, Int, Int) -> DecodeBits GameModeAttribute
+decodeGameModeAttributeBits version =
+  GameModeAttribute <$> pure (numBits version) <*> getWord8Bits
+    (numBits version)
+
+numBits :: (Int, Int, Int) -> Int
+numBits version = if version >= (868, 12, 0) then 8 else 2
diff --git a/source/library/Rattletrap/Decode/Header.hs b/source/library/Rattletrap/Decode/Header.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Header.hs
@@ -0,0 +1,22 @@
+module Rattletrap.Decode.Header
+  ( decodeHeader
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Dictionary
+import Rattletrap.Decode.Property
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.Header
+import Rattletrap.Type.Word32le
+
+decodeHeader :: Decode Header
+decodeHeader = do
+  (major, minor) <- (,) <$> decodeWord32le <*> decodeWord32le
+  Header major minor
+    <$> decodeWhen
+          (major >= Word32le 868 && minor >= Word32le 18)
+          decodeWord32le
+    <*> decodeStr
+    <*> decodeDictionary decodeProperty
diff --git a/source/library/Rattletrap/Decode/Initialization.hs b/source/library/Rattletrap/Decode/Initialization.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Initialization.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.Initialization
+  ( decodeInitializationBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int8Vector
+import Rattletrap.Decode.Vector
+import Rattletrap.Type.Initialization
+
+decodeInitializationBits
+  :: (Int, Int, Int) -> Bool -> Bool -> DecodeBits Initialization
+decodeInitializationBits version hasLocation hasRotation =
+  Initialization
+    <$> decodeWhen hasLocation (decodeVectorBits version)
+    <*> decodeWhen hasRotation decodeInt8VectorBits
diff --git a/source/library/Rattletrap/Decode/Int32le.hs b/source/library/Rattletrap/Decode/Int32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Int32le.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Int32le
+  ( decodeInt32le
+  , decodeInt32leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Int32le
+
+decodeInt32le :: Decode Int32le
+decodeInt32le = Int32le <$> getInt32le
+
+decodeInt32leBits :: DecodeBits Int32le
+decodeInt32leBits = toBits decodeInt32le 4
diff --git a/source/library/Rattletrap/Decode/Int64Attribute.hs b/source/library/Rattletrap/Decode/Int64Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Int64Attribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.Int64Attribute
+  ( decodeInt64AttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int64le
+import Rattletrap.Type.Int64Attribute
+
+decodeInt64AttributeBits :: DecodeBits Int64Attribute
+decodeInt64AttributeBits = Int64Attribute <$> decodeInt64leBits
diff --git a/source/library/Rattletrap/Decode/Int64le.hs b/source/library/Rattletrap/Decode/Int64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Int64le.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Decode.Int64le
+  ( decodeInt64leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Int64le
+
+decodeInt64le :: Decode Int64le
+decodeInt64le = Int64le <$> getInt64le
+
+decodeInt64leBits :: DecodeBits Int64le
+decodeInt64leBits = toBits decodeInt64le 8
diff --git a/source/library/Rattletrap/Decode/Int8Vector.hs b/source/library/Rattletrap/Decode/Int8Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Int8Vector.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Decode.Int8Vector
+  ( decodeInt8VectorBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int8le
+import Rattletrap.Type.Int8Vector
+import Rattletrap.Type.Int8le
+
+decodeInt8VectorBits :: DecodeBits Int8Vector
+decodeInt8VectorBits =
+  Int8Vector <$> decodeFieldBits <*> decodeFieldBits <*> decodeFieldBits
+
+decodeFieldBits :: DecodeBits (Maybe Int8le)
+decodeFieldBits = do
+  hasField <- getBool
+  decodeWhen hasField decodeInt8leBits
diff --git a/source/library/Rattletrap/Decode/Int8le.hs b/source/library/Rattletrap/Decode/Int8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Int8le.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Decode.Int8le
+  ( decodeInt8leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Int8le
+
+decodeInt8le :: Decode Int8le
+decodeInt8le = Int8le <$> getInt8
+
+decodeInt8leBits :: DecodeBits Int8le
+decodeInt8leBits = toBits decodeInt8le 1
diff --git a/source/library/Rattletrap/Decode/IntAttribute.hs b/source/library/Rattletrap/Decode/IntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/IntAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.IntAttribute
+  ( decodeIntAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Type.IntAttribute
+
+decodeIntAttributeBits :: DecodeBits IntAttribute
+decodeIntAttributeBits = IntAttribute <$> decodeInt32leBits
diff --git a/source/library/Rattletrap/Decode/KeyFrame.hs b/source/library/Rattletrap/Decode/KeyFrame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/KeyFrame.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Decode.KeyFrame
+  ( decodeKeyFrame
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Float32le
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.KeyFrame
+
+decodeKeyFrame :: Decode KeyFrame
+decodeKeyFrame =
+  KeyFrame <$> decodeFloat32le <*> decodeWord32le <*> decodeWord32le
diff --git a/source/library/Rattletrap/Decode/List.hs b/source/library/Rattletrap/Decode/List.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/List.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.List
+  ( decodeList
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.List
+import Rattletrap.Type.Word32le
+
+import qualified Control.Monad as Monad
+
+decodeList :: Decode a -> Decode (List a)
+decodeList decodeElement = do
+  size <- decodeWord32le
+  List <$> Monad.replicateM (fromIntegral (word32leValue size)) decodeElement
diff --git a/source/library/Rattletrap/Decode/LoadoutAttribute.hs b/source/library/Rattletrap/Decode/LoadoutAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/LoadoutAttribute.hs
@@ -0,0 +1,31 @@
+module Rattletrap.Decode.LoadoutAttribute
+  ( decodeLoadoutAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.LoadoutAttribute
+import Rattletrap.Type.Word8le
+
+decodeLoadoutAttributeBits :: DecodeBits LoadoutAttribute
+decodeLoadoutAttributeBits = do
+  version <- decodeWord8leBits
+  LoadoutAttribute version
+    <$> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 11) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 16) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 17) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 19) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
+    <*> decodeWhen (version >= Word8le 22) decodeWord32leBits
diff --git a/source/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs b/source/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/LoadoutOnlineAttribute.hs
@@ -0,0 +1,25 @@
+module Rattletrap.Decode.LoadoutOnlineAttribute
+  ( decodeLoadoutOnlineAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.ProductAttribute
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.LoadoutOnlineAttribute
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+import Rattletrap.Type.Word8le
+
+import qualified Control.Monad as Monad
+import qualified Data.Map as Map
+
+decodeLoadoutOnlineAttributeBits
+  :: (Int, Int, Int)
+  -> Map.Map Word32le Str
+  -> DecodeBits LoadoutOnlineAttribute
+decodeLoadoutOnlineAttributeBits version objectMap = do
+  size <- decodeWord8leBits
+  LoadoutOnlineAttribute <$> Monad.replicateM
+    (fromIntegral (word8leValue size))
+    (decodeProductAttributesBits version objectMap)
diff --git a/source/library/Rattletrap/Decode/LoadoutsAttribute.hs b/source/library/Rattletrap/Decode/LoadoutsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/LoadoutsAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.LoadoutsAttribute
+  ( decodeLoadoutsAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.LoadoutAttribute
+import Rattletrap.Type.LoadoutsAttribute
+
+decodeLoadoutsAttributeBits :: DecodeBits LoadoutsAttribute
+decodeLoadoutsAttributeBits =
+  LoadoutsAttribute
+    <$> decodeLoadoutAttributeBits
+    <*> decodeLoadoutAttributeBits
diff --git a/source/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs b/source/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/LoadoutsOnlineAttribute.hs
@@ -0,0 +1,23 @@
+module Rattletrap.Decode.LoadoutsOnlineAttribute
+  ( decodeLoadoutsOnlineAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.LoadoutOnlineAttribute
+import Rattletrap.Type.LoadoutsOnlineAttribute
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+import qualified Data.Map as Map
+
+decodeLoadoutsOnlineAttributeBits
+  :: (Int, Int, Int)
+  -> Map.Map Word32le Str
+  -> DecodeBits LoadoutsOnlineAttribute
+decodeLoadoutsOnlineAttributeBits version objectMap =
+  LoadoutsOnlineAttribute
+    <$> decodeLoadoutOnlineAttributeBits version objectMap
+    <*> decodeLoadoutOnlineAttributeBits version objectMap
+    <*> getBool
+    <*> getBool
diff --git a/source/library/Rattletrap/Decode/LocationAttribute.hs b/source/library/Rattletrap/Decode/LocationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/LocationAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.LocationAttribute
+  ( decodeLocationAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Vector
+import Rattletrap.Type.LocationAttribute
+
+decodeLocationAttributeBits :: (Int, Int, Int) -> DecodeBits LocationAttribute
+decodeLocationAttributeBits version =
+  LocationAttribute <$> decodeVectorBits version
diff --git a/source/library/Rattletrap/Decode/Mark.hs b/source/library/Rattletrap/Decode/Mark.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Mark.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.Mark
+  ( decodeMark
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.Mark
+
+decodeMark :: Decode Mark
+decodeMark = Mark <$> decodeStr <*> decodeWord32le
diff --git a/source/library/Rattletrap/Decode/Message.hs b/source/library/Rattletrap/Decode/Message.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Message.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.Message
+  ( decodeMessage
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.Message
+
+decodeMessage :: Decode Message
+decodeMessage = Message <$> decodeWord32le <*> decodeStr <*> decodeStr
diff --git a/source/library/Rattletrap/Decode/MusicStingerAttribute.hs b/source/library/Rattletrap/Decode/MusicStingerAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/MusicStingerAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.MusicStingerAttribute
+  ( decodeMusicStingerAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.MusicStingerAttribute
+
+decodeMusicStingerAttributeBits :: DecodeBits MusicStingerAttribute
+decodeMusicStingerAttributeBits =
+  MusicStingerAttribute
+    <$> getBool
+    <*> decodeWord32leBits
+    <*> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/PartyLeaderAttribute.hs b/source/library/Rattletrap/Decode/PartyLeaderAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PartyLeaderAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Decode.PartyLeaderAttribute
+  ( decodePartyLeaderAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.RemoteId
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.PartyLeaderAttribute
+import Rattletrap.Type.Word8le
+
+decodePartyLeaderAttributeBits
+  :: (Int, Int, Int) -> DecodeBits PartyLeaderAttribute
+decodePartyLeaderAttributeBits version = do
+  systemId <- decodeWord8leBits
+  PartyLeaderAttribute systemId <$> decodeWhen
+    (systemId /= Word8le 0)
+    ((,) <$> decodeRemoteIdBits version systemId <*> decodeWord8leBits)
diff --git a/source/library/Rattletrap/Decode/PickupAttribute.hs b/source/library/Rattletrap/Decode/PickupAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PickupAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Decode.PickupAttribute
+  ( decodePickupAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.PickupAttribute
+
+decodePickupAttributeBits :: DecodeBits PickupAttribute
+decodePickupAttributeBits = do
+  instigator <- getBool
+  PickupAttribute <$> decodeWhen instigator decodeWord32leBits <*> getBool
diff --git a/source/library/Rattletrap/Decode/PickupAttributeNew.hs b/source/library/Rattletrap/Decode/PickupAttributeNew.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PickupAttributeNew.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.PickupAttributeNew
+  ( decodePickupAttributeNewBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word8le
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.PickupAttributeNew
+
+decodePickupAttributeNewBits :: DecodeBits PickupAttributeNew
+decodePickupAttributeNewBits = do
+  instigator <- getBool
+  PickupAttributeNew <$> decodeWhen instigator decodeWord32leBits <*> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs b/source/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.PlayerHistoryKeyAttribute
+  ( decodePlayerHistoryKeyAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.PlayerHistoryKeyAttribute
+
+decodePlayerHistoryKeyAttributeBits :: DecodeBits PlayerHistoryKeyAttribute
+decodePlayerHistoryKeyAttributeBits =
+  PlayerHistoryKeyAttribute <$> getBitsLE 14
diff --git a/source/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs b/source/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PrivateMatchSettingsAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Decode.PrivateMatchSettingsAttribute
+  ( decodePrivateMatchSettingsAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.PrivateMatchSettingsAttribute
+
+decodePrivateMatchSettingsAttributeBits
+  :: DecodeBits PrivateMatchSettingsAttribute
+decodePrivateMatchSettingsAttributeBits =
+  PrivateMatchSettingsAttribute
+    <$> decodeStrBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeStrBits
+    <*> decodeStrBits
+    <*> getBool
diff --git a/source/library/Rattletrap/Decode/ProductAttribute.hs b/source/library/Rattletrap/Decode/ProductAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ProductAttribute.hs
@@ -0,0 +1,72 @@
+module Rattletrap.Decode.ProductAttribute
+  ( decodeProductAttributesBits
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Decode.Word32le
+import Rattletrap.Decode.Word8le
+import Rattletrap.Decode.Str
+import Rattletrap.Type.Common
+import Rattletrap.Type.ProductAttribute
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+import Rattletrap.Type.Word8le
+
+import qualified Control.Monad as Monad
+import qualified Data.Map as Map
+
+decodeProductAttributesBits
+  :: (Int, Int, Int) -> Map Word32le Str -> DecodeBits [ProductAttribute]
+decodeProductAttributesBits version objectMap = do
+  size <- decodeWord8leBits
+  Monad.replicateM
+    (fromIntegral (word8leValue size))
+    (decodeProductAttributeBits version objectMap)
+
+decodeProductAttributeBits
+  :: (Int, Int, Int) -> Map Word32le Str -> DecodeBits ProductAttribute
+decodeProductAttributeBits version objectMap = do
+  flag <- getBool
+  objectId <- decodeWord32leBits
+  let maybeObjectName = Map.lookup objectId objectMap
+  value <- case fromStr <$> maybeObjectName of
+    Just "TAGame.ProductAttribute_Painted_TA" -> decodePainted version
+    Just "TAGame.ProductAttribute_SpecialEdition_TA" -> decodeSpecialEdition
+    Just "TAGame.ProductAttribute_TeamEdition_TA" -> decodeTeamEdition version
+    Just "TAGame.ProductAttribute_TitleID_TA" -> decodeTitle
+    Just "TAGame.ProductAttribute_UserColor_TA" -> decodeColor version
+    Just objectName ->
+      fail
+        ("unknown object name "
+        <> show objectName
+        <> " for ID "
+        <> show objectId
+        )
+    Nothing -> fail ("missing object name for ID " <> show objectId)
+  pure (ProductAttribute flag objectId maybeObjectName value)
+
+decodeSpecialEdition :: DecodeBits ProductAttributeValue
+decodeSpecialEdition = ProductAttributeValueSpecialEdition <$> getBitsLE 31
+
+decodePainted :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
+decodePainted version = if version >= (868, 18, 0)
+  then ProductAttributeValuePaintedNew <$> getBitsLE 31
+  else ProductAttributeValuePaintedOld <$> decodeCompressedWordBits 13
+
+decodeTeamEdition :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
+decodeTeamEdition version = if version >= (868, 18, 0)
+  then ProductAttributeValueTeamEditionNew <$> getBitsLE 31
+  else ProductAttributeValueTeamEditionOld <$> decodeCompressedWordBits 13
+
+decodeColor :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
+decodeColor version = if version >= (868, 23, 8)
+  then ProductAttributeValueUserColorNew <$> decodeWord32leBits
+  else do
+    hasValue <- getBool
+    ProductAttributeValueUserColorOld <$> decodeWhen hasValue (getBitsLE 31)
+
+decodeTitle :: DecodeBits ProductAttributeValue
+decodeTitle = ProductAttributeValueTitleId <$> decodeStrBits
diff --git a/source/library/Rattletrap/Decode/Property.hs b/source/library/Rattletrap/Decode/Property.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Property.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Decode.Property
+  ( decodeProperty
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.PropertyValue
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word64le
+import Rattletrap.Type.Property
+
+decodeProperty :: Decode Property
+decodeProperty = do
+  kind <- decodeStr
+  Property kind <$> decodeWord64le <*> decodePropertyValue decodeProperty kind
diff --git a/source/library/Rattletrap/Decode/PropertyValue.hs b/source/library/Rattletrap/Decode/PropertyValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/PropertyValue.hs
@@ -0,0 +1,32 @@
+module Rattletrap.Decode.PropertyValue
+  ( decodePropertyValue
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Dictionary
+import Rattletrap.Decode.Float32le
+import Rattletrap.Decode.Int32le
+import Rattletrap.Decode.List
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.Word64le
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.PropertyValue
+import Rattletrap.Type.Str
+
+decodePropertyValue :: Decode a -> Str -> Decode (PropertyValue a)
+decodePropertyValue getProperty kind = case fromStr kind of
+  "ArrayProperty" ->
+    PropertyValueArray <$> decodeList (decodeDictionary getProperty)
+  "BoolProperty" -> PropertyValueBool <$> decodeWord8le
+  "ByteProperty" -> do
+    k <- decodeStr
+    PropertyValueByte k
+      <$> decodeWhen (fromStr k /= "OnlinePlatform_Steam") decodeStr
+  "FloatProperty" -> PropertyValueFloat <$> decodeFloat32le
+  "IntProperty" -> PropertyValueInt <$> decodeInt32le
+  "NameProperty" -> PropertyValueName <$> decodeStr
+  "QWordProperty" -> PropertyValueQWord <$> decodeWord64le
+  "StrProperty" -> PropertyValueStr <$> decodeStr
+  _ -> fail ("don't know how to read property value " <> show kind)
diff --git a/source/library/Rattletrap/Decode/QWordAttribute.hs b/source/library/Rattletrap/Decode/QWordAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/QWordAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.QWordAttribute
+  ( decodeQWordAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word64le
+import Rattletrap.Type.QWordAttribute
+
+decodeQWordAttributeBits :: DecodeBits QWordAttribute
+decodeQWordAttributeBits = QWordAttribute <$> decodeWord64leBits
diff --git a/source/library/Rattletrap/Decode/Quaternion.hs b/source/library/Rattletrap/Decode/Quaternion.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Quaternion.hs
@@ -0,0 +1,26 @@
+module Rattletrap.Decode.Quaternion
+  ( decodeQuaternionBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Quaternion
+
+decodeQuaternionBits :: DecodeBits Quaternion
+decodeQuaternionBits =
+  toQuaternion <$> decodeComponent <*> decodePart <*> decodePart <*> decodePart
+
+decodeComponent :: DecodeBits Component
+decodeComponent = do
+  x <- decodeCompressedWordBits 3
+  case compressedWordValue x of
+    0 -> pure ComponentX
+    1 -> pure ComponentY
+    2 -> pure ComponentZ
+    3 -> pure ComponentW
+    y -> fail ("invalid component: " <> show y)
+
+decodePart :: DecodeBits Double
+decodePart = decompressPart <$> decodeCompressedWordBits maxCompressedValue
diff --git a/source/library/Rattletrap/Decode/RemoteId.hs b/source/library/Rattletrap/Decode/RemoteId.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/RemoteId.hs
@@ -0,0 +1,48 @@
+module Rattletrap.Decode.RemoteId
+  ( decodeRemoteIdBits
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word64le
+import Rattletrap.Type.RemoteId
+import Rattletrap.Type.Word64le
+import Rattletrap.Type.Word8le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.ByteString as Bytes
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.Word as Word
+
+decodeRemoteIdBits :: (Int, Int, Int) -> Word8le -> DecodeBits RemoteId
+decodeRemoteIdBits version systemId = case word8leValue systemId of
+  0 -> RemoteIdSplitscreen <$> getBitsLE 24
+  1 -> RemoteIdSteam <$> decodeWord64leBits
+  2 -> RemoteIdPlayStation <$> decodePsName <*> decodePsBytes version
+  4 -> RemoteIdXbox <$> decodeWord64leBits
+  6 -> do
+    (a, b, c, d) <- getWord256
+    pure $ RemoteIdSwitch a b c d
+  7 -> if version >= (868, 24, 10)
+    then RemoteIdPsyNet . Left <$> decodeWord64leBits
+    else RemoteIdPsyNet . Right <$> getWord256
+  _ -> fail ("unknown system id " <> show systemId)
+
+decodePsName :: DecodeBits Text.Text
+decodePsName = fmap
+  (Text.dropWhileEnd (== '\x00') . Text.decodeLatin1 . reverseBytes)
+  (getByteStringBits 16)
+
+decodePsBytes :: (Int, Int, Int) -> DecodeBits [Word.Word8]
+decodePsBytes version = Bytes.unpack
+  <$> getByteStringBits (if version >= (868, 20, 1) then 24 else 16)
+
+getWord256 :: DecodeBits (Word64le, Word64le, Word64le, Word64le)
+getWord256 = do
+  a <- decodeWord64leBits
+  b <- decodeWord64leBits
+  c <- decodeWord64leBits
+  d <- decodeWord64leBits
+  pure (a, b, c, d)
diff --git a/source/library/Rattletrap/Decode/Replay.hs b/source/library/Rattletrap/Decode/Replay.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Replay.hs
@@ -0,0 +1,66 @@
+module Rattletrap.Decode.Replay
+  ( decodeReplay
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Content
+import Rattletrap.Decode.Header
+import Rattletrap.Decode.Section
+import Rattletrap.Encode.Content
+import Rattletrap.Type.Content
+import Rattletrap.Type.Dictionary
+import Rattletrap.Type.Header
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Property
+import Rattletrap.Type.PropertyValue
+import Rattletrap.Type.Replay
+import Rattletrap.Type.Section
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+decodeReplay :: Bool -> Decode Replay
+decodeReplay fast = do
+  header <- decodeSection decodeHeader
+  content <- if fast
+    then pure $ toSection putContent defaultContent
+    else
+      let body = sectionBody header
+      in
+        decodeSection $ decodeContent
+          (getVersion body)
+          (getNumFrames body)
+          (getMaxChannels body)
+  pure $ Replay header content
+
+getVersion :: Header -> (Int, Int, Int)
+getVersion header =
+  ( fromIntegral (word32leValue (headerEngineVersion header))
+  , fromIntegral (word32leValue (headerLicenseeVersion header))
+  , getPatchVersion header
+  )
+
+getPatchVersion :: Header -> Int
+getPatchVersion header = case headerPatchVersion header of
+  Just version -> fromIntegral (word32leValue version)
+  Nothing ->
+    case dictionaryLookup (toStr "MatchType") (headerProperties header) of
+      -- This is an ugly, ugly hack to handle replays from season 2 of RLCS.
+      -- See `decodeSpawnedReplicationBits` and #85.
+      Just Property { propertyValue = PropertyValueName str }
+        | fromStr str == "Lan" -> -1
+      _ -> 0
+
+getNumFrames :: Header -> Int
+getNumFrames header =
+  case dictionaryLookup (toStr "NumFrames") (headerProperties header) of
+    Just (Property _ _ (PropertyValueInt numFrames)) ->
+      fromIntegral (int32leValue numFrames)
+    _ -> 0
+
+getMaxChannels :: Header -> Word
+getMaxChannels header =
+  case dictionaryLookup (toStr "MaxChannels") (headerProperties header) of
+    Just (Property _ _ (PropertyValueInt numFrames)) ->
+      fromIntegral (int32leValue numFrames)
+    _ -> 1023
diff --git a/source/library/Rattletrap/Decode/Replication.hs b/source/library/Rattletrap/Decode/Replication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Replication.hs
@@ -0,0 +1,45 @@
+module Rattletrap.Decode.Replication
+  ( decodeReplicationsBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Decode.ReplicationValue
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Replication
+import Rattletrap.Type.Word32le
+
+import qualified Control.Monad.Trans.Class as Trans
+import qualified Control.Monad.Trans.State as State
+import qualified Data.Map as Map
+
+decodeReplicationsBits
+  :: (Int, Int, Int)
+  -> Word
+  -> ClassAttributeMap
+  -> State.StateT
+       (Map.Map CompressedWord Word32le)
+       DecodeBits
+       [Replication]
+decodeReplicationsBits version limit classes = do
+  hasReplication <- Trans.lift getBool
+  if hasReplication
+    then
+      (:)
+      <$> decodeReplicationBits version limit classes
+      <*> decodeReplicationsBits version limit classes
+    else pure []
+
+decodeReplicationBits
+  :: (Int, Int, Int)
+  -> Word
+  -> ClassAttributeMap
+  -> State.StateT
+       (Map.Map CompressedWord Word32le)
+       DecodeBits
+       Replication
+decodeReplicationBits version limit classes = do
+  actor <- Trans.lift (decodeCompressedWordBits limit)
+  Replication actor <$> decodeReplicationValueBits version classes actor
diff --git a/source/library/Rattletrap/Decode/ReplicationValue.hs b/source/library/Rattletrap/Decode/ReplicationValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ReplicationValue.hs
@@ -0,0 +1,45 @@
+module Rattletrap.Decode.ReplicationValue
+  ( decodeReplicationValueBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.DestroyedReplication
+import Rattletrap.Decode.SpawnedReplication
+import Rattletrap.Decode.UpdatedReplication
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.ReplicationValue
+import Rattletrap.Type.Word32le
+
+import qualified Control.Monad.Trans.Class as Trans
+import qualified Control.Monad.Trans.State as State
+import qualified Data.Map as Map
+
+decodeReplicationValueBits
+  :: (Int, Int, Int)
+  -> ClassAttributeMap
+  -> CompressedWord
+  -> State.StateT
+       (Map.Map CompressedWord Word32le)
+       DecodeBits
+       ReplicationValue
+decodeReplicationValueBits version classAttributeMap actorId = do
+  actorMap <- State.get
+  isOpen <- Trans.lift getBool
+  if isOpen
+    then do
+      isNew <- Trans.lift getBool
+      if isNew
+        then
+          ReplicationValueSpawned
+            <$> decodeSpawnedReplicationBits version classAttributeMap actorId
+        else ReplicationValueUpdated <$> Trans.lift
+          (decodeUpdatedReplicationBits
+            version
+            classAttributeMap
+            actorMap
+            actorId
+          )
+    else ReplicationValueDestroyed
+      <$> Trans.lift decodeDestroyedReplicationBits
diff --git a/source/library/Rattletrap/Decode/ReservationAttribute.hs b/source/library/Rattletrap/Decode/ReservationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/ReservationAttribute.hs
@@ -0,0 +1,25 @@
+module Rattletrap.Decode.ReservationAttribute
+  ( decodeReservationAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Decode.Str
+import Rattletrap.Decode.UniqueIdAttribute
+import Rattletrap.Type.ReservationAttribute
+import Rattletrap.Type.UniqueIdAttribute
+import Rattletrap.Type.Word8le
+
+decodeReservationAttributeBits
+  :: (Int, Int, Int) -> DecodeBits ReservationAttribute
+decodeReservationAttributeBits version = do
+  number <- decodeCompressedWordBits 7
+  uniqueId <- decodeUniqueIdAttributeBits version
+  ReservationAttribute number uniqueId
+    <$> decodeWhen
+          (uniqueIdAttributeSystemId uniqueId /= Word8le 0)
+          decodeStrBits
+    <*> getBool
+    <*> getBool
+    <*> decodeWhen (version >= (868, 12, 0)) (getWord8Bits 6)
diff --git a/source/library/Rattletrap/Decode/RigidBodyStateAttribute.hs b/source/library/Rattletrap/Decode/RigidBodyStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/RigidBodyStateAttribute.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Decode.RigidBodyStateAttribute
+  ( decodeRigidBodyStateAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Rotation
+import Rattletrap.Decode.Vector
+import Rattletrap.Type.RigidBodyStateAttribute
+
+decodeRigidBodyStateAttributeBits
+  :: (Int, Int, Int) -> DecodeBits RigidBodyStateAttribute
+decodeRigidBodyStateAttributeBits version = do
+  sleeping <- getBool
+  RigidBodyStateAttribute sleeping
+    <$> decodeVectorBits version
+    <*> decodeRotationBits version
+    <*> decodeWhen (not sleeping) (decodeVectorBits version)
+    <*> decodeWhen (not sleeping) (decodeVectorBits version)
diff --git a/source/library/Rattletrap/Decode/Rotation.hs b/source/library/Rattletrap/Decode/Rotation.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Rotation.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Rotation
+  ( decodeRotationBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWordVector
+import Rattletrap.Decode.Quaternion
+import Rattletrap.Type.Rotation
+
+decodeRotationBits :: (Int, Int, Int) -> DecodeBits Rotation
+decodeRotationBits version = if version >= (868, 22, 7)
+  then RotationQuaternion <$> decodeQuaternionBits
+  else RotationCompressedWordVector <$> decodeCompressedWordVectorBits
diff --git a/source/library/Rattletrap/Decode/Section.hs b/source/library/Rattletrap/Decode/Section.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Section.hs
@@ -0,0 +1,26 @@
+module Rattletrap.Decode.Section
+  ( decodeSection
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.Section
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Crc
+
+import qualified Control.Monad as Monad
+
+decodeSection :: Decode a -> Decode (Section a)
+decodeSection getBody = do
+  size <- decodeWord32le
+  crc <- decodeWord32le
+  rawBody <- getByteString (fromIntegral (word32leValue size))
+  let actualCrc = Word32le (getCrc32 rawBody)
+  Monad.when (actualCrc /= crc) (fail (crcMessage actualCrc crc))
+  body <- either fail pure (runDecode getBody rawBody)
+  pure (Section size crc body)
+
+crcMessage :: Word32le -> Word32le -> String
+crcMessage actual expected = unwords
+  ["actual CRC", show actual, "does not match expected CRC", show expected]
diff --git a/source/library/Rattletrap/Decode/SpawnedReplication.hs b/source/library/Rattletrap/Decode/SpawnedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/SpawnedReplication.hs
@@ -0,0 +1,70 @@
+module Rattletrap.Decode.SpawnedReplication
+  ( decodeSpawnedReplicationBits
+  )
+where
+
+import Data.Semigroup ((<>))
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Initialization
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.SpawnedReplication
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+import qualified Control.Monad.Trans.Class as Trans
+import qualified Control.Monad.Trans.State as State
+import qualified Data.Map as Map
+
+decodeSpawnedReplicationBits
+  :: (Int, Int, Int)
+  -> ClassAttributeMap
+  -> CompressedWord
+  -> State.StateT
+       (Map.Map CompressedWord Word32le)
+       DecodeBits
+       SpawnedReplication
+decodeSpawnedReplicationBits version classAttributeMap actorId = do
+  flag <- Trans.lift getBool
+  nameIndex <- decodeWhen
+    (version >= (868, 14, 0))
+    (Trans.lift decodeWord32leBits)
+  name <- either fail pure (lookupName classAttributeMap nameIndex)
+  objectId <- Trans.lift decodeWord32leBits
+  State.modify (Map.insert actorId objectId)
+  objectName <- either fail pure (lookupObjectName classAttributeMap objectId)
+  className <- either fail pure (lookupClassName objectName)
+  let hasLocation = classHasLocation className
+  let hasRotation = classHasRotation className
+  initialization <- Trans.lift
+    (decodeInitializationBits version hasLocation hasRotation)
+  pure
+    (SpawnedReplication
+      flag
+      nameIndex
+      name
+      objectId
+      objectName
+      className
+      initialization
+    )
+
+lookupName :: ClassAttributeMap -> Maybe Word32le -> Either String (Maybe Str)
+lookupName classAttributeMap maybeNameIndex = case maybeNameIndex of
+  Nothing -> Right Nothing
+  Just nameIndex ->
+    case getName (classAttributeMapNameMap classAttributeMap) nameIndex of
+      Nothing -> Left ("could not get name for index " <> show nameIndex)
+      Just name -> Right (Just name)
+
+lookupObjectName :: ClassAttributeMap -> Word32le -> Either String Str
+lookupObjectName classAttributeMap objectId =
+  case getObjectName (classAttributeMapObjectMap classAttributeMap) objectId of
+    Nothing -> Left ("could not get object name for id " <> show objectId)
+    Just objectName -> Right objectName
+
+lookupClassName :: Str -> Either String Str
+lookupClassName objectName = case getClassName objectName of
+  Nothing -> Left ("could not get class name for object " <> show objectName)
+  Just className -> Right className
diff --git a/source/library/Rattletrap/Decode/StatEventAttribute.hs b/source/library/Rattletrap/Decode/StatEventAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/StatEventAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Decode.StatEventAttribute
+  ( decodeStatEventAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Type.StatEventAttribute
+
+decodeStatEventAttributeBits :: DecodeBits StatEventAttribute
+decodeStatEventAttributeBits =
+  StatEventAttribute <$> getBool <*> decodeInt32leBits
diff --git a/source/library/Rattletrap/Decode/Str.hs b/source/library/Rattletrap/Decode/Str.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Str.hs
@@ -0,0 +1,42 @@
+module Rattletrap.Decode.Str
+  ( decodeStr
+  , decodeStrBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Int32le
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Str
+import Rattletrap.Utility.Bytes
+
+import qualified Data.ByteString as Bytes
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+
+decodeStr :: Decode Str
+decodeStr = do
+  rawSize <- decodeInt32le
+  bytes <- getByteString (normalizeTextSize rawSize)
+  pure (Str (dropNull (getTextDecoder rawSize bytes)))
+
+decodeStrBits :: DecodeBits Str
+decodeStrBits = do
+  rawSize <- decodeInt32leBits
+  bytes <- getByteStringBits (normalizeTextSize rawSize)
+  pure (Str (dropNull (getTextDecoder rawSize (reverseBytes bytes))))
+
+normalizeTextSize :: Integral a => Int32le -> a
+normalizeTextSize size = case int32leValue size of
+  0x05000000 -> 8
+  x -> if x < 0 then (-2 * fromIntegral x) else fromIntegral x
+
+getTextDecoder :: Int32le -> Bytes.ByteString -> Text.Text
+getTextDecoder size bytes =
+  let
+    decode =
+      if size < Int32le 0 then Text.decodeUtf16LE else Text.decodeLatin1
+  in decode bytes
+
+dropNull :: Text.Text -> Text.Text
+dropNull = Text.dropWhileEnd (== '\x00')
diff --git a/source/library/Rattletrap/Decode/StringAttribute.hs b/source/library/Rattletrap/Decode/StringAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/StringAttribute.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Decode.StringAttribute
+  ( decodeStringAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Str
+import Rattletrap.Type.StringAttribute
+
+decodeStringAttributeBits :: DecodeBits StringAttribute
+decodeStringAttributeBits = StringAttribute <$> decodeStrBits
diff --git a/source/library/Rattletrap/Decode/TeamPaintAttribute.hs b/source/library/Rattletrap/Decode/TeamPaintAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/TeamPaintAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Decode.TeamPaintAttribute
+  ( decodeTeamPaintAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.TeamPaintAttribute
+
+decodeTeamPaintAttributeBits :: DecodeBits TeamPaintAttribute
+decodeTeamPaintAttributeBits =
+  TeamPaintAttribute
+    <$> decodeWord8leBits
+    <*> decodeWord8leBits
+    <*> decodeWord8leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
diff --git a/source/library/Rattletrap/Decode/TitleAttribute.hs b/source/library/Rattletrap/Decode/TitleAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/TitleAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Decode.TitleAttribute
+  ( decodeTitleAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Word32le
+import Rattletrap.Type.TitleAttribute
+
+decodeTitleAttributeBits :: DecodeBits TitleAttribute
+decodeTitleAttributeBits =
+  TitleAttribute
+    <$> getBool
+    <*> getBool
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> decodeWord32leBits
+    <*> getBool
diff --git a/source/library/Rattletrap/Decode/UniqueIdAttribute.hs b/source/library/Rattletrap/Decode/UniqueIdAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/UniqueIdAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Decode.UniqueIdAttribute
+  ( decodeUniqueIdAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.RemoteId
+import Rattletrap.Decode.Word8le
+import Rattletrap.Type.UniqueIdAttribute
+
+decodeUniqueIdAttributeBits :: (Int, Int, Int) -> DecodeBits UniqueIdAttribute
+decodeUniqueIdAttributeBits version = do
+  systemId <- decodeWord8leBits
+  UniqueIdAttribute systemId
+    <$> decodeRemoteIdBits version systemId
+    <*> decodeWord8leBits
diff --git a/source/library/Rattletrap/Decode/UpdatedReplication.hs b/source/library/Rattletrap/Decode/UpdatedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/UpdatedReplication.hs
@@ -0,0 +1,22 @@
+module Rattletrap.Decode.UpdatedReplication
+  ( decodeUpdatedReplicationBits
+  )
+where
+
+import Rattletrap.Decode.Attribute
+import Rattletrap.Decode.Common
+import Rattletrap.Type.ClassAttributeMap
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.UpdatedReplication
+import Rattletrap.Type.Word32le
+
+import qualified Data.Map as Map
+
+decodeUpdatedReplicationBits
+  :: (Int, Int, Int)
+  -> ClassAttributeMap
+  -> Map.Map CompressedWord Word32le
+  -> CompressedWord
+  -> DecodeBits UpdatedReplication
+decodeUpdatedReplicationBits version classes actors actor =
+  UpdatedReplication <$> decodeAttributesBits version classes actors actor
diff --git a/source/library/Rattletrap/Decode/Vector.hs b/source/library/Rattletrap/Decode/Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Vector.hs
@@ -0,0 +1,29 @@
+module Rattletrap.Decode.Vector
+  ( decodeVectorBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.CompressedWord
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Vector
+
+decodeVectorBits :: (Int, Int, Int) -> DecodeBits Vector
+decodeVectorBits version = do
+  size <- decodeCompressedWordBits (if version >= (868, 22, 7) then 21 else 19)
+  let
+    limit = getLimit size
+    bias = getBias size
+  Vector size bias
+    <$> fmap (fromDelta bias) (decodeCompressedWordBits limit)
+    <*> fmap (fromDelta bias) (decodeCompressedWordBits limit)
+    <*> fmap (fromDelta bias) (decodeCompressedWordBits limit)
+
+getLimit :: CompressedWord -> Word
+getLimit = (2 ^) . (+ 2) . compressedWordValue
+
+getBias :: CompressedWord -> Word
+getBias = (2 ^) . (+ 1) . compressedWordValue
+
+fromDelta :: Word -> CompressedWord -> Int
+fromDelta bias x = fromIntegral (compressedWordValue x) - fromIntegral bias
diff --git a/source/library/Rattletrap/Decode/WeldedInfoAttribute.hs b/source/library/Rattletrap/Decode/WeldedInfoAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/WeldedInfoAttribute.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Decode.WeldedInfoAttribute
+  ( decodeWeldedInfoAttributeBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Decode.Float32le
+import Rattletrap.Decode.Int32le
+import Rattletrap.Decode.Int8Vector
+import Rattletrap.Decode.Vector
+import Rattletrap.Type.WeldedInfoAttribute
+
+decodeWeldedInfoAttributeBits
+  :: (Int, Int, Int) -> DecodeBits WeldedInfoAttribute
+decodeWeldedInfoAttributeBits version =
+  WeldedInfoAttribute
+    <$> getBool
+    <*> decodeInt32leBits
+    <*> decodeVectorBits version
+    <*> decodeFloat32leBits
+    <*> decodeInt8VectorBits
diff --git a/source/library/Rattletrap/Decode/Word32le.hs b/source/library/Rattletrap/Decode/Word32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Word32le.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Word32le
+  ( decodeWord32le
+  , decodeWord32leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Word32le
+
+decodeWord32le :: Decode Word32le
+decodeWord32le = Word32le <$> getWord32le
+
+decodeWord32leBits :: DecodeBits Word32le
+decodeWord32leBits = toBits decodeWord32le 4
diff --git a/source/library/Rattletrap/Decode/Word64le.hs b/source/library/Rattletrap/Decode/Word64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Word64le.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Word64le
+  ( decodeWord64le
+  , decodeWord64leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Word64le
+
+decodeWord64le :: Decode Word64le
+decodeWord64le = Word64le <$> getWord64le
+
+decodeWord64leBits :: DecodeBits Word64le
+decodeWord64leBits = toBits decodeWord64le 8
diff --git a/source/library/Rattletrap/Decode/Word8le.hs b/source/library/Rattletrap/Decode/Word8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Decode/Word8le.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Decode.Word8le
+  ( decodeWord8le
+  , decodeWord8leBits
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Type.Word8le
+
+decodeWord8le :: Decode Word8le
+decodeWord8le = Word8le <$> getWord8
+
+decodeWord8leBits :: DecodeBits Word8le
+decodeWord8leBits = toBits decodeWord8le 1
diff --git a/source/library/Rattletrap/Encode/AppliedDamageAttribute.hs b/source/library/Rattletrap/Encode/AppliedDamageAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/AppliedDamageAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Encode.AppliedDamageAttribute
+  ( putAppliedDamageAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Encode.Vector
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.AppliedDamageAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putAppliedDamageAttribute :: AppliedDamageAttribute -> BinaryBits.BitPut ()
+putAppliedDamageAttribute appliedDamageAttribute = do
+  putWord8Bits (appliedDamageAttributeUnknown1 appliedDamageAttribute)
+  putVector (appliedDamageAttributeLocation appliedDamageAttribute)
+  putInt32Bits (appliedDamageAttributeUnknown3 appliedDamageAttribute)
+  putInt32Bits (appliedDamageAttributeUnknown4 appliedDamageAttribute)
diff --git a/source/library/Rattletrap/Encode/Attribute.hs b/source/library/Rattletrap/Encode/Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Attribute.hs
@@ -0,0 +1,26 @@
+module Rattletrap.Encode.Attribute
+  ( putAttributes
+  )
+where
+
+import Rattletrap.Encode.AttributeValue
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Type.Attribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putAttributes :: [Attribute] -> BinaryBits.BitPut ()
+putAttributes attributes = case attributes of
+  [] -> BinaryBits.putBool False
+  [attribute] -> do
+    putAttribute attribute
+    BinaryBits.putBool False
+  first : rest -> do
+    putAttribute first
+    putAttributes rest
+
+putAttribute :: Attribute -> BinaryBits.BitPut ()
+putAttribute attribute = do
+  BinaryBits.putBool True
+  putCompressedWord (attributeId attribute)
+  putAttributeValue (attributeValue attribute)
diff --git a/source/library/Rattletrap/Encode/AttributeMapping.hs b/source/library/Rattletrap/Encode/AttributeMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/AttributeMapping.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.AttributeMapping
+  ( putAttributeMapping
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.AttributeMapping
+
+import qualified Data.Binary as Binary
+
+putAttributeMapping :: AttributeMapping -> Binary.Put
+putAttributeMapping attributeMapping = do
+  putWord32 (attributeMappingObjectId attributeMapping)
+  putWord32 (attributeMappingStreamId attributeMapping)
diff --git a/source/library/Rattletrap/Encode/AttributeValue.hs b/source/library/Rattletrap/Encode/AttributeValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/AttributeValue.hs
@@ -0,0 +1,83 @@
+module Rattletrap.Encode.AttributeValue
+  ( putAttributeValue
+  )
+where
+
+import Rattletrap.Encode.AppliedDamageAttribute
+import Rattletrap.Encode.BooleanAttribute
+import Rattletrap.Encode.ByteAttribute
+import Rattletrap.Encode.CamSettingsAttribute
+import Rattletrap.Encode.ClubColorsAttribute
+import Rattletrap.Encode.DamageStateAttribute
+import Rattletrap.Encode.DemolishAttribute
+import Rattletrap.Encode.EnumAttribute
+import Rattletrap.Encode.ExplosionAttribute
+import Rattletrap.Encode.ExtendedExplosionAttribute
+import Rattletrap.Encode.FlaggedIntAttribute
+import Rattletrap.Encode.FlaggedByteAttribute
+import Rattletrap.Encode.FloatAttribute
+import Rattletrap.Encode.GameModeAttribute
+import Rattletrap.Encode.Int64Attribute
+import Rattletrap.Encode.IntAttribute
+import Rattletrap.Encode.LoadoutAttribute
+import Rattletrap.Encode.LoadoutOnlineAttribute
+import Rattletrap.Encode.LoadoutsAttribute
+import Rattletrap.Encode.LoadoutsOnlineAttribute
+import Rattletrap.Encode.LocationAttribute
+import Rattletrap.Encode.MusicStingerAttribute
+import Rattletrap.Encode.PartyLeaderAttribute
+import Rattletrap.Encode.PickupAttribute
+import Rattletrap.Encode.PickupAttributeNew
+import Rattletrap.Encode.PlayerHistoryKeyAttribute
+import Rattletrap.Encode.PrivateMatchSettingsAttribute
+import Rattletrap.Encode.QWordAttribute
+import Rattletrap.Encode.ReservationAttribute
+import Rattletrap.Encode.RigidBodyStateAttribute
+import Rattletrap.Encode.StatEventAttribute
+import Rattletrap.Encode.StringAttribute
+import Rattletrap.Encode.TeamPaintAttribute
+import Rattletrap.Encode.TitleAttribute
+import Rattletrap.Encode.UniqueIdAttribute
+import Rattletrap.Encode.WeldedInfoAttribute
+import Rattletrap.Type.AttributeValue
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putAttributeValue :: AttributeValue -> BinaryBits.BitPut ()
+putAttributeValue value = case value of
+  AttributeValueAppliedDamage x -> putAppliedDamageAttribute x
+  AttributeValueBoolean x -> putBooleanAttribute x
+  AttributeValueByte x -> putByteAttribute x
+  AttributeValueCamSettings x -> putCamSettingsAttribute x
+  AttributeValueClubColors x -> putClubColorsAttribute x
+  AttributeValueDamageState x -> putDamageStateAttribute x
+  AttributeValueDemolish x -> putDemolishAttribute x
+  AttributeValueEnum x -> putEnumAttribute x
+  AttributeValueExplosion x -> putExplosionAttribute x
+  AttributeValueExtendedExplosion x -> putExtendedExplosionAttribute x
+  AttributeValueFlaggedInt x -> putFlaggedIntAttribute x
+  AttributeValueFlaggedByte x -> putFlaggedByteAttribute x
+  AttributeValueFloat x -> putFloatAttribute x
+  AttributeValueGameMode x -> putGameModeAttribute x
+  AttributeValueInt x -> putIntAttribute x
+  AttributeValueInt64 x -> putInt64Attribute x
+  AttributeValueLoadout x -> putLoadoutAttribute x
+  AttributeValueLoadoutOnline x -> putLoadoutOnlineAttribute x
+  AttributeValueLoadouts x -> putLoadoutsAttribute x
+  AttributeValueLoadoutsOnline x -> putLoadoutsOnlineAttribute x
+  AttributeValueLocation x -> putLocationAttribute x
+  AttributeValueMusicStinger x -> putMusicStingerAttribute x
+  AttributeValuePartyLeader x -> putPartyLeaderAttribute x
+  AttributeValuePickup x -> putPickupAttribute x
+  AttributeValuePickupNew x -> putPickupAttributeNew x
+  AttributeValuePlayerHistoryKey x -> putPlayerHistoryKeyAttribute x
+  AttributeValuePrivateMatchSettings x -> putPrivateMatchSettingsAttribute x
+  AttributeValueQWord x -> putQWordAttribute x
+  AttributeValueReservation x -> putReservationAttribute x
+  AttributeValueRigidBodyState x -> putRigidBodyStateAttribute x
+  AttributeValueStatEvent x -> putStatEventAttribute x
+  AttributeValueString x -> putStringAttribute x
+  AttributeValueTeamPaint x -> putTeamPaintAttribute x
+  AttributeValueTitle x -> putTitleAttribute x
+  AttributeValueUniqueId x -> putUniqueIdAttribute x
+  AttributeValueWeldedInfo x -> putWeldedInfoAttribute x
diff --git a/source/library/Rattletrap/Encode/BooleanAttribute.hs b/source/library/Rattletrap/Encode/BooleanAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/BooleanAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Encode.BooleanAttribute
+  ( putBooleanAttribute
+  )
+where
+
+import Rattletrap.Type.BooleanAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putBooleanAttribute :: BooleanAttribute -> BinaryBits.BitPut ()
+putBooleanAttribute booleanAttribute =
+  BinaryBits.putBool (booleanAttributeValue booleanAttribute)
diff --git a/source/library/Rattletrap/Encode/ByteAttribute.hs b/source/library/Rattletrap/Encode/ByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ByteAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.ByteAttribute
+  ( putByteAttribute
+  )
+where
+
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.ByteAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putByteAttribute :: ByteAttribute -> BinaryBits.BitPut ()
+putByteAttribute byteAttribute =
+  putWord8Bits (byteAttributeValue byteAttribute)
diff --git a/source/library/Rattletrap/Encode/Cache.hs b/source/library/Rattletrap/Encode/Cache.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Cache.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Encode.Cache
+  ( putCache
+  )
+where
+
+import Rattletrap.Encode.AttributeMapping
+import Rattletrap.Encode.List
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Cache
+
+import qualified Data.Binary as Binary
+
+putCache :: Cache -> Binary.Put
+putCache cache = do
+  putWord32 (cacheClassId cache)
+  putWord32 (cacheParentCacheId cache)
+  putWord32 (cacheCacheId cache)
+  putList putAttributeMapping (cacheAttributeMappings cache)
diff --git a/source/library/Rattletrap/Encode/CamSettingsAttribute.hs b/source/library/Rattletrap/Encode/CamSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/CamSettingsAttribute.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.CamSettingsAttribute
+  ( putCamSettingsAttribute
+  )
+where
+
+import Rattletrap.Encode.Float32le
+import Rattletrap.Type.CamSettingsAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putCamSettingsAttribute :: CamSettingsAttribute -> BinaryBits.BitPut ()
+putCamSettingsAttribute camSettingsAttribute = do
+  putFloat32Bits (camSettingsAttributeFov camSettingsAttribute)
+  putFloat32Bits (camSettingsAttributeHeight camSettingsAttribute)
+  putFloat32Bits (camSettingsAttributeAngle camSettingsAttribute)
+  putFloat32Bits (camSettingsAttributeDistance camSettingsAttribute)
+  putFloat32Bits (camSettingsAttributeStiffness camSettingsAttribute)
+  putFloat32Bits (camSettingsAttributeSwivelSpeed camSettingsAttribute)
+  case camSettingsAttributeTransitionSpeed camSettingsAttribute of
+    Nothing -> pure ()
+    Just transitionSpeed -> putFloat32Bits transitionSpeed
diff --git a/source/library/Rattletrap/Encode/ClassMapping.hs b/source/library/Rattletrap/Encode/ClassMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ClassMapping.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Encode.ClassMapping
+  ( putClassMapping
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.ClassMapping
+
+import qualified Data.Binary as Binary
+
+putClassMapping :: ClassMapping -> Binary.Put
+putClassMapping classMapping = do
+  putText (classMappingName classMapping)
+  putWord32 (classMappingStreamId classMapping)
diff --git a/source/library/Rattletrap/Encode/ClubColorsAttribute.hs b/source/library/Rattletrap/Encode/ClubColorsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ClubColorsAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.ClubColorsAttribute
+  ( putClubColorsAttribute
+  )
+where
+
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.ClubColorsAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putClubColorsAttribute :: ClubColorsAttribute -> BinaryBits.BitPut ()
+putClubColorsAttribute clubColorsAttribute = do
+  BinaryBits.putBool (clubColorsAttributeBlueFlag clubColorsAttribute)
+  putWord8Bits (clubColorsAttributeBlueColor clubColorsAttribute)
+  BinaryBits.putBool (clubColorsAttributeOrangeFlag clubColorsAttribute)
+  putWord8Bits (clubColorsAttributeOrangeColor clubColorsAttribute)
diff --git a/source/library/Rattletrap/Encode/Common.hs b/source/library/Rattletrap/Encode/Common.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Common.hs
@@ -0,0 +1,10 @@
+module Rattletrap.Encode.Common
+  ( putBitsLE
+  )
+where
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Bits as Bits
+
+putBitsLE :: Bits.Bits a => Int -> a -> BinaryBits.BitPut ()
+putBitsLE size x = mapM_ (BinaryBits.putBool . Bits.testBit x) [0 .. size - 1]
diff --git a/source/library/Rattletrap/Encode/CompressedWord.hs b/source/library/Rattletrap/Encode/CompressedWord.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/CompressedWord.hs
@@ -0,0 +1,44 @@
+module Rattletrap.Encode.CompressedWord
+  ( putCompressedWord
+  )
+where
+
+import Rattletrap.Type.CompressedWord
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Bits as Bits
+
+putCompressedWord :: CompressedWord -> BinaryBits.BitPut ()
+putCompressedWord compressedWord =
+  let
+    limit = compressedWordLimit compressedWord
+    value = compressedWordValue compressedWord
+    maxBits = getMaxBits limit
+  in putCompressedWordStep limit value maxBits 0 0
+
+putCompressedWordStep
+  :: Word -> Word -> Int -> Int -> Word -> BinaryBits.BitPut ()
+putCompressedWordStep limit value maxBits position soFar =
+  if position < maxBits
+    then do
+      let x = Bits.shiftL 1 position :: Word
+      if maxBits > 1 && position == maxBits - 1 && soFar + x > limit
+        then pure ()
+        else do
+          let bit = Bits.testBit value position
+          BinaryBits.putBool bit
+          let delta = if bit then x else 0
+          putCompressedWordStep
+            limit
+            value
+            maxBits
+            (position + 1)
+            (soFar + delta)
+    else pure ()
+
+getMaxBits :: Word -> Int
+getMaxBits x =
+  let
+    n :: Int
+    n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
+  in if x < 1024 && x == 2 ^ n then n + 1 else n
diff --git a/source/library/Rattletrap/Encode/CompressedWordVector.hs b/source/library/Rattletrap/Encode/CompressedWordVector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/CompressedWordVector.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Encode.CompressedWordVector
+  ( putCompressedWordVector
+  )
+where
+
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Type.CompressedWordVector
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putCompressedWordVector :: CompressedWordVector -> BinaryBits.BitPut ()
+putCompressedWordVector compressedWordVector = do
+  putCompressedWord (compressedWordVectorX compressedWordVector)
+  putCompressedWord (compressedWordVectorY compressedWordVector)
+  putCompressedWord (compressedWordVectorZ compressedWordVector)
diff --git a/source/library/Rattletrap/Encode/Content.hs b/source/library/Rattletrap/Encode/Content.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Content.hs
@@ -0,0 +1,43 @@
+module Rattletrap.Encode.Content
+  ( putContent
+  )
+where
+
+import Rattletrap.Encode.Cache
+import Rattletrap.Encode.ClassMapping
+import Rattletrap.Encode.Frame
+import Rattletrap.Encode.KeyFrame
+import Rattletrap.Encode.List
+import Rattletrap.Encode.Mark
+import Rattletrap.Encode.Message
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Content
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putContent :: Content -> Binary.Put
+putContent content = do
+  putList putText (contentLevels content)
+  putList putKeyFrame (contentKeyFrames content)
+  let streamSize = contentStreamSize content
+  putWord32 streamSize
+  let
+    stream = LazyBytes.toStrict
+      (Binary.runPut (BinaryBits.runBitPut (putFrames (contentFrames content)))
+      )
+  Binary.putByteString
+    (reverseBytes (padBytes (word32leValue 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)
+  mapM_ Binary.putWord8 (contentUnknown content)
diff --git a/source/library/Rattletrap/Encode/DamageStateAttribute.hs b/source/library/Rattletrap/Encode/DamageStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/DamageStateAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Encode.DamageStateAttribute
+  ( putDamageStateAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Encode.Vector
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.DamageStateAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putDamageStateAttribute :: DamageStateAttribute -> BinaryBits.BitPut ()
+putDamageStateAttribute damageStateAttribute = do
+  putWord8Bits (damageStateAttributeUnknown1 damageStateAttribute)
+  BinaryBits.putBool (damageStateAttributeUnknown2 damageStateAttribute)
+  putInt32Bits (damageStateAttributeUnknown3 damageStateAttribute)
+  putVector (damageStateAttributeUnknown4 damageStateAttribute)
+  BinaryBits.putBool (damageStateAttributeUnknown5 damageStateAttribute)
+  BinaryBits.putBool (damageStateAttributeUnknown6 damageStateAttribute)
diff --git a/source/library/Rattletrap/Encode/DemolishAttribute.hs b/source/library/Rattletrap/Encode/DemolishAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/DemolishAttribute.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.DemolishAttribute
+  ( putDemolishAttribute
+  )
+where
+
+import Rattletrap.Encode.Vector
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.DemolishAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putDemolishAttribute :: DemolishAttribute -> BinaryBits.BitPut ()
+putDemolishAttribute demolishAttribute = do
+  BinaryBits.putBool (demolishAttributeAttackerFlag demolishAttribute)
+  putWord32Bits (demolishAttributeAttackerActorId demolishAttribute)
+  BinaryBits.putBool (demolishAttributeVictimFlag demolishAttribute)
+  putWord32Bits (demolishAttributeVictimActorId demolishAttribute)
+  putVector (demolishAttributeAttackerVelocity demolishAttribute)
+  putVector (demolishAttributeVictimVelocity demolishAttribute)
diff --git a/source/library/Rattletrap/Encode/DestroyedReplication.hs b/source/library/Rattletrap/Encode/DestroyedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/DestroyedReplication.hs
@@ -0,0 +1,11 @@
+module Rattletrap.Encode.DestroyedReplication
+  ( putDestroyedReplication
+  )
+where
+
+import Rattletrap.Type.DestroyedReplication
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putDestroyedReplication :: DestroyedReplication -> BinaryBits.BitPut ()
+putDestroyedReplication _ = pure ()
diff --git a/source/library/Rattletrap/Encode/Dictionary.hs b/source/library/Rattletrap/Encode/Dictionary.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Dictionary.hs
@@ -0,0 +1,17 @@
+module Rattletrap.Encode.Dictionary
+  ( putDictionary
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Type.Dictionary
+
+import qualified Data.Binary as Binary
+
+putDictionary :: (a -> Binary.Put) -> Dictionary a -> Binary.Put
+putDictionary f x = case x of
+  DictionaryElement k v y -> do
+    putText k
+    f v
+    putDictionary f y
+  DictionaryEnd y -> putText y
diff --git a/source/library/Rattletrap/Encode/EnumAttribute.hs b/source/library/Rattletrap/Encode/EnumAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/EnumAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.EnumAttribute
+  ( putEnumAttribute
+  )
+where
+
+import Rattletrap.Encode.Common
+import Rattletrap.Type.EnumAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putEnumAttribute :: EnumAttribute -> BinaryBits.BitPut ()
+putEnumAttribute enumAttribute =
+  putBitsLE 11 (enumAttributeValue enumAttribute)
diff --git a/source/library/Rattletrap/Encode/ExplosionAttribute.hs b/source/library/Rattletrap/Encode/ExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ExplosionAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.ExplosionAttribute
+  ( putExplosionAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Encode.Vector
+import Rattletrap.Type.ExplosionAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putExplosionAttribute :: ExplosionAttribute -> BinaryBits.BitPut ()
+putExplosionAttribute explosionAttribute = do
+  BinaryBits.putBool False
+  putInt32Bits (explosionAttributeActorId explosionAttribute)
+  putVector (explosionAttributeLocation explosionAttribute)
diff --git a/source/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs b/source/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ExtendedExplosionAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.ExtendedExplosionAttribute
+  ( putExtendedExplosionAttribute
+  )
+where
+
+import Rattletrap.Encode.ExplosionAttribute
+import Rattletrap.Encode.FlaggedIntAttribute
+import Rattletrap.Type.ExtendedExplosionAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putExtendedExplosionAttribute
+  :: ExtendedExplosionAttribute -> BinaryBits.BitPut ()
+putExtendedExplosionAttribute x = do
+  putExplosionAttribute (extendedExplosionAttributeExplosion x)
+  putFlaggedIntAttribute (extendedExplosionAttributeUnknown x)
diff --git a/source/library/Rattletrap/Encode/FlaggedByteAttribute.hs b/source/library/Rattletrap/Encode/FlaggedByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/FlaggedByteAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.FlaggedByteAttribute
+  ( putFlaggedByteAttribute
+  )
+where
+
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.FlaggedByteAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putFlaggedByteAttribute :: FlaggedByteAttribute -> BinaryBits.BitPut ()
+putFlaggedByteAttribute flaggedByteAttribute = do
+  BinaryBits.putBool (flaggedByteAttributeFlag flaggedByteAttribute)
+  putWord8Bits (flaggedByteAttributeByte flaggedByteAttribute)
diff --git a/source/library/Rattletrap/Encode/FlaggedIntAttribute.hs b/source/library/Rattletrap/Encode/FlaggedIntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/FlaggedIntAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.FlaggedIntAttribute
+  ( putFlaggedIntAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Type.FlaggedIntAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putFlaggedIntAttribute :: FlaggedIntAttribute -> BinaryBits.BitPut ()
+putFlaggedIntAttribute flaggedIntAttribute = do
+  BinaryBits.putBool (flaggedIntAttributeFlag flaggedIntAttribute)
+  putInt32Bits (flaggedIntAttributeInt flaggedIntAttribute)
diff --git a/source/library/Rattletrap/Encode/Float32le.hs b/source/library/Rattletrap/Encode/Float32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Float32le.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Float32le
+  ( putFloat32
+  , putFloat32Bits
+  )
+where
+
+import Rattletrap.Type.Float32le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putFloat32 :: Float32le -> Binary.Put
+putFloat32 = Binary.putFloatle . float32leValue
+
+putFloat32Bits :: Float32le -> BinaryBits.BitPut ()
+putFloat32Bits float32 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putFloat32 float32))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/FloatAttribute.hs b/source/library/Rattletrap/Encode/FloatAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/FloatAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.FloatAttribute
+  ( putFloatAttribute
+  )
+where
+
+import Rattletrap.Encode.Float32le
+import Rattletrap.Type.FloatAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putFloatAttribute :: FloatAttribute -> BinaryBits.BitPut ()
+putFloatAttribute floatAttribute =
+  putFloat32Bits (floatAttributeValue floatAttribute)
diff --git a/source/library/Rattletrap/Encode/Frame.hs b/source/library/Rattletrap/Encode/Frame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Frame.hs
@@ -0,0 +1,24 @@
+module Rattletrap.Encode.Frame
+  ( putFrames
+  )
+where
+
+import Rattletrap.Encode.Float32le
+import Rattletrap.Encode.Replication
+import Rattletrap.Type.Frame
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putFrames :: [Frame] -> BinaryBits.BitPut ()
+putFrames frames = case frames of
+  [] -> pure ()
+  [frame] -> putFrame frame
+  first : rest -> do
+    putFrame first
+    putFrames rest
+
+putFrame :: Frame -> BinaryBits.BitPut ()
+putFrame frame = do
+  putFloat32Bits (frameTime frame)
+  putFloat32Bits (frameDelta frame)
+  putReplications (frameReplications frame)
diff --git a/source/library/Rattletrap/Encode/GameModeAttribute.hs b/source/library/Rattletrap/Encode/GameModeAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/GameModeAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.GameModeAttribute
+  ( putGameModeAttribute
+  )
+where
+
+import Rattletrap.Type.GameModeAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putGameModeAttribute :: GameModeAttribute -> BinaryBits.BitPut ()
+putGameModeAttribute gameModeAttribute = do
+  let numBits = gameModeAttributeNumBits gameModeAttribute
+  let word = gameModeAttributeWord gameModeAttribute
+  BinaryBits.putWord8 numBits word
diff --git a/source/library/Rattletrap/Encode/Header.hs b/source/library/Rattletrap/Encode/Header.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Header.hs
@@ -0,0 +1,22 @@
+module Rattletrap.Encode.Header
+  ( putHeader
+  )
+where
+
+import Rattletrap.Encode.Dictionary
+import Rattletrap.Encode.Property
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Header
+
+import qualified Data.Binary as Binary
+
+putHeader :: Header -> Binary.Put
+putHeader header = do
+  putWord32 (headerEngineVersion header)
+  putWord32 (headerLicenseeVersion header)
+  case headerPatchVersion header of
+    Nothing -> pure ()
+    Just patchVersion -> putWord32 patchVersion
+  putText (headerLabel header)
+  putDictionary putProperty (headerProperties header)
diff --git a/source/library/Rattletrap/Encode/Initialization.hs b/source/library/Rattletrap/Encode/Initialization.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Initialization.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.Initialization
+  ( putInitialization
+  )
+where
+
+import Rattletrap.Encode.Int8Vector
+import Rattletrap.Encode.Vector
+import Rattletrap.Type.Initialization
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putInitialization :: Initialization -> BinaryBits.BitPut ()
+putInitialization initialization = do
+  case initializationLocation initialization of
+    Nothing -> pure ()
+    Just location -> putVector location
+  case initializationRotation initialization of
+    Nothing -> pure ()
+    Just rotation -> putInt8Vector rotation
diff --git a/source/library/Rattletrap/Encode/Int32le.hs b/source/library/Rattletrap/Encode/Int32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Int32le.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Int32le
+  ( putInt32
+  , putInt32Bits
+  )
+where
+
+import Rattletrap.Type.Int32le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putInt32 :: Int32le -> Binary.Put
+putInt32 int32 = Binary.putInt32le (int32leValue int32)
+
+putInt32Bits :: Int32le -> BinaryBits.BitPut ()
+putInt32Bits int32 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putInt32 int32))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/Int64Attribute.hs b/source/library/Rattletrap/Encode/Int64Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Int64Attribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.Int64Attribute
+  ( putInt64Attribute
+  )
+where
+
+import Rattletrap.Encode.Int64le
+import Rattletrap.Type.Int64Attribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putInt64Attribute :: Int64Attribute -> BinaryBits.BitPut ()
+putInt64Attribute int64Attribute =
+  putInt64Bits (int64AttributeValue int64Attribute)
diff --git a/source/library/Rattletrap/Encode/Int64le.hs b/source/library/Rattletrap/Encode/Int64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Int64le.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Encode.Int64le
+  ( putInt64Bits
+  )
+where
+
+import Rattletrap.Type.Int64le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putInt64 :: Int64le -> Binary.Put
+putInt64 int64 = Binary.putInt64le (int64leValue int64)
+
+putInt64Bits :: Int64le -> BinaryBits.BitPut ()
+putInt64Bits int64 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putInt64 int64))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/Int8Vector.hs b/source/library/Rattletrap/Encode/Int8Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Int8Vector.hs
@@ -0,0 +1,23 @@
+module Rattletrap.Encode.Int8Vector
+  ( putInt8Vector
+  )
+where
+
+import Rattletrap.Encode.Int8le
+import Rattletrap.Type.Int8Vector
+import Rattletrap.Type.Int8le
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putInt8Vector :: Int8Vector -> BinaryBits.BitPut ()
+putInt8Vector int8Vector = do
+  putInt8VectorField (int8VectorX int8Vector)
+  putInt8VectorField (int8VectorY int8Vector)
+  putInt8VectorField (int8VectorZ int8Vector)
+
+putInt8VectorField :: Maybe Int8le -> BinaryBits.BitPut ()
+putInt8VectorField maybeField = case maybeField of
+  Nothing -> BinaryBits.putBool False
+  Just field -> do
+    BinaryBits.putBool True
+    putInt8Bits field
diff --git a/source/library/Rattletrap/Encode/Int8le.hs b/source/library/Rattletrap/Encode/Int8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Int8le.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.Int8le
+  ( putInt8Bits
+  )
+where
+
+import Rattletrap.Type.Int8le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putInt8 :: Int8le -> Binary.Put
+putInt8 int8 = Binary.putInt8 (int8leValue int8)
+
+putInt8Bits :: Int8le -> BinaryBits.BitPut ()
+putInt8Bits int8 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putInt8 int8))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/IntAttribute.hs b/source/library/Rattletrap/Encode/IntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/IntAttribute.hs
@@ -0,0 +1,12 @@
+module Rattletrap.Encode.IntAttribute
+  ( putIntAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Type.IntAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putIntAttribute :: IntAttribute -> BinaryBits.BitPut ()
+putIntAttribute intAttribute = putInt32Bits (intAttributeValue intAttribute)
diff --git a/source/library/Rattletrap/Encode/KeyFrame.hs b/source/library/Rattletrap/Encode/KeyFrame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/KeyFrame.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.KeyFrame
+  ( putKeyFrame
+  )
+where
+
+import Rattletrap.Encode.Float32le
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.KeyFrame
+
+import qualified Data.Binary as Binary
+
+putKeyFrame :: KeyFrame -> Binary.Put
+putKeyFrame keyFrame = do
+  putFloat32 (keyFrameTime keyFrame)
+  putWord32 (keyFrameFrame keyFrame)
+  putWord32 (keyFramePosition keyFrame)
diff --git a/source/library/Rattletrap/Encode/List.hs b/source/library/Rattletrap/Encode/List.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/List.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.List
+  ( putList
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.List
+import Rattletrap.Type.Word32le
+
+import qualified Data.Binary as Binary
+
+putList :: (a -> Binary.Put) -> List a -> Binary.Put
+putList putElement list = do
+  let elements = listValue list
+  putWord32 (Word32le (fromIntegral (length elements)))
+  mapM_ putElement elements
diff --git a/source/library/Rattletrap/Encode/LoadoutAttribute.hs b/source/library/Rattletrap/Encode/LoadoutAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/LoadoutAttribute.hs
@@ -0,0 +1,35 @@
+module Rattletrap.Encode.LoadoutAttribute
+  ( putLoadoutAttribute
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.LoadoutAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putLoadoutAttribute :: LoadoutAttribute -> BinaryBits.BitPut ()
+putLoadoutAttribute loadoutAttribute = do
+  putWord8Bits (loadoutAttributeVersion loadoutAttribute)
+  putWord32Bits (loadoutAttributeBody loadoutAttribute)
+  putWord32Bits (loadoutAttributeDecal loadoutAttribute)
+  putWord32Bits (loadoutAttributeWheels loadoutAttribute)
+  putWord32Bits (loadoutAttributeRocketTrail loadoutAttribute)
+  putWord32Bits (loadoutAttributeAntenna loadoutAttribute)
+  putWord32Bits (loadoutAttributeTopper loadoutAttribute)
+  putWord32Bits (loadoutAttributeUnknown1 loadoutAttribute)
+  putOptional (loadoutAttributeUnknown2 loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeEngineAudio loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeTrail loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeGoalExplosion loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeBanner loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeUnknown3 loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeUnknown4 loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeUnknown5 loadoutAttribute) putWord32Bits
+  putOptional (loadoutAttributeUnknown6 loadoutAttribute) putWord32Bits
+
+putOptional :: Maybe a -> (a -> BinaryBits.BitPut ()) -> BinaryBits.BitPut ()
+putOptional m f = case m of
+  Just x -> f x
+  Nothing -> pure ()
diff --git a/source/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs b/source/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/LoadoutOnlineAttribute.hs
@@ -0,0 +1,17 @@
+module Rattletrap.Encode.LoadoutOnlineAttribute
+  ( putLoadoutOnlineAttribute
+  )
+where
+
+import Rattletrap.Encode.ProductAttribute
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.LoadoutOnlineAttribute
+import Rattletrap.Type.Word8le
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putLoadoutOnlineAttribute :: LoadoutOnlineAttribute -> BinaryBits.BitPut ()
+putLoadoutOnlineAttribute loadoutAttribute = do
+  let attributes = loadoutAttributeValue loadoutAttribute
+  putWord8Bits (Word8le (fromIntegral (length attributes)))
+  mapM_ putProductAttributes attributes
diff --git a/source/library/Rattletrap/Encode/LoadoutsAttribute.hs b/source/library/Rattletrap/Encode/LoadoutsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/LoadoutsAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.LoadoutsAttribute
+  ( putLoadoutsAttribute
+  )
+where
+
+import Rattletrap.Encode.LoadoutAttribute
+import Rattletrap.Type.LoadoutsAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putLoadoutsAttribute :: LoadoutsAttribute -> BinaryBits.BitPut ()
+putLoadoutsAttribute loadoutsAttribute = do
+  putLoadoutAttribute (loadoutsAttributeBlue loadoutsAttribute)
+  putLoadoutAttribute (loadoutsAttributeOrange loadoutsAttribute)
diff --git a/source/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs b/source/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/LoadoutsOnlineAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Encode.LoadoutsOnlineAttribute
+  ( putLoadoutsOnlineAttribute
+  )
+where
+
+import Rattletrap.Encode.LoadoutOnlineAttribute
+import Rattletrap.Type.LoadoutsOnlineAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putLoadoutsOnlineAttribute :: LoadoutsOnlineAttribute -> BinaryBits.BitPut ()
+putLoadoutsOnlineAttribute loadoutsOnlineAttribute = do
+  putLoadoutOnlineAttribute
+    (loadoutsOnlineAttributeBlue loadoutsOnlineAttribute)
+  putLoadoutOnlineAttribute
+    (loadoutsOnlineAttributeOrange loadoutsOnlineAttribute)
+  BinaryBits.putBool (loadoutsOnlineAttributeUnknown1 loadoutsOnlineAttribute)
+  BinaryBits.putBool (loadoutsOnlineAttributeUnknown2 loadoutsOnlineAttribute)
diff --git a/source/library/Rattletrap/Encode/LocationAttribute.hs b/source/library/Rattletrap/Encode/LocationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/LocationAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.LocationAttribute
+  ( putLocationAttribute
+  )
+where
+
+import Rattletrap.Encode.Vector
+import Rattletrap.Type.LocationAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putLocationAttribute :: LocationAttribute -> BinaryBits.BitPut ()
+putLocationAttribute locationAttribute =
+  putVector (locationAttributeValue locationAttribute)
diff --git a/source/library/Rattletrap/Encode/Mark.hs b/source/library/Rattletrap/Encode/Mark.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Mark.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Encode.Mark
+  ( putMark
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Mark
+
+import qualified Data.Binary as Binary
+
+putMark :: Mark -> Binary.Put
+putMark mark = do
+  putText (markValue mark)
+  putWord32 (markFrame mark)
diff --git a/source/library/Rattletrap/Encode/Message.hs b/source/library/Rattletrap/Encode/Message.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Message.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.Message
+  ( putMessage
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Message
+
+import qualified Data.Binary as Binary
+
+putMessage :: Message -> Binary.Put
+putMessage message = do
+  putWord32 (messageFrame message)
+  putText (messageName message)
+  putText (messageValue message)
diff --git a/source/library/Rattletrap/Encode/MusicStingerAttribute.hs b/source/library/Rattletrap/Encode/MusicStingerAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/MusicStingerAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.MusicStingerAttribute
+  ( putMusicStingerAttribute
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.MusicStingerAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putMusicStingerAttribute :: MusicStingerAttribute -> BinaryBits.BitPut ()
+putMusicStingerAttribute musicStingerAttribute = do
+  BinaryBits.putBool (musicStingerAttributeFlag musicStingerAttribute)
+  putWord32Bits (musicStingerAttributeCue musicStingerAttribute)
+  putWord8Bits (musicStingerAttributeTrigger musicStingerAttribute)
diff --git a/source/library/Rattletrap/Encode/PartyLeaderAttribute.hs b/source/library/Rattletrap/Encode/PartyLeaderAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PartyLeaderAttribute.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.PartyLeaderAttribute
+  ( putPartyLeaderAttribute
+  )
+where
+
+import Rattletrap.Encode.RemoteId
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.PartyLeaderAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putPartyLeaderAttribute :: PartyLeaderAttribute -> BinaryBits.BitPut ()
+putPartyLeaderAttribute partyLeaderAttribute = do
+  putWord8Bits (partyLeaderAttributeSystemId partyLeaderAttribute)
+  case partyLeaderAttributeId partyLeaderAttribute of
+    Nothing -> pure ()
+    Just (remoteId, localId) -> do
+      putRemoteId remoteId
+      putWord8Bits localId
diff --git a/source/library/Rattletrap/Encode/PickupAttribute.hs b/source/library/Rattletrap/Encode/PickupAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PickupAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Encode.PickupAttribute
+  ( putPickupAttribute
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.PickupAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putPickupAttribute :: PickupAttribute -> BinaryBits.BitPut ()
+putPickupAttribute pickupAttribute = do
+  case pickupAttributeInstigatorId pickupAttribute of
+    Nothing -> BinaryBits.putBool False
+    Just instigatorId -> do
+      BinaryBits.putBool True
+      putWord32Bits instigatorId
+  BinaryBits.putBool (pickupAttributePickedUp pickupAttribute)
diff --git a/source/library/Rattletrap/Encode/PickupAttributeNew.hs b/source/library/Rattletrap/Encode/PickupAttributeNew.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PickupAttributeNew.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.PickupAttributeNew
+  ( putPickupAttributeNew
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.PickupAttributeNew
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putPickupAttributeNew :: PickupAttributeNew -> BinaryBits.BitPut ()
+putPickupAttributeNew pickupAttributeNew = do
+  case pickupAttributeNewInstigatorId pickupAttributeNew of
+    Nothing -> BinaryBits.putBool False
+    Just instigatorId -> do
+      BinaryBits.putBool True
+      putWord32Bits instigatorId
+  putWord8Bits (pickupAttributeNewPickedUp pickupAttributeNew)
diff --git a/source/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs b/source/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.PlayerHistoryKeyAttribute
+  ( putPlayerHistoryKeyAttribute
+  )
+where
+
+import Rattletrap.Encode.Common
+import Rattletrap.Type.PlayerHistoryKeyAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putPlayerHistoryKeyAttribute
+  :: PlayerHistoryKeyAttribute -> BinaryBits.BitPut ()
+putPlayerHistoryKeyAttribute = putBitsLE 14 . playerHistoryKeyAttributeUnknown
diff --git a/source/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs b/source/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PrivateMatchSettingsAttribute.hs
@@ -0,0 +1,26 @@
+module Rattletrap.Encode.PrivateMatchSettingsAttribute
+  ( putPrivateMatchSettingsAttribute
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.PrivateMatchSettingsAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putPrivateMatchSettingsAttribute
+  :: PrivateMatchSettingsAttribute -> BinaryBits.BitPut ()
+putPrivateMatchSettingsAttribute privateMatchSettingsAttribute = do
+  putTextBits
+    (privateMatchSettingsAttributeMutators privateMatchSettingsAttribute)
+  putWord32Bits
+    (privateMatchSettingsAttributeJoinableBy privateMatchSettingsAttribute)
+  putWord32Bits
+    (privateMatchSettingsAttributeMaxPlayers privateMatchSettingsAttribute)
+  putTextBits
+    (privateMatchSettingsAttributeGameName privateMatchSettingsAttribute)
+  putTextBits
+    (privateMatchSettingsAttributePassword privateMatchSettingsAttribute)
+  BinaryBits.putBool
+    (privateMatchSettingsAttributeFlag privateMatchSettingsAttribute)
diff --git a/source/library/Rattletrap/Encode/ProductAttribute.hs b/source/library/Rattletrap/Encode/ProductAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ProductAttribute.hs
@@ -0,0 +1,37 @@
+module Rattletrap.Encode.ProductAttribute
+  ( putProductAttributes
+  )
+where
+
+import Rattletrap.Encode.Common
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Encode.Word32le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Encode.Str
+import Rattletrap.Type.ProductAttribute
+import Rattletrap.Type.Word8le
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putProductAttributes :: [ProductAttribute] -> BinaryBits.BitPut ()
+putProductAttributes attributes = do
+  putWord8Bits (Word8le (fromIntegral (length attributes)))
+  mapM_ putProductAttribute attributes
+
+putProductAttribute :: ProductAttribute -> BinaryBits.BitPut ()
+putProductAttribute attribute = do
+  BinaryBits.putBool (productAttributeUnknown attribute)
+  putWord32Bits (productAttributeObjectId attribute)
+  case productAttributeValue attribute of
+    ProductAttributeValuePaintedOld x -> putCompressedWord x
+    ProductAttributeValuePaintedNew x -> putBitsLE 31 x
+    ProductAttributeValueTeamEditionOld x -> putCompressedWord x
+    ProductAttributeValueTeamEditionNew x -> putBitsLE 31 x
+    ProductAttributeValueSpecialEdition x -> putBitsLE 31 x
+    ProductAttributeValueUserColorOld x -> case x of
+      Nothing -> BinaryBits.putBool False
+      Just y -> do
+        BinaryBits.putBool True
+        putBitsLE 31 y
+    ProductAttributeValueUserColorNew x -> putWord32Bits x
+    ProductAttributeValueTitleId x -> putTextBits x
diff --git a/source/library/Rattletrap/Encode/Property.hs b/source/library/Rattletrap/Encode/Property.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Property.hs
@@ -0,0 +1,17 @@
+module Rattletrap.Encode.Property
+  ( putProperty
+  )
+where
+
+import Rattletrap.Encode.PropertyValue
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word64le
+import Rattletrap.Type.Property
+
+import qualified Data.Binary as Binary
+
+putProperty :: Property -> Binary.Put
+putProperty property = do
+  putText (propertyKind property)
+  putWord64 (propertySize property)
+  putPropertyValue putProperty (propertyValue property)
diff --git a/source/library/Rattletrap/Encode/PropertyValue.hs b/source/library/Rattletrap/Encode/PropertyValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/PropertyValue.hs
@@ -0,0 +1,30 @@
+module Rattletrap.Encode.PropertyValue
+  ( putPropertyValue
+  )
+where
+
+import Rattletrap.Encode.Dictionary
+import Rattletrap.Encode.Float32le
+import Rattletrap.Encode.Int32le
+import Rattletrap.Encode.List
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.Word64le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.PropertyValue
+
+import qualified Data.Binary as Binary
+
+putPropertyValue :: (a -> Binary.Put) -> PropertyValue a -> Binary.Put
+putPropertyValue putProperty value = case value of
+  PropertyValueArray list -> putList (putDictionary putProperty) list
+  PropertyValueBool word8 -> putWord8 word8
+  PropertyValueByte k mv -> do
+    putText k
+    case mv of
+      Nothing -> pure ()
+      Just v -> putText v
+  PropertyValueFloat float32 -> putFloat32 float32
+  PropertyValueInt int32 -> putInt32 int32
+  PropertyValueName text -> putText text
+  PropertyValueQWord word64 -> putWord64 word64
+  PropertyValueStr text -> putText text
diff --git a/source/library/Rattletrap/Encode/QWordAttribute.hs b/source/library/Rattletrap/Encode/QWordAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/QWordAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.QWordAttribute
+  ( putQWordAttribute
+  )
+where
+
+import Rattletrap.Encode.Word64le
+import Rattletrap.Type.QWordAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putQWordAttribute :: QWordAttribute -> BinaryBits.BitPut ()
+putQWordAttribute qWordAttribute =
+  putWord64Bits (qWordAttributeValue qWordAttribute)
diff --git a/source/library/Rattletrap/Encode/Quaternion.hs b/source/library/Rattletrap/Encode/Quaternion.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Quaternion.hs
@@ -0,0 +1,41 @@
+module Rattletrap.Encode.Quaternion
+  ( putQuaternion
+  )
+where
+
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Quaternion
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putQuaternion :: Quaternion -> BinaryBits.BitPut ()
+putQuaternion q = do
+  let c = maxComponent q
+  putComponent c
+  case c of
+    ComponentX -> putParts (quaternionY q) (quaternionZ q) (quaternionW q)
+    ComponentY -> putParts (quaternionX q) (quaternionZ q) (quaternionW q)
+    ComponentZ -> putParts (quaternionX q) (quaternionY q) (quaternionW q)
+    ComponentW -> putParts (quaternionX q) (quaternionY q) (quaternionZ q)
+
+putComponent :: Component -> BinaryBits.BitPut ()
+putComponent component = putCompressedWord
+  (CompressedWord
+    3
+    (case component of
+      ComponentX -> 0
+      ComponentY -> 1
+      ComponentZ -> 2
+      ComponentW -> 3
+    )
+  )
+
+putParts :: Double -> Double -> Double -> BinaryBits.BitPut ()
+putParts a b c = do
+  putPart a
+  putPart b
+  putPart c
+
+putPart :: Double -> BinaryBits.BitPut ()
+putPart = putCompressedWord . compressPart
diff --git a/source/library/Rattletrap/Encode/RemoteId.hs b/source/library/Rattletrap/Encode/RemoteId.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/RemoteId.hs
@@ -0,0 +1,35 @@
+module Rattletrap.Encode.RemoteId
+  ( putRemoteId
+  )
+where
+
+import Rattletrap.Encode.Common
+import Rattletrap.Encode.Word64le
+import Rattletrap.Type.RemoteId
+import Rattletrap.Type.Word64le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.ByteString as Bytes
+
+putRemoteId :: RemoteId -> BinaryBits.BitPut ()
+putRemoteId remoteId = case remoteId of
+  RemoteIdPlayStation name bytes -> do
+    let rawName = reverseBytes (padBytes (16 :: Int) (encodeLatin1 name))
+    BinaryBits.putByteString rawName
+    BinaryBits.putByteString (Bytes.pack bytes)
+  RemoteIdPsyNet e -> case e of
+    Left l -> putWord64Bits l
+    Right (a, b, c, d) -> putWord256 a b c d
+  RemoteIdSplitscreen word24 -> putBitsLE 24 word24
+  RemoteIdSteam word64 -> putWord64Bits word64
+  RemoteIdSwitch a b c d -> putWord256 a b c d
+  RemoteIdXbox word64 -> putWord64Bits word64
+
+putWord256
+  :: Word64le -> Word64le -> Word64le -> Word64le -> BinaryBits.BitPut ()
+putWord256 a b c d = do
+  putWord64Bits a
+  putWord64Bits b
+  putWord64Bits c
+  putWord64Bits d
diff --git a/source/library/Rattletrap/Encode/Replay.hs b/source/library/Rattletrap/Encode/Replay.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Replay.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Replay
+  ( putReplay
+  )
+where
+
+import Rattletrap.Encode.Content
+import Rattletrap.Encode.Header
+import Rattletrap.Encode.Section
+import Rattletrap.Type.Replay
+
+import qualified Data.Binary as Binary
+
+-- | Generates a raw replay. Use this with 'Data.Binary.Put.runPut'.
+--
+-- @
+-- let bytes = 'Data.Binary.Put.runPut' ('putReplay' replay)
+-- @
+putReplay :: Replay -> Binary.Put
+putReplay replay = do
+  putSection putHeader (replayHeader replay)
+  putSection putContent (replayContent replay)
diff --git a/source/library/Rattletrap/Encode/Replication.hs b/source/library/Rattletrap/Encode/Replication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Replication.hs
@@ -0,0 +1,26 @@
+module Rattletrap.Encode.Replication
+  ( putReplications
+  )
+where
+
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Encode.ReplicationValue
+import Rattletrap.Type.Replication
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putReplications :: [Replication] -> BinaryBits.BitPut ()
+putReplications replications = case replications of
+  [] -> BinaryBits.putBool False
+  [replication] -> do
+    putReplication replication
+    BinaryBits.putBool False
+  first : rest -> do
+    putReplication first
+    putReplications rest
+
+putReplication :: Replication -> BinaryBits.BitPut ()
+putReplication replication = do
+  BinaryBits.putBool True
+  putCompressedWord (replicationActorId replication)
+  putReplicationValue (replicationValue replication)
diff --git a/source/library/Rattletrap/Encode/ReplicationValue.hs b/source/library/Rattletrap/Encode/ReplicationValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ReplicationValue.hs
@@ -0,0 +1,25 @@
+module Rattletrap.Encode.ReplicationValue
+  ( putReplicationValue
+  )
+where
+
+import Rattletrap.Encode.DestroyedReplication
+import Rattletrap.Encode.SpawnedReplication
+import Rattletrap.Encode.UpdatedReplication
+import Rattletrap.Type.ReplicationValue
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putReplicationValue :: ReplicationValue -> BinaryBits.BitPut ()
+putReplicationValue value = case value of
+  ReplicationValueSpawned x -> do
+    BinaryBits.putBool True
+    BinaryBits.putBool True
+    putSpawnedReplication x
+  ReplicationValueUpdated x -> do
+    BinaryBits.putBool True
+    BinaryBits.putBool False
+    putUpdatedReplication x
+  ReplicationValueDestroyed x -> do
+    BinaryBits.putBool False
+    putDestroyedReplication x
diff --git a/source/library/Rattletrap/Encode/ReservationAttribute.hs b/source/library/Rattletrap/Encode/ReservationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/ReservationAttribute.hs
@@ -0,0 +1,24 @@
+module Rattletrap.Encode.ReservationAttribute
+  ( putReservationAttribute
+  )
+where
+
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Encode.Str
+import Rattletrap.Encode.UniqueIdAttribute
+import Rattletrap.Type.ReservationAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putReservationAttribute :: ReservationAttribute -> BinaryBits.BitPut ()
+putReservationAttribute reservationAttribute = do
+  putCompressedWord (reservationAttributeNumber reservationAttribute)
+  putUniqueIdAttribute (reservationAttributeUniqueId reservationAttribute)
+  case reservationAttributeName reservationAttribute of
+    Nothing -> pure ()
+    Just name -> putTextBits name
+  BinaryBits.putBool (reservationAttributeUnknown1 reservationAttribute)
+  BinaryBits.putBool (reservationAttributeUnknown2 reservationAttribute)
+  case reservationAttributeUnknown3 reservationAttribute of
+    Nothing -> pure ()
+    Just c -> BinaryBits.putWord8 6 c
diff --git a/source/library/Rattletrap/Encode/RigidBodyStateAttribute.hs b/source/library/Rattletrap/Encode/RigidBodyStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/RigidBodyStateAttribute.hs
@@ -0,0 +1,22 @@
+module Rattletrap.Encode.RigidBodyStateAttribute
+  ( putRigidBodyStateAttribute
+  )
+where
+
+import Rattletrap.Encode.Rotation
+import Rattletrap.Encode.Vector
+import Rattletrap.Type.RigidBodyStateAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putRigidBodyStateAttribute :: RigidBodyStateAttribute -> BinaryBits.BitPut ()
+putRigidBodyStateAttribute rigidBodyStateAttribute = do
+  BinaryBits.putBool (rigidBodyStateAttributeSleeping rigidBodyStateAttribute)
+  putVector (rigidBodyStateAttributeLocation rigidBodyStateAttribute)
+  putRotation (rigidBodyStateAttributeRotation rigidBodyStateAttribute)
+  case rigidBodyStateAttributeLinearVelocity rigidBodyStateAttribute of
+    Nothing -> pure ()
+    Just linearVelocity -> putVector linearVelocity
+  case rigidBodyStateAttributeAngularVelocity rigidBodyStateAttribute of
+    Nothing -> pure ()
+    Just angularVelocity -> putVector angularVelocity
diff --git a/source/library/Rattletrap/Encode/Rotation.hs b/source/library/Rattletrap/Encode/Rotation.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Rotation.hs
@@ -0,0 +1,15 @@
+module Rattletrap.Encode.Rotation
+  ( putRotation
+  )
+where
+
+import Rattletrap.Encode.CompressedWordVector
+import Rattletrap.Encode.Quaternion
+import Rattletrap.Type.Rotation
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putRotation :: Rotation -> BinaryBits.BitPut ()
+putRotation r = case r of
+  RotationCompressedWordVector cwv -> putCompressedWordVector cwv
+  RotationQuaternion q -> putQuaternion q
diff --git a/source/library/Rattletrap/Encode/Section.hs b/source/library/Rattletrap/Encode/Section.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Section.hs
@@ -0,0 +1,31 @@
+module Rattletrap.Encode.Section
+  ( putSection
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.Section
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Crc
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Lazy as LazyBytes
+
+-- | Given a way to put the 'sectionBody', puts a section. This will also put
+-- the size and CRC.
+--
+-- @
+-- let bytes = 'Data.Binary.Put.runPut' ('putSection' 'Rattletrap.Content.putContent' content)
+-- @
+putSection :: (a -> Binary.Put) -> Section a -> Binary.Put
+putSection putBody section = do
+  let
+    rawBody =
+      LazyBytes.toStrict (Binary.runPut (putBody (sectionBody section)))
+  let size = Bytes.length rawBody
+  let crc = getCrc32 rawBody
+  putWord32 (Word32le (fromIntegral size))
+  putWord32 (Word32le crc)
+  Binary.putByteString rawBody
diff --git a/source/library/Rattletrap/Encode/SpawnedReplication.hs b/source/library/Rattletrap/Encode/SpawnedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/SpawnedReplication.hs
@@ -0,0 +1,19 @@
+module Rattletrap.Encode.SpawnedReplication
+  ( putSpawnedReplication
+  )
+where
+
+import Rattletrap.Encode.Initialization
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.SpawnedReplication
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putSpawnedReplication :: SpawnedReplication -> BinaryBits.BitPut ()
+putSpawnedReplication spawnedReplication = do
+  BinaryBits.putBool (spawnedReplicationFlag spawnedReplication)
+  case spawnedReplicationNameIndex spawnedReplication of
+    Nothing -> pure ()
+    Just nameIndex -> putWord32Bits nameIndex
+  putWord32Bits (spawnedReplicationObjectId spawnedReplication)
+  putInitialization (spawnedReplicationInitialization spawnedReplication)
diff --git a/source/library/Rattletrap/Encode/StatEventAttribute.hs b/source/library/Rattletrap/Encode/StatEventAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/StatEventAttribute.hs
@@ -0,0 +1,14 @@
+module Rattletrap.Encode.StatEventAttribute
+  ( putStatEventAttribute
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Type.StatEventAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putStatEventAttribute :: StatEventAttribute -> BinaryBits.BitPut ()
+putStatEventAttribute statEventAttribute = do
+  BinaryBits.putBool (statEventAttributeUnknown statEventAttribute)
+  putInt32Bits (statEventAttributeObjectId statEventAttribute)
diff --git a/source/library/Rattletrap/Encode/Str.hs b/source/library/Rattletrap/Encode/Str.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Str.hs
@@ -0,0 +1,53 @@
+module Rattletrap.Encode.Str
+  ( putText
+  , putTextBits
+  )
+where
+
+import Rattletrap.Encode.Int32le
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Str
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString as Bytes
+import qualified Data.Char as Char
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+
+putText :: Str -> Binary.Put
+putText text = do
+  let size = getTextSize text
+  let encode = getTextEncoder size
+  putInt32 size
+  Binary.putByteString (encode (addNull (strValue text)))
+
+putTextBits :: Str -> BinaryBits.BitPut ()
+putTextBits text = do
+  let size = getTextSize text
+  let encode = getTextEncoder size
+  putInt32Bits size
+  BinaryBits.putByteString (reverseBytes (encode (addNull (strValue text))))
+
+getTextSize :: Str -> Int32le
+getTextSize text =
+  let
+    value = strValue text
+    scale = if Text.all Char.isLatin1 value then 1 else -1 :: Int32
+    rawSize = if Text.null value
+      then 0
+      else fromIntegral (Text.length value) + 1 :: Int32
+    size = if value == Text.pack "\x00\x00\x00None"
+      then 0x05000000
+      else scale * rawSize :: Int32
+  in Int32le size
+
+getTextEncoder :: Int32le -> Text.Text -> Bytes.ByteString
+getTextEncoder size text =
+  if size < Int32le 0 then Text.encodeUtf16LE text else encodeLatin1 text
+
+addNull :: Text.Text -> Text.Text
+addNull text = if Text.null text then text else Text.snoc text '\x00'
diff --git a/source/library/Rattletrap/Encode/StringAttribute.hs b/source/library/Rattletrap/Encode/StringAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/StringAttribute.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.StringAttribute
+  ( putStringAttribute
+  )
+where
+
+import Rattletrap.Encode.Str
+import Rattletrap.Type.StringAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putStringAttribute :: StringAttribute -> BinaryBits.BitPut ()
+putStringAttribute stringAttribute =
+  putTextBits (stringAttributeValue stringAttribute)
diff --git a/source/library/Rattletrap/Encode/TeamPaintAttribute.hs b/source/library/Rattletrap/Encode/TeamPaintAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/TeamPaintAttribute.hs
@@ -0,0 +1,18 @@
+module Rattletrap.Encode.TeamPaintAttribute
+  ( putTeamPaintAttribute
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.TeamPaintAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putTeamPaintAttribute :: TeamPaintAttribute -> BinaryBits.BitPut ()
+putTeamPaintAttribute teamPaintAttribute = do
+  putWord8Bits (teamPaintAttributeTeam teamPaintAttribute)
+  putWord8Bits (teamPaintAttributePrimaryColor teamPaintAttribute)
+  putWord8Bits (teamPaintAttributeAccentColor teamPaintAttribute)
+  putWord32Bits (teamPaintAttributePrimaryFinish teamPaintAttribute)
+  putWord32Bits (teamPaintAttributeAccentFinish teamPaintAttribute)
diff --git a/source/library/Rattletrap/Encode/TitleAttribute.hs b/source/library/Rattletrap/Encode/TitleAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/TitleAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Encode.TitleAttribute
+  ( putTitleAttribute
+  )
+where
+
+import Rattletrap.Encode.Word32le
+import Rattletrap.Type.TitleAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putTitleAttribute :: TitleAttribute -> BinaryBits.BitPut ()
+putTitleAttribute titleAttribute = do
+  BinaryBits.putBool (titleAttributeUnknown1 titleAttribute)
+  BinaryBits.putBool (titleAttributeUnknown2 titleAttribute)
+  putWord32Bits (titleAttributeUnknown3 titleAttribute)
+  putWord32Bits (titleAttributeUnknown4 titleAttribute)
+  putWord32Bits (titleAttributeUnknown5 titleAttribute)
+  putWord32Bits (titleAttributeUnknown6 titleAttribute)
+  putWord32Bits (titleAttributeUnknown7 titleAttribute)
+  BinaryBits.putBool (titleAttributeUnknown8 titleAttribute)
diff --git a/source/library/Rattletrap/Encode/UniqueIdAttribute.hs b/source/library/Rattletrap/Encode/UniqueIdAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/UniqueIdAttribute.hs
@@ -0,0 +1,16 @@
+module Rattletrap.Encode.UniqueIdAttribute
+  ( putUniqueIdAttribute
+  )
+where
+
+import Rattletrap.Encode.RemoteId
+import Rattletrap.Encode.Word8le
+import Rattletrap.Type.UniqueIdAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putUniqueIdAttribute :: UniqueIdAttribute -> BinaryBits.BitPut ()
+putUniqueIdAttribute uniqueIdAttribute = do
+  putWord8Bits (uniqueIdAttributeSystemId uniqueIdAttribute)
+  putRemoteId (uniqueIdAttributeRemoteId uniqueIdAttribute)
+  putWord8Bits (uniqueIdAttributeLocalId uniqueIdAttribute)
diff --git a/source/library/Rattletrap/Encode/UpdatedReplication.hs b/source/library/Rattletrap/Encode/UpdatedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/UpdatedReplication.hs
@@ -0,0 +1,13 @@
+module Rattletrap.Encode.UpdatedReplication
+  ( putUpdatedReplication
+  )
+where
+
+import Rattletrap.Encode.Attribute
+import Rattletrap.Type.UpdatedReplication
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putUpdatedReplication :: UpdatedReplication -> BinaryBits.BitPut ()
+putUpdatedReplication updatedReplication =
+  putAttributes (updatedReplicationAttributes updatedReplication)
diff --git a/source/library/Rattletrap/Encode/Vector.hs b/source/library/Rattletrap/Encode/Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Vector.hs
@@ -0,0 +1,27 @@
+module Rattletrap.Encode.Vector
+  ( putVector
+  )
+where
+
+import Rattletrap.Encode.CompressedWord
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Vector
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putVector :: Vector -> BinaryBits.BitPut ()
+putVector vector = do
+  let
+    bitSize =
+      round (logBase (2 :: Float) (fromIntegral (vectorBias vector))) - 1 :: Word
+    dx =
+      fromIntegral (vectorX vector + fromIntegral (vectorBias vector)) :: Word
+    dy =
+      fromIntegral (vectorY vector + fromIntegral (vectorBias vector)) :: Word
+    dz =
+      fromIntegral (vectorZ vector + fromIntegral (vectorBias vector)) :: Word
+    limit = 2 ^ (bitSize + 2) :: Word
+  putCompressedWord (vectorSize vector)
+  putCompressedWord (CompressedWord limit dx)
+  putCompressedWord (CompressedWord limit dy)
+  putCompressedWord (CompressedWord limit dz)
diff --git a/source/library/Rattletrap/Encode/WeldedInfoAttribute.hs b/source/library/Rattletrap/Encode/WeldedInfoAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/WeldedInfoAttribute.hs
@@ -0,0 +1,20 @@
+module Rattletrap.Encode.WeldedInfoAttribute
+  ( putWeldedInfoAttribute
+  )
+where
+
+import Rattletrap.Encode.Float32le
+import Rattletrap.Encode.Int32le
+import Rattletrap.Encode.Int8Vector
+import Rattletrap.Encode.Vector
+import Rattletrap.Type.WeldedInfoAttribute
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+
+putWeldedInfoAttribute :: WeldedInfoAttribute -> BinaryBits.BitPut ()
+putWeldedInfoAttribute weldedInfoAttribute = do
+  BinaryBits.putBool (weldedInfoAttributeActive weldedInfoAttribute)
+  putInt32Bits (weldedInfoAttributeActorId weldedInfoAttribute)
+  putVector (weldedInfoAttributeOffset weldedInfoAttribute)
+  putFloat32Bits (weldedInfoAttributeMass weldedInfoAttribute)
+  putInt8Vector (weldedInfoAttributeRotation weldedInfoAttribute)
diff --git a/source/library/Rattletrap/Encode/Word32le.hs b/source/library/Rattletrap/Encode/Word32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Word32le.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Word32le
+  ( putWord32
+  , putWord32Bits
+  )
+where
+
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putWord32 :: Word32le -> Binary.Put
+putWord32 word32 = Binary.putWord32le (word32leValue word32)
+
+putWord32Bits :: Word32le -> BinaryBits.BitPut ()
+putWord32Bits word32 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putWord32 word32))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/Word64le.hs b/source/library/Rattletrap/Encode/Word64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Word64le.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Word64le
+  ( putWord64
+  , putWord64Bits
+  )
+where
+
+import Rattletrap.Type.Word64le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putWord64 :: Word64le -> Binary.Put
+putWord64 word64 = Binary.putWord64le (word64leValue word64)
+
+putWord64Bits :: Word64le -> BinaryBits.BitPut ()
+putWord64Bits word64 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putWord64 word64))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Encode/Word8le.hs b/source/library/Rattletrap/Encode/Word8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Encode/Word8le.hs
@@ -0,0 +1,21 @@
+module Rattletrap.Encode.Word8le
+  ( putWord8
+  , putWord8Bits
+  )
+where
+
+import Rattletrap.Type.Word8le
+import Rattletrap.Utility.Bytes
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString.Lazy as LazyBytes
+
+putWord8 :: Word8le -> Binary.Put
+putWord8 word8 = Binary.putWord8 (word8leValue word8)
+
+putWord8Bits :: Word8le -> BinaryBits.BitPut ()
+putWord8Bits word8 = do
+  let bytes = LazyBytes.toStrict (Binary.runPut (putWord8 word8))
+  BinaryBits.putByteString (reverseBytes bytes)
diff --git a/source/library/Rattletrap/Type/AppliedDamageAttribute.hs b/source/library/Rattletrap/Type/AppliedDamageAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/AppliedDamageAttribute.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.AppliedDamageAttribute
+  ( AppliedDamageAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Vector
+import Rattletrap.Type.Word8le
+
+data AppliedDamageAttribute = AppliedDamageAttribute
+  { appliedDamageAttributeUnknown1 :: Word8le
+  , appliedDamageAttributeLocation :: Vector
+  , appliedDamageAttributeUnknown3 :: Int32le
+  , appliedDamageAttributeUnknown4 :: Int32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''AppliedDamageAttribute)
diff --git a/source/library/Rattletrap/Type/Attribute.hs b/source/library/Rattletrap/Type/Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Attribute.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Attribute
+  ( Attribute(..)
+  )
+where
+
+import Rattletrap.Type.AttributeValue
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Str
+
+data Attribute = Attribute
+  { attributeId :: CompressedWord
+  , attributeName :: Str
+  -- ^ Read-only! Changing an attribute's name requires editing the class
+  -- attribute map.
+  , attributeValue :: AttributeValue
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Attribute)
diff --git a/source/library/Rattletrap/Type/AttributeMapping.hs b/source/library/Rattletrap/Type/AttributeMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/AttributeMapping.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.AttributeMapping
+  ( AttributeMapping(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+
+data AttributeMapping = AttributeMapping
+  { attributeMappingObjectId :: Word32le
+  , attributeMappingStreamId :: Word32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''AttributeMapping)
diff --git a/source/library/Rattletrap/Type/AttributeType.hs b/source/library/Rattletrap/Type/AttributeType.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/AttributeType.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.AttributeType
+  ( AttributeType(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+data AttributeType
+  = AttributeTypeAppliedDamage
+  | AttributeTypeBoolean
+  | AttributeTypeByte
+  | AttributeTypeCamSettings
+  | AttributeTypeClubColors
+  | AttributeTypeDamageState
+  | AttributeTypeDemolish
+  | AttributeTypeEnum
+  | AttributeTypeExplosion
+  | AttributeTypeExtendedExplosion
+  | AttributeTypeFlaggedInt
+  | AttributeTypeFlaggedByte
+  | AttributeTypeFloat
+  | AttributeTypeGameMode
+  | AttributeTypeInt
+  | AttributeTypeInt64
+  | AttributeTypeLoadout
+  | AttributeTypeLoadoutOnline
+  | AttributeTypeLoadouts
+  | AttributeTypeLoadoutsOnline
+  | AttributeTypeLocation
+  | AttributeTypeMusicStinger
+  | AttributeTypePartyLeader
+  | AttributeTypePickup
+  | AttributeTypePickupNew
+  | AttributeTypePlayerHistoryKey
+  | AttributeTypePrivateMatchSettings
+  | AttributeTypeQWord
+  | AttributeTypeReservation
+  | AttributeTypeRigidBodyState
+  | AttributeTypeStatEvent
+  | AttributeTypeString
+  | AttributeTypeTeamPaint
+  | AttributeTypeTitle
+  | AttributeTypeUniqueId
+  | AttributeTypeWeldedInfo
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''AttributeType)
diff --git a/source/library/Rattletrap/Type/AttributeValue.hs b/source/library/Rattletrap/Type/AttributeValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/AttributeValue.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.AttributeValue
+  ( AttributeValue(..)
+  )
+where
+
+import Rattletrap.Type.AppliedDamageAttribute
+import Rattletrap.Type.BooleanAttribute
+import Rattletrap.Type.ByteAttribute
+import Rattletrap.Type.CamSettingsAttribute
+import Rattletrap.Type.ClubColorsAttribute
+import Rattletrap.Type.Common
+import Rattletrap.Type.DamageStateAttribute
+import Rattletrap.Type.DemolishAttribute
+import Rattletrap.Type.EnumAttribute
+import Rattletrap.Type.ExplosionAttribute
+import Rattletrap.Type.ExtendedExplosionAttribute
+import Rattletrap.Type.FlaggedIntAttribute
+import Rattletrap.Type.FlaggedByteAttribute
+import Rattletrap.Type.FloatAttribute
+import Rattletrap.Type.GameModeAttribute
+import Rattletrap.Type.Int64Attribute
+import Rattletrap.Type.IntAttribute
+import Rattletrap.Type.LoadoutAttribute
+import Rattletrap.Type.LoadoutOnlineAttribute
+import Rattletrap.Type.LoadoutsAttribute
+import Rattletrap.Type.LoadoutsOnlineAttribute
+import Rattletrap.Type.LocationAttribute
+import Rattletrap.Type.MusicStingerAttribute
+import Rattletrap.Type.PartyLeaderAttribute
+import Rattletrap.Type.PickupAttribute
+import Rattletrap.Type.PickupAttributeNew
+import Rattletrap.Type.PlayerHistoryKeyAttribute
+import Rattletrap.Type.PrivateMatchSettingsAttribute
+import Rattletrap.Type.QWordAttribute
+import Rattletrap.Type.ReservationAttribute
+import Rattletrap.Type.RigidBodyStateAttribute
+import Rattletrap.Type.StatEventAttribute
+import Rattletrap.Type.StringAttribute
+import Rattletrap.Type.TeamPaintAttribute
+import Rattletrap.Type.TitleAttribute
+import Rattletrap.Type.UniqueIdAttribute
+import Rattletrap.Type.WeldedInfoAttribute
+
+data AttributeValue
+  = AttributeValueAppliedDamage AppliedDamageAttribute
+  | AttributeValueBoolean BooleanAttribute
+  | AttributeValueByte ByteAttribute
+  | AttributeValueCamSettings CamSettingsAttribute
+  | AttributeValueClubColors ClubColorsAttribute
+  | AttributeValueDamageState DamageStateAttribute
+  | AttributeValueDemolish DemolishAttribute
+  | AttributeValueEnum EnumAttribute
+  | AttributeValueExplosion ExplosionAttribute
+  | AttributeValueExtendedExplosion ExtendedExplosionAttribute
+  | AttributeValueFlaggedInt FlaggedIntAttribute
+  | AttributeValueFlaggedByte FlaggedByteAttribute
+  | AttributeValueFloat FloatAttribute
+  | AttributeValueGameMode GameModeAttribute
+  | AttributeValueInt IntAttribute
+  | AttributeValueInt64 Int64Attribute
+  | AttributeValueLoadout LoadoutAttribute
+  | AttributeValueLoadoutOnline LoadoutOnlineAttribute
+  | AttributeValueLoadouts LoadoutsAttribute
+  | AttributeValueLoadoutsOnline LoadoutsOnlineAttribute
+  | AttributeValueLocation LocationAttribute
+  | AttributeValueMusicStinger MusicStingerAttribute
+  | AttributeValuePartyLeader PartyLeaderAttribute
+  | AttributeValuePickup PickupAttribute
+  | AttributeValuePickupNew PickupAttributeNew
+  | AttributeValuePlayerHistoryKey PlayerHistoryKeyAttribute
+  | AttributeValuePrivateMatchSettings PrivateMatchSettingsAttribute
+  | AttributeValueQWord QWordAttribute
+  | AttributeValueReservation ReservationAttribute
+  | AttributeValueRigidBodyState RigidBodyStateAttribute
+  | AttributeValueStatEvent StatEventAttribute
+  | AttributeValueString StringAttribute
+  | AttributeValueTeamPaint TeamPaintAttribute
+  | AttributeValueTitle TitleAttribute
+  | AttributeValueUniqueId UniqueIdAttribute
+  | AttributeValueWeldedInfo WeldedInfoAttribute
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''AttributeValue)
diff --git a/source/library/Rattletrap/Type/BooleanAttribute.hs b/source/library/Rattletrap/Type/BooleanAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/BooleanAttribute.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.BooleanAttribute
+  ( BooleanAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype BooleanAttribute = BooleanAttribute
+  { booleanAttributeValue :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''BooleanAttribute)
diff --git a/source/library/Rattletrap/Type/ByteAttribute.hs b/source/library/Rattletrap/Type/ByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ByteAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ByteAttribute
+  ( ByteAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word8le
+
+newtype ByteAttribute = ByteAttribute
+  { byteAttributeValue :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ByteAttribute)
diff --git a/source/library/Rattletrap/Type/Cache.hs b/source/library/Rattletrap/Type/Cache.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Cache.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Cache
+  ( Cache(..)
+  )
+where
+
+import Rattletrap.Type.AttributeMapping
+import Rattletrap.Type.Common
+import Rattletrap.Type.List
+import Rattletrap.Type.Word32le
+
+data Cache = Cache
+  { cacheClassId :: Word32le
+  , cacheParentCacheId :: Word32le
+  , cacheCacheId :: Word32le
+  , cacheAttributeMappings :: List AttributeMapping
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Cache)
diff --git a/source/library/Rattletrap/Type/CamSettingsAttribute.hs b/source/library/Rattletrap/Type/CamSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/CamSettingsAttribute.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.CamSettingsAttribute
+  ( CamSettingsAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Float32le
+
+data CamSettingsAttribute = CamSettingsAttribute
+  { camSettingsAttributeFov :: Float32le
+  , camSettingsAttributeHeight :: Float32le
+  , camSettingsAttributeAngle :: Float32le
+  , camSettingsAttributeDistance :: Float32le
+  , camSettingsAttributeStiffness :: Float32le
+  , camSettingsAttributeSwivelSpeed :: Float32le
+  , camSettingsAttributeTransitionSpeed :: Maybe Float32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''CamSettingsAttribute)
diff --git a/source/library/Rattletrap/Type/ClassAttributeMap.hs b/source/library/Rattletrap/Type/ClassAttributeMap.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ClassAttributeMap.hs
@@ -0,0 +1,324 @@
+module Rattletrap.Type.ClassAttributeMap
+  ( ClassAttributeMap(..)
+  , classHasLocation
+  , classHasRotation
+  , getAttributeIdLimit
+  , getAttributeMap
+  , getAttributeName
+  , getClassName
+  , getName
+  , getObjectName
+  , makeClassAttributeMap
+  )
+where
+
+import Rattletrap.Data
+import Rattletrap.Type.AttributeMapping
+import Rattletrap.Type.Cache
+import Rattletrap.Type.ClassMapping
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.List
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+import qualified Data.IntMap as IntMap
+import qualified Data.List as List
+import qualified Data.Map as Map
+import qualified Data.Maybe as Maybe
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+import qualified Data.Tuple as Tuple
+
+-- | This data structure holds all the information about classes, objects, and
+-- attributes in the replay. The class hierarchy is not fixed; it is encoded
+-- in the 'Rattletrap.Content.Content'. Similarly, the attributes that belong
+-- to each class are not fixed either. Converting the raw data into a usable
+-- structure is tedious; see 'makeClassAttributeMap'.
+data ClassAttributeMap = ClassAttributeMap
+  { classAttributeMapObjectMap :: Map Word32le Str
+  -- ^ A map from object IDs to their names.
+  , classAttributeMapObjectClassMap :: Map Word32le Word32le
+  -- ^ A map from object IDs to their class IDs.
+    , classAttributeMapValue :: Map Word32le (Map Word32le Word32le)
+  -- ^ A map from class IDs to a map from attribute stream IDs to attribute
+  -- IDs.
+  , classAttributeMapNameMap :: IntMap.IntMap Str
+  } deriving (Eq, Ord, Show)
+
+type Bimap l r = (Map l r, Map r l)
+
+bimap :: (Ord l, Ord r) => [(l, r)] -> Bimap l r
+bimap xs = (Map.fromList xs, Map.fromList (fmap Tuple.swap xs))
+
+lookupL :: Ord l => l -> Bimap l r -> Maybe r
+lookupL k = Map.lookup k . fst
+
+lookupR :: Ord r => r -> Bimap l r -> Maybe l
+lookupR k = Map.lookup k . snd
+
+-- | Makes a 'ClassAttributeMap' given the necessary fields from the
+-- 'Rattletrap.Content.Content'.
+makeClassAttributeMap
+  :: List Str
+  -- ^ From 'Rattletrap.Content.contentObjects'.
+  -> List ClassMapping
+  -- ^ From 'Rattletrap.Content.contentClassMappings'.
+  -> List Cache
+  -- ^ From 'Rattletrap.Content.contentCaches'.
+  -> List Str
+  -- ^ From 'Rattletrap.Content.contentNames'.
+  -> ClassAttributeMap
+makeClassAttributeMap objects classMappings caches names =
+  let
+    objectMap = makeObjectMap objects
+    classMap = makeClassMap classMappings
+    objectClassMap = makeObjectClassMap objectMap classMap
+    classCache = makeClassCache classMap caches
+    attributeMap = makeAttributeMap caches
+    classIds = fmap (\(_, classId, _, _) -> classId) classCache
+    parentMap = makeParentMap classCache
+    value = Map.fromList
+      (fmap
+        (\classId ->
+          let
+            ownAttributes =
+              Maybe.fromMaybe Map.empty (Map.lookup classId attributeMap)
+            parentsAttributes = case Map.lookup classId parentMap of
+              Nothing -> []
+              Just parentClassIds -> fmap
+                (\parentClassId -> Maybe.fromMaybe
+                  Map.empty
+                  (Map.lookup parentClassId attributeMap)
+                )
+                parentClassIds
+            attributes = ownAttributes : parentsAttributes
+          in (classId, Map.fromList (concatMap Map.toList attributes))
+        )
+        classIds
+      )
+    nameMap = makeNameMap names
+  in ClassAttributeMap objectMap objectClassMap value nameMap
+
+makeNameMap :: List Str -> IntMap.IntMap Str
+makeNameMap names = IntMap.fromDistinctAscList (zip [0 ..] (listValue names))
+
+getName :: IntMap.IntMap Str -> Word32le -> Maybe Str
+getName nameMap nameIndex =
+  IntMap.lookup (fromIntegral (word32leValue nameIndex)) nameMap
+
+makeObjectClassMap
+  :: Map Word32le Str -> Bimap Word32le Str -> Map Word32le Word32le
+makeObjectClassMap objectMap classMap = do
+  let objectIds = Map.keys objectMap
+  let classIds = fmap (getClassId objectMap classMap) objectIds
+  let rawPairs = zip objectIds classIds
+  let
+    pairs = Maybe.mapMaybe
+      (\(objectId, maybeClassId) -> case maybeClassId of
+        Nothing -> Nothing
+        Just classId -> Just (objectId, classId)
+      )
+      rawPairs
+  Map.fromList pairs
+
+getClassId
+  :: Map Word32le Str -> Bimap Word32le Str -> Word32le -> Maybe Word32le
+getClassId objectMap classMap objectId = do
+  objectName <- getObjectName objectMap objectId
+  className <- getClassName objectName
+  lookupR className classMap
+
+makeClassCache
+  :: Bimap Word32le Str
+  -> List Cache
+  -> [(Maybe Str, Word32le, Word32le, Word32le)]
+makeClassCache classMap caches = fmap
+  (\cache ->
+    let classId = cacheClassId cache
+    in
+      ( lookupL classId classMap
+      , classId
+      , cacheCacheId cache
+      , cacheParentCacheId cache
+      )
+  )
+  (listValue caches)
+
+makeClassMap :: List ClassMapping -> Bimap Word32le Str
+makeClassMap classMappings = bimap
+  (fmap
+    (\classMapping ->
+      (classMappingStreamId classMapping, classMappingName classMapping)
+    )
+    (listValue classMappings)
+  )
+
+makeAttributeMap :: List Cache -> Map Word32le (Map Word32le Word32le)
+makeAttributeMap caches = Map.fromList
+  (fmap
+    (\cache ->
+      ( cacheClassId cache
+      , Map.fromList
+        (fmap
+          (\attributeMapping ->
+            ( attributeMappingStreamId attributeMapping
+            , attributeMappingObjectId attributeMapping
+            )
+          )
+          (listValue (cacheAttributeMappings cache))
+        )
+      )
+    )
+    (listValue caches)
+  )
+
+makeShallowParentMap
+  :: [(Maybe Str, Word32le, Word32le, Word32le)] -> Map Word32le Word32le
+makeShallowParentMap classCache = Map.fromList
+  (Maybe.mapMaybe
+    (\xs -> case xs of
+      [] -> Nothing
+      (maybeClassName, classId, _, parentCacheId) : rest -> do
+        parentClassId <- getParentClass maybeClassName parentCacheId rest
+        pure (classId, parentClassId)
+    )
+    (List.tails (reverse classCache))
+  )
+
+makeParentMap
+  :: [(Maybe Str, Word32le, Word32le, Word32le)] -> Map Word32le [Word32le]
+makeParentMap classCache =
+  let shallowParentMap = makeShallowParentMap classCache
+  in
+    Map.mapWithKey
+      (\classId _ -> getParentClasses shallowParentMap classId)
+      shallowParentMap
+
+getParentClasses :: Map Word32le Word32le -> Word32le -> [Word32le]
+getParentClasses shallowParentMap classId =
+  case Map.lookup classId shallowParentMap of
+    Nothing -> []
+    Just parentClassId ->
+      parentClassId : getParentClasses shallowParentMap parentClassId
+
+getParentClass
+  :: Maybe Str
+  -> Word32le
+  -> [(Maybe Str, Word32le, Word32le, Word32le)]
+  -> Maybe Word32le
+getParentClass maybeClassName parentCacheId xs = case maybeClassName of
+  Nothing -> getParentClassById parentCacheId xs
+  Just className -> getParentClassByName className parentCacheId xs
+
+getParentClassById
+  :: Word32le -> [(Maybe Str, Word32le, Word32le, Word32le)] -> Maybe Word32le
+getParentClassById parentCacheId xs =
+  case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of
+    [] -> if parentCacheId == Word32le 0
+      then Nothing
+      else getParentClassById (Word32le (word32leValue parentCacheId - 1)) xs
+    (_, parentClassId, _, _) : _ -> Just parentClassId
+
+getParentClassByName
+  :: Str
+  -> Word32le
+  -> [(Maybe Str, Word32le, Word32le, Word32le)]
+  -> Maybe Word32le
+getParentClassByName className parentCacheId xs =
+  case Map.lookup className parentClasses of
+    Nothing -> getParentClassById parentCacheId xs
+    Just parentClassName -> Maybe.maybe
+      (getParentClassById parentCacheId xs)
+      Just
+      (Maybe.listToMaybe
+        (fmap
+          (\(_, parentClassId, _, _) -> parentClassId)
+          (filter
+            (\(_, _, cacheId, _) -> cacheId <= parentCacheId)
+            (filter
+              (\(maybeClassName, _, _, _) ->
+                maybeClassName == Just parentClassName
+              )
+              xs
+            )
+          )
+        )
+      )
+
+parentClasses :: Map Str Str
+parentClasses =
+  Map.map toStr (Map.mapKeys toStr (Map.fromList rawParentClasses))
+
+makeObjectMap :: List Str -> Map Word32le Str
+makeObjectMap objects =
+  Map.fromAscList (zip (fmap Word32le [0 ..]) (listValue objects))
+
+getObjectName :: Map Word32le Str -> Word32le -> Maybe Str
+getObjectName objectMap objectId = Map.lookup objectId objectMap
+
+getClassName :: Str -> Maybe Str
+getClassName rawObjectName =
+  Map.lookup (normalizeObjectName rawObjectName) objectClasses
+
+normalizeObjectName :: Str -> Str
+normalizeObjectName objectName =
+  let
+    name = strValue objectName
+    crowdActor = Text.pack "TheWorld:PersistentLevel.CrowdActor_TA"
+    crowdManager = Text.pack "TheWorld:PersistentLevel.CrowdManager_TA"
+    boostPickup = Text.pack "TheWorld:PersistentLevel.VehiclePickup_Boost_TA"
+    mapScoreboard = Text.pack "TheWorld:PersistentLevel.InMapScoreboard_TA"
+    breakout = Text.pack "TheWorld:PersistentLevel.BreakOutActor_Platform_TA"
+  in if Text.isInfixOf crowdActor name
+    then Str crowdActor
+    else if Text.isInfixOf crowdManager name
+      then Str crowdManager
+      else if Text.isInfixOf boostPickup name
+        then Str boostPickup
+        else if Text.isInfixOf mapScoreboard name
+          then Str mapScoreboard
+          else if Text.isInfixOf breakout name
+            then Str breakout
+            else objectName
+
+objectClasses :: Map Str Str
+objectClasses =
+  Map.map toStr (Map.mapKeys toStr (Map.fromList rawObjectClasses))
+
+classHasLocation :: Str -> Bool
+classHasLocation className = Set.member className classesWithLocation
+
+classesWithLocation :: Set.Set Str
+classesWithLocation = Set.fromList (fmap toStr rawClassesWithLocation)
+
+classHasRotation :: Str -> Bool
+classHasRotation className = Set.member className classesWithRotation
+
+classesWithRotation :: Set.Set Str
+classesWithRotation = Set.fromList (fmap toStr rawClassesWithRotation)
+
+getAttributeIdLimit :: Map Word32le Word32le -> Maybe Word
+getAttributeIdLimit attributeMap = do
+  ((streamId, _), _) <- Map.maxViewWithKey attributeMap
+  pure (fromIntegral (word32leValue streamId))
+
+getAttributeName
+  :: ClassAttributeMap -> Map Word32le Word32le -> CompressedWord -> Maybe Str
+getAttributeName classAttributeMap attributeMap streamId = do
+  let key = Word32le (fromIntegral (compressedWordValue streamId))
+  attributeId <- Map.lookup key attributeMap
+  let objectMap = classAttributeMapObjectMap classAttributeMap
+  Map.lookup attributeId objectMap
+
+getAttributeMap
+  :: ClassAttributeMap
+  -> Map CompressedWord Word32le
+  -> CompressedWord
+  -> Maybe (Map Word32le Word32le)
+getAttributeMap classAttributeMap actorMap actorId = do
+  objectId <- Map.lookup actorId actorMap
+  let objectClassMap = classAttributeMapObjectClassMap classAttributeMap
+  classId <- Map.lookup objectId objectClassMap
+  let value = classAttributeMapValue classAttributeMap
+  Map.lookup classId value
diff --git a/source/library/Rattletrap/Type/ClassMapping.hs b/source/library/Rattletrap/Type/ClassMapping.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ClassMapping.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ClassMapping
+  ( ClassMapping(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data ClassMapping = ClassMapping
+  { classMappingName :: Str
+  , classMappingStreamId :: Word32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ClassMapping)
diff --git a/source/library/Rattletrap/Type/ClubColorsAttribute.hs b/source/library/Rattletrap/Type/ClubColorsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ClubColorsAttribute.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ClubColorsAttribute
+  ( ClubColorsAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word8le
+
+data ClubColorsAttribute = ClubColorsAttribute
+  { clubColorsAttributeBlueFlag :: Bool
+  , clubColorsAttributeBlueColor :: Word8le
+  , clubColorsAttributeOrangeFlag :: Bool
+  , clubColorsAttributeOrangeColor :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ClubColorsAttribute)
diff --git a/source/library/Rattletrap/Type/Common.hs b/source/library/Rattletrap/Type/Common.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Common.hs
@@ -0,0 +1,56 @@
+module Rattletrap.Type.Common
+  ( Int.Int8
+  , Int.Int32
+  , Int.Int64
+  , Map.Map
+  , Text.Text
+  , Word.Word8
+  , Word.Word16
+  , Word.Word32
+  , Word.Word64
+  , deriveJson
+  )
+where
+
+import qualified Data.Aeson as Json
+import qualified Data.Aeson.TH as Json
+import qualified Data.Char as Char
+import qualified Data.Int as Int
+import qualified Data.Map as Map
+import qualified Data.Maybe as Maybe
+import qualified Data.Text as Text
+import qualified Data.Word as Word
+import qualified Language.Haskell.TH as TH
+
+deriveJson :: TH.Name -> TH.Q [TH.Dec]
+deriveJson name = Json.deriveJSON (jsonOptions (TH.nameBase name)) name
+
+jsonOptions :: String -> Json.Options
+jsonOptions prefix = Json.defaultOptions
+  { Json.constructorTagModifier = toSnakeCase . partialDropPrefix prefix
+  , Json.fieldLabelModifier = toSnakeCase
+    . partialDropPrefix (lowerFirst prefix)
+  , Json.omitNothingFields = True
+  , Json.sumEncoding = Json.ObjectWithSingleField
+  , Json.unwrapUnaryRecords = True
+  }
+
+lowerFirst :: String -> String
+lowerFirst string = case string of
+  "" -> string
+  first : rest -> Char.toLower first : rest
+
+toSnakeCase :: String -> String
+toSnakeCase = Json.camelTo2 '_'
+
+partialDropPrefix :: (Eq a, Show a) => [a] -> [a] -> [a]
+partialDropPrefix prefix list = Maybe.fromMaybe
+  (error (unwords [show prefix, "is not a prefix of", show list]))
+  (dropPrefix prefix list)
+
+dropPrefix :: Eq a => [a] -> [a] -> Maybe [a]
+dropPrefix prefix list = case prefix of
+  [] -> Just list
+  ph : pt -> case list of
+    [] -> Nothing
+    lh : lt -> if ph == lh then dropPrefix pt lt else Nothing
diff --git a/source/library/Rattletrap/Type/CompressedWord.hs b/source/library/Rattletrap/Type/CompressedWord.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/CompressedWord.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.CompressedWord
+  ( CompressedWord(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+-- | Although there's no guarantee that these values will not overflow, it's
+-- exceptionally unlikely. Most 'CompressedWord's are very small.
+data CompressedWord = CompressedWord
+  { compressedWordLimit :: Word
+  , compressedWordValue :: Word
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''CompressedWord)
diff --git a/source/library/Rattletrap/Type/CompressedWordVector.hs b/source/library/Rattletrap/Type/CompressedWordVector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/CompressedWordVector.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.CompressedWordVector
+  ( CompressedWordVector(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+
+data CompressedWordVector = CompressedWordVector
+  { compressedWordVectorX :: CompressedWord
+  , compressedWordVectorY :: CompressedWord
+  , compressedWordVectorZ :: CompressedWord
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''CompressedWordVector)
diff --git a/source/library/Rattletrap/Type/Content.hs b/source/library/Rattletrap/Type/Content.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Content.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Content
+  ( Content(..)
+  , defaultContent
+  )
+where
+
+import Rattletrap.Type.Cache
+import Rattletrap.Type.ClassMapping
+import Rattletrap.Type.Common
+import Rattletrap.Type.Frame
+import Rattletrap.Type.KeyFrame
+import Rattletrap.Type.List
+import Rattletrap.Type.Mark
+import Rattletrap.Type.Message
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+-- | Contains low-level game data about a 'Rattletrap.Replay.Replay'.
+data Content = Content
+  { contentLevels :: List Str
+  -- ^ This typically only has one element, like @stadium_oob_audio_map@.
+  , contentKeyFrames :: List KeyFrame
+  -- ^ A list of which frames are key frames. Although they aren't necessary
+  -- for replay, key frames are frames that replicate every actor. They
+  -- typically happen once every 10 seconds.
+  , contentStreamSize :: Word32le
+  -- ^ The size of the stream in bytes. This is only really necessary because
+  -- the stream has some arbitrary amount of padding at the end.
+  , contentFrames :: [Frame]
+  -- ^ The actual game data. This is where all the interesting information is.
+  , contentMessages :: List Message
+  -- ^ Debugging messages. In newer replays, this is always empty.
+  , contentMarks :: List Mark
+  -- ^ Tick marks shown on the scrubber when watching a replay.
+  , contentPackages :: List Str
+  -- ^ A list of @.upk@ files to load, like
+  -- @..\\..\\TAGame\\CookedPCConsole\\Stadium_P.upk@.
+  , contentObjects :: List Str
+  -- ^ Objects in the stream. Used for the
+  -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
+  , contentNames :: List Str
+  -- ^ It's not clear what these are used for. This list is usually not empty,
+  -- but appears unused otherwise.
+  , contentClassMappings :: List ClassMapping
+  -- ^ A mapping between classes and their ID in the stream. Used for the
+  -- 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
+  , contentCaches :: List Cache
+  -- ^ A list of classes along with their parent classes and attributes. Used
+  -- for the 'Rattletrap.Type.ClassAttributeMap.ClassAttributeMap'.
+  , contentUnknown :: [Word8]
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Content)
+
+defaultContent :: Content
+defaultContent = Content
+  { contentLevels = List []
+  , contentKeyFrames = List []
+  , contentStreamSize = Word32le 0
+  , contentFrames = []
+  , contentMessages = List []
+  , contentMarks = List []
+  , contentPackages = List []
+  , contentObjects = List []
+  , contentNames = List []
+  , contentClassMappings = List []
+  , contentCaches = List []
+  , contentUnknown = []
+  }
diff --git a/source/library/Rattletrap/Type/DamageStateAttribute.hs b/source/library/Rattletrap/Type/DamageStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/DamageStateAttribute.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.DamageStateAttribute
+  ( DamageStateAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Vector
+import Rattletrap.Type.Word8le
+
+data DamageStateAttribute = DamageStateAttribute
+  { damageStateAttributeUnknown1 :: Word8le
+  , damageStateAttributeUnknown2 :: Bool
+  , damageStateAttributeUnknown3 :: Int32le
+  , damageStateAttributeUnknown4 :: Vector
+  , damageStateAttributeUnknown5 :: Bool
+  , damageStateAttributeUnknown6 :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''DamageStateAttribute)
diff --git a/source/library/Rattletrap/Type/DemolishAttribute.hs b/source/library/Rattletrap/Type/DemolishAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/DemolishAttribute.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.DemolishAttribute
+  ( DemolishAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Vector
+import Rattletrap.Type.Word32le
+
+data DemolishAttribute = DemolishAttribute
+  { demolishAttributeAttackerFlag :: Bool
+  , demolishAttributeAttackerActorId :: Word32le
+  , demolishAttributeVictimFlag :: Bool
+  , demolishAttributeVictimActorId :: Word32le
+  , demolishAttributeAttackerVelocity :: Vector
+  , demolishAttributeVictimVelocity :: Vector
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''DemolishAttribute)
diff --git a/source/library/Rattletrap/Type/DestroyedReplication.hs b/source/library/Rattletrap/Type/DestroyedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/DestroyedReplication.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.DestroyedReplication
+  ( DestroyedReplication(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+-- | Destroyed replications don't actually contain any extra information. All
+-- you need to know is the actor's ID, which is given by the
+-- 'Rattletrap.Replication.Replication'.
+data DestroyedReplication = DestroyedReplication
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''DestroyedReplication)
diff --git a/source/library/Rattletrap/Type/Dictionary.hs b/source/library/Rattletrap/Type/Dictionary.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Dictionary.hs
@@ -0,0 +1,69 @@
+module Rattletrap.Type.Dictionary
+  ( Dictionary(..)
+  , dictionaryLookup
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+
+import qualified Control.Monad as Monad
+import qualified Data.Aeson as Json
+import qualified Data.Aeson.Types as Json
+import qualified Data.Map as Map
+import qualified Data.Text as Text
+
+data Dictionary a
+  = DictionaryElement Str a (Dictionary a)
+  | DictionaryEnd Str
+  deriving (Eq, Ord, Show)
+
+instance Json.FromJSON a => Json.FromJSON (Dictionary a) where
+  parseJSON = Json.withObject
+    "Dictionary"
+    (\o -> do
+      keys <- get o "keys"
+      lastKey <- get o "last_key"
+      value <- get o "value"
+      Monad.foldM
+        (\d k -> case Map.lookup k value of
+          Nothing -> fail (unwords ["missing key", show k])
+          Just v -> pure (DictionaryElement (Str k) v d)
+        )
+        (DictionaryEnd lastKey)
+        (reverse keys)
+    )
+
+instance Json.ToJSON a => Json.ToJSON (Dictionary a) where
+  toJSON d = Json.object
+    [ pair "keys" (dictionaryKeys d)
+    , pair "last_key" (dictionaryLastKey d)
+    , pair "value" (dictionaryValue d)
+    ]
+
+dictionaryKeys :: Dictionary a -> [Str]
+dictionaryKeys = fmap fst . toList
+
+dictionaryLastKey :: Dictionary a -> Str
+dictionaryLastKey x = case x of
+  DictionaryElement _ _ y -> dictionaryLastKey y
+  DictionaryEnd y -> y
+
+dictionaryLookup :: Str -> Dictionary a -> Maybe a
+dictionaryLookup k x = case x of
+  DictionaryElement j v y -> if k == j then Just v else dictionaryLookup k y
+  DictionaryEnd _ -> Nothing
+
+dictionaryValue :: Dictionary a -> Map Text a
+dictionaryValue = Map.mapKeys strValue . Map.fromList . toList
+
+get :: Json.FromJSON a => Json.Object -> String -> Json.Parser a
+get o k = o Json..: Text.pack k
+
+pair :: Json.ToJSON a => String -> a -> (Text, Json.Value)
+pair k v = (Text.pack k, Json.toJSON v)
+
+toList :: Dictionary a -> [(Str, a)]
+toList x = case x of
+  DictionaryElement k v y -> (k, v) : toList y
+  DictionaryEnd _ -> []
diff --git a/source/library/Rattletrap/Type/EnumAttribute.hs b/source/library/Rattletrap/Type/EnumAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/EnumAttribute.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.EnumAttribute
+  ( EnumAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype EnumAttribute = EnumAttribute
+  { enumAttributeValue :: Word16
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''EnumAttribute)
diff --git a/source/library/Rattletrap/Type/ExplosionAttribute.hs b/source/library/Rattletrap/Type/ExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ExplosionAttribute.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ExplosionAttribute
+  ( ExplosionAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Vector
+
+data ExplosionAttribute = ExplosionAttribute
+  { explosionAttributeFlag :: Bool
+  , explosionAttributeActorId :: Int32le
+  , explosionAttributeLocation :: Vector
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ExplosionAttribute)
diff --git a/source/library/Rattletrap/Type/ExtendedExplosionAttribute.hs b/source/library/Rattletrap/Type/ExtendedExplosionAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ExtendedExplosionAttribute.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ExtendedExplosionAttribute
+  ( ExtendedExplosionAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.ExplosionAttribute
+import Rattletrap.Type.FlaggedIntAttribute
+
+data ExtendedExplosionAttribute = ExtendedExplosionAttribute
+  { extendedExplosionAttributeExplosion :: ExplosionAttribute
+  , extendedExplosionAttributeUnknown :: FlaggedIntAttribute
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ExtendedExplosionAttribute)
diff --git a/source/library/Rattletrap/Type/FlaggedByteAttribute.hs b/source/library/Rattletrap/Type/FlaggedByteAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/FlaggedByteAttribute.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.FlaggedByteAttribute
+  ( FlaggedByteAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word8le
+
+data FlaggedByteAttribute = FlaggedByteAttribute
+  { flaggedByteAttributeFlag :: Bool
+  , flaggedByteAttributeByte :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''FlaggedByteAttribute)
diff --git a/source/library/Rattletrap/Type/FlaggedIntAttribute.hs b/source/library/Rattletrap/Type/FlaggedIntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/FlaggedIntAttribute.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.FlaggedIntAttribute
+  ( FlaggedIntAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+
+data FlaggedIntAttribute = FlaggedIntAttribute
+  { flaggedIntAttributeFlag :: Bool
+  , flaggedIntAttributeInt :: Int32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''FlaggedIntAttribute)
diff --git a/source/library/Rattletrap/Type/Float32le.hs b/source/library/Rattletrap/Type/Float32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Float32le.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Float32le
+  ( Float32le(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype Float32le = Float32le
+  { float32leValue :: Float
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Float32le)
diff --git a/source/library/Rattletrap/Type/FloatAttribute.hs b/source/library/Rattletrap/Type/FloatAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/FloatAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.FloatAttribute
+  ( FloatAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Float32le
+
+newtype FloatAttribute = FloatAttribute
+  { floatAttributeValue :: Float32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''FloatAttribute)
diff --git a/source/library/Rattletrap/Type/Frame.hs b/source/library/Rattletrap/Type/Frame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Frame.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Frame
+  ( Frame(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Float32le
+import Rattletrap.Type.Replication
+
+data Frame = Frame
+  { frameTime :: Float32le
+  -- ^ Time in seconds since the beginning of the match.
+  , frameDelta :: Float32le
+  -- ^ Time in seconds since the last frame. Usually about 0.03 since there
+  -- are 30 frames per second.
+  , frameReplications :: [Replication]
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Frame)
diff --git a/source/library/Rattletrap/Type/GameModeAttribute.hs b/source/library/Rattletrap/Type/GameModeAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/GameModeAttribute.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.GameModeAttribute
+  ( GameModeAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+data GameModeAttribute = GameModeAttribute
+  { gameModeAttributeNumBits :: Int
+  -- ^ This field is guaranteed to be small. In other words, it won't overflow.
+  -- It's stored as a regular 'Int' rather than something more precise like an
+  -- 'Int8' because it just gets passed to functions that expect 'Int's.
+  -- There's no reason to do a bunch of conversions.
+  , gameModeAttributeWord :: Word8
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''GameModeAttribute)
diff --git a/source/library/Rattletrap/Type/Header.hs b/source/library/Rattletrap/Type/Header.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Header.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Header
+  ( Header(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Dictionary
+import Rattletrap.Type.Property
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+-- | Contains high-level metadata about a 'Rattletrap.Replay.Replay'.
+data Header = Header
+  { headerEngineVersion :: Word32le
+  -- ^ The "major" ("engine") version number.
+  , headerLicenseeVersion :: Word32le
+  -- ^ The "minor" ("licensee") version number.
+  , headerPatchVersion :: Maybe Word32le
+  -- ^ The "patch" ("net") version number.
+  , headerLabel :: Str
+  -- ^ Always @TAGame.Replay_Soccar_TA@.
+  , headerProperties :: Dictionary Property
+  -- ^ These properties determine how a replay will look in the list of
+  -- replays in-game. One element is required for the replay to show up:
+  --
+  -- - MapName: This is a 'Rattletrap.PropertyValue.NameProperty' with a
+  --   case-insensitive map identifier, like @Stadium_P@.
+  --
+  -- There are many other properties that affect how the replay looks in the
+  -- list of replays.
+  --
+  -- - Date: A 'Rattletrap.PropertyValue.StrProperty' with the format
+  --   @YYYY-mm-dd:HH-MM@. Dates are not validated, but the month must be
+  --   between 1 and 12 to show up. The hour is shown modulo 12 with AM or PM.
+  -- - MatchType: A 'Rattletrap.PropertyValue.NameProperty'. If this is not
+  --   one of the expected values, nothing will be shown next to the replay's
+  --   map. The expected values are: @Online@, @Offline@, @Private@, and
+  --   @Season@.
+  -- - NumFrames: This 'Rattletrap.PropertyValue.IntProperty' is used to
+  --   calculate the length of the match. There are 30 frames per second,
+  --   a typical 5-minute match has about 9,000 frames.
+  -- - PrimaryPlayerTeam: This is an 'Rattletrap.PropertyValue.IntProperty'.
+  --   It is either 0 (blue) or 1 (orange). Any other value is ignored. If
+  --   this would be 0, you don't have to set it at all.
+  -- - ReplayName: An optional 'Rattletrap.PropertyValue.StrProperty' with a
+  --   user-supplied name for the replay.
+  -- - Team0Score: The blue team's score as an
+  --   'Rattletrap.PropertyValue.IntProperty'. Can be omitted if the score is
+  --   0.
+  -- - Team1Score: The orange team's score as an
+  --   'Rattletrap.PropertyValue.IntProperty'. Can also be omitted if the
+  --   score is 0.
+  -- - TeamSize: An 'Rattletrap.PropertyValue.IntProperty' with the number of
+  --   players per team. This value is not validated, so you can put absurd
+  --   values like 99. To get an "unfair" team size like 1v4, you must set the
+  --   bUnfairBots 'Rattletrap.PropertyValue.BoolProperty' to @True@.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Header)
diff --git a/source/library/Rattletrap/Type/Initialization.hs b/source/library/Rattletrap/Type/Initialization.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Initialization.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Initialization
+  ( Initialization(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int8Vector
+import Rattletrap.Type.Vector
+
+data Initialization = Initialization
+  { initializationLocation :: Maybe Vector
+  -- ^ Not every class has an initial location. See
+  -- 'Rattletrap.Data.rawClassesWithLocation'.
+  , initializationRotation :: Maybe Int8Vector
+  -- ^ Only classes with location can have rotation, but not every one does.
+  -- See 'Rattletrap.Data.rawClassesWithRotation'.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Initialization)
diff --git a/source/library/Rattletrap/Type/Int32le.hs b/source/library/Rattletrap/Type/Int32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Int32le.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Int32le
+  ( Int32le(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype Int32le = Int32le
+  { int32leValue :: Int32
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Int32le)
diff --git a/source/library/Rattletrap/Type/Int64Attribute.hs b/source/library/Rattletrap/Type/Int64Attribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Int64Attribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Int64Attribute
+  ( Int64Attribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int64le
+
+newtype Int64Attribute = Int64Attribute
+  { int64AttributeValue :: Int64le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Int64Attribute)
diff --git a/source/library/Rattletrap/Type/Int64le.hs b/source/library/Rattletrap/Type/Int64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Int64le.hs
@@ -0,0 +1,28 @@
+module Rattletrap.Type.Int64le
+  ( Int64le(..)
+  )
+where
+
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+import qualified Data.Int as Int
+import qualified Data.Scientific as Scientific
+import qualified Data.Text as Text
+import qualified Text.Read as Read
+
+newtype Int64le = Int64le
+  { int64leValue :: Int.Int64
+  } deriving (Eq, Ord, Show)
+
+instance Aeson.FromJSON Int64le where
+  parseJSON value = case value of
+    Aeson.String text -> case Read.readEither $ Text.unpack text of
+      Left _ -> Aeson.typeMismatch "Int64le" value
+      Right int64 -> pure $ Int64le int64
+    Aeson.Number number -> case Scientific.toBoundedInteger number of
+      Nothing -> Aeson.typeMismatch "Int64le" value
+      Just int64 -> pure $ Int64le int64
+    _ -> Aeson.typeMismatch "Int64le" value
+
+instance Aeson.ToJSON Int64le where
+  toJSON = Aeson.toJSON . show . int64leValue
diff --git a/source/library/Rattletrap/Type/Int8Vector.hs b/source/library/Rattletrap/Type/Int8Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Int8Vector.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Int8Vector
+  ( Int8Vector(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int8le
+
+data Int8Vector = Int8Vector
+  { int8VectorX :: Maybe Int8le
+  , int8VectorY :: Maybe Int8le
+  , int8VectorZ :: Maybe Int8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Int8Vector)
diff --git a/source/library/Rattletrap/Type/Int8le.hs b/source/library/Rattletrap/Type/Int8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Int8le.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Int8le
+  ( Int8le(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype Int8le = Int8le
+  { int8leValue :: Int8
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Int8le)
diff --git a/source/library/Rattletrap/Type/IntAttribute.hs b/source/library/Rattletrap/Type/IntAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/IntAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.IntAttribute
+  ( IntAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+
+newtype IntAttribute = IntAttribute
+  { intAttributeValue :: Int32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''IntAttribute)
diff --git a/source/library/Rattletrap/Type/KeyFrame.hs b/source/library/Rattletrap/Type/KeyFrame.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/KeyFrame.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.KeyFrame
+  ( KeyFrame(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Float32le
+import Rattletrap.Type.Word32le
+
+data KeyFrame = KeyFrame
+  { keyFrameTime :: Float32le
+  -- ^ When this key frame occurs, in seconds.
+  , keyFrameFrame :: Word32le
+  -- ^ The frame number of this key frame, starting from 0.
+  , keyFramePosition :: Word32le
+  -- ^ The bit position of this key frame in the stream.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''KeyFrame)
diff --git a/source/library/Rattletrap/Type/List.hs b/source/library/Rattletrap/Type/List.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/List.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.List
+  ( List(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype List a = List
+  { listValue :: [a]
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''List)
diff --git a/source/library/Rattletrap/Type/LoadoutAttribute.hs b/source/library/Rattletrap/Type/LoadoutAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/LoadoutAttribute.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.LoadoutAttribute
+  ( LoadoutAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+import Rattletrap.Type.Word8le
+
+data LoadoutAttribute = LoadoutAttribute
+  { loadoutAttributeVersion :: Word8le
+  , loadoutAttributeBody :: Word32le
+  , loadoutAttributeDecal :: Word32le
+  , loadoutAttributeWheels :: Word32le
+  , loadoutAttributeRocketTrail :: Word32le
+  -- ^ Now known as "rocket boost".
+  , loadoutAttributeAntenna :: Word32le
+  , loadoutAttributeTopper :: Word32le
+  , loadoutAttributeUnknown1 :: Word32le
+  , loadoutAttributeUnknown2 :: Maybe Word32le
+  , loadoutAttributeEngineAudio :: Maybe Word32le
+  , loadoutAttributeTrail :: Maybe Word32le
+  , loadoutAttributeGoalExplosion :: Maybe Word32le
+  , loadoutAttributeBanner :: Maybe Word32le
+  , loadoutAttributeUnknown3 :: Maybe Word32le
+  , loadoutAttributeUnknown4 :: Maybe Word32le
+  , loadoutAttributeUnknown5 :: Maybe Word32le
+  , loadoutAttributeUnknown6 :: Maybe Word32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''LoadoutAttribute)
diff --git a/source/library/Rattletrap/Type/LoadoutOnlineAttribute.hs b/source/library/Rattletrap/Type/LoadoutOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/LoadoutOnlineAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.LoadoutOnlineAttribute
+  ( LoadoutOnlineAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.ProductAttribute
+
+newtype LoadoutOnlineAttribute = LoadoutOnlineAttribute
+  { loadoutAttributeValue :: [[ProductAttribute]]
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''LoadoutOnlineAttribute)
diff --git a/source/library/Rattletrap/Type/LoadoutsAttribute.hs b/source/library/Rattletrap/Type/LoadoutsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/LoadoutsAttribute.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.LoadoutsAttribute
+  ( LoadoutsAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.LoadoutAttribute
+
+data LoadoutsAttribute = LoadoutsAttribute
+  { loadoutsAttributeBlue :: LoadoutAttribute
+  , loadoutsAttributeOrange :: LoadoutAttribute
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''LoadoutsAttribute)
diff --git a/source/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs b/source/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/LoadoutsOnlineAttribute.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.LoadoutsOnlineAttribute
+  ( LoadoutsOnlineAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.LoadoutOnlineAttribute
+
+data LoadoutsOnlineAttribute = LoadoutsOnlineAttribute
+  { loadoutsOnlineAttributeBlue :: LoadoutOnlineAttribute
+  , loadoutsOnlineAttributeOrange :: LoadoutOnlineAttribute
+  , loadoutsOnlineAttributeUnknown1 :: Bool
+  , loadoutsOnlineAttributeUnknown2 :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''LoadoutsOnlineAttribute)
diff --git a/source/library/Rattletrap/Type/LocationAttribute.hs b/source/library/Rattletrap/Type/LocationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/LocationAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.LocationAttribute
+  ( LocationAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Vector
+
+newtype LocationAttribute = LocationAttribute
+  { locationAttributeValue :: Vector
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''LocationAttribute)
diff --git a/source/library/Rattletrap/Type/Mark.hs b/source/library/Rattletrap/Type/Mark.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Mark.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Mark
+  ( Mark(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data Mark = Mark
+  { markValue :: Str
+  -- ^ Which type of mark this is, like @Team0Goal@.
+  , markFrame :: Word32le
+  -- ^ Which frame this mark belongs to, starting from 0.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Mark)
diff --git a/source/library/Rattletrap/Type/Message.hs b/source/library/Rattletrap/Type/Message.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Message.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Message
+  ( Message(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data Message = Message
+  { messageFrame :: Word32le
+  -- ^ Which frame this message belongs to, starting from 0.
+  , messageName :: Str
+  -- ^ The primary player's name.
+  , messageValue :: Str
+  -- ^ The content of the message.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Message)
diff --git a/source/library/Rattletrap/Type/MusicStingerAttribute.hs b/source/library/Rattletrap/Type/MusicStingerAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/MusicStingerAttribute.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.MusicStingerAttribute
+  ( MusicStingerAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+import Rattletrap.Type.Word8le
+
+data MusicStingerAttribute = MusicStingerAttribute
+  { musicStingerAttributeFlag :: Bool
+  , musicStingerAttributeCue :: Word32le
+  , musicStingerAttributeTrigger :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''MusicStingerAttribute)
diff --git a/source/library/Rattletrap/Type/PartyLeaderAttribute.hs b/source/library/Rattletrap/Type/PartyLeaderAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PartyLeaderAttribute.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PartyLeaderAttribute
+  ( PartyLeaderAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.RemoteId
+import Rattletrap.Type.Word8le
+
+data PartyLeaderAttribute = PartyLeaderAttribute
+  { partyLeaderAttributeSystemId :: Word8le
+  , partyLeaderAttributeId :: Maybe (RemoteId, Word8le)
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''PartyLeaderAttribute)
diff --git a/source/library/Rattletrap/Type/PickupAttribute.hs b/source/library/Rattletrap/Type/PickupAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PickupAttribute.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PickupAttribute
+  ( PickupAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+
+data PickupAttribute = PickupAttribute
+  { pickupAttributeInstigatorId :: Maybe Word32le
+  , pickupAttributePickedUp :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''PickupAttribute)
diff --git a/source/library/Rattletrap/Type/PickupAttributeNew.hs b/source/library/Rattletrap/Type/PickupAttributeNew.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PickupAttributeNew.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PickupAttributeNew
+  ( PickupAttributeNew(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word8le
+import Rattletrap.Type.Word32le
+
+data PickupAttributeNew = PickupAttributeNew
+  { pickupAttributeNewInstigatorId :: Maybe Word32le
+  , pickupAttributeNewPickedUp :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''PickupAttributeNew)
diff --git a/source/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs b/source/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PlayerHistoryKeyAttribute.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PlayerHistoryKeyAttribute
+  ( PlayerHistoryKeyAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype PlayerHistoryKeyAttribute = PlayerHistoryKeyAttribute
+  { playerHistoryKeyAttributeUnknown :: Word16
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''PlayerHistoryKeyAttribute)
diff --git a/source/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs b/source/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PrivateMatchSettingsAttribute.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PrivateMatchSettingsAttribute
+  ( PrivateMatchSettingsAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data PrivateMatchSettingsAttribute = PrivateMatchSettingsAttribute
+  { privateMatchSettingsAttributeMutators :: Str
+  , privateMatchSettingsAttributeJoinableBy :: Word32le
+  , privateMatchSettingsAttributeMaxPlayers :: Word32le
+  , privateMatchSettingsAttributeGameName :: Str
+  , privateMatchSettingsAttributePassword :: Str
+  , privateMatchSettingsAttributeFlag :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''PrivateMatchSettingsAttribute)
diff --git a/source/library/Rattletrap/Type/ProductAttribute.hs b/source/library/Rattletrap/Type/ProductAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ProductAttribute.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ProductAttribute
+  ( ProductAttribute(..)
+  , ProductAttributeValue(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data ProductAttributeValue
+  = ProductAttributeValuePaintedOld CompressedWord
+  | ProductAttributeValuePaintedNew Word32
+  | ProductAttributeValueTeamEditionOld CompressedWord
+  | ProductAttributeValueTeamEditionNew Word32
+  | ProductAttributeValueSpecialEdition Word32
+  | ProductAttributeValueUserColorOld (Maybe Word32)
+  | ProductAttributeValueUserColorNew Word32le
+  | ProductAttributeValueTitleId Str
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''ProductAttributeValue)
+
+data ProductAttribute = ProductAttribute
+  { productAttributeUnknown :: Bool
+  , productAttributeObjectId :: Word32le
+  , productAttributeObjectName :: Maybe Str
+  -- ^ read-only
+  , productAttributeValue :: ProductAttributeValue
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ProductAttribute)
diff --git a/source/library/Rattletrap/Type/Property.hs b/source/library/Rattletrap/Type/Property.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Property.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Property
+  ( Property(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.PropertyValue
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word64le
+
+data Property = Property
+  { propertyKind :: Str
+  , propertySize :: Word64le
+  -- ^ Not used.
+  , propertyValue :: PropertyValue Property
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Property)
diff --git a/source/library/Rattletrap/Type/PropertyValue.hs b/source/library/Rattletrap/Type/PropertyValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/PropertyValue.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.PropertyValue
+  ( PropertyValue(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Dictionary
+import Rattletrap.Type.Float32le
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.List
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word64le
+import Rattletrap.Type.Word8le
+
+data PropertyValue a
+  = PropertyValueArray (List (Dictionary a))
+  -- ^ Yes, a list of dictionaries. No, it doesn't make sense. These usually
+  -- only have one element.
+  | PropertyValueBool Word8le
+  | PropertyValueByte Str (Maybe Str)
+  -- ^ This is a strange name for essentially a key-value pair.
+  | PropertyValueFloat Float32le
+  | PropertyValueInt Int32le
+  | PropertyValueName Str
+  -- ^ It's unclear how exactly this is different than a 'StrProperty'.
+  | PropertyValueQWord Word64le
+  | PropertyValueStr Str
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''PropertyValue)
diff --git a/source/library/Rattletrap/Type/QWordAttribute.hs b/source/library/Rattletrap/Type/QWordAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/QWordAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.QWordAttribute
+  ( QWordAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word64le
+
+newtype QWordAttribute = QWordAttribute
+  { qWordAttributeValue :: Word64le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''QWordAttribute)
diff --git a/source/library/Rattletrap/Type/Quaternion.hs b/source/library/Rattletrap/Type/Quaternion.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Quaternion.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Quaternion
+  ( Quaternion(..)
+  , Component(..)
+  , toQuaternion
+  , compressPart
+  , decompressPart
+  , maxComponent
+  , maxCompressedValue
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+
+import qualified Data.Maybe as Maybe
+
+data Quaternion = Quaternion
+  { quaternionX :: Double
+  , quaternionY :: Double
+  , quaternionZ :: Double
+  , quaternionW :: Double
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Quaternion)
+
+data Component
+  = ComponentX
+  | ComponentY
+  | ComponentZ
+  | ComponentW
+  deriving (Eq, Ord, Show)
+
+toQuaternion :: Component -> Double -> Double -> Double -> Quaternion
+toQuaternion component a b c =
+  let d = toPart a b c
+  in
+    case component of
+      ComponentX -> Quaternion d a b c
+      ComponentY -> Quaternion a d b c
+      ComponentZ -> Quaternion a b d c
+      ComponentW -> Quaternion a b c d
+
+toPart :: Double -> Double -> Double -> Double
+toPart a b c = sqrt (1 - (a * a) - (b * b) - (c * c))
+
+compressPart :: Double -> CompressedWord
+compressPart =
+  CompressedWord maxCompressedValue
+    . round
+    . (* wordToDouble maxCompressedValue)
+    . (+ 0.5)
+    . (/ 2.0)
+    . (/ maxValue)
+
+decompressPart :: CompressedWord -> Double
+decompressPart x =
+  (* maxValue)
+    . (* 2.0)
+    . subtract 0.5
+    . (/ wordToDouble (compressedWordLimit x))
+    . wordToDouble
+    $ compressedWordValue x
+
+maxComponent :: Quaternion -> Component
+maxComponent quaternion =
+  let
+    x = quaternionX quaternion
+    y = quaternionY quaternion
+    z = quaternionZ quaternion
+    w = quaternionW quaternion
+    parts =
+      [(x, ComponentX), (y, ComponentY), (z, ComponentZ), (w, ComponentW)]
+    biggestPart = maximum parts
+    roundTrip = decompressPart . compressPart
+    computedPart = Maybe.fromMaybe
+      biggestPart
+      (Maybe.listToMaybe
+        (filter (\(value, _) -> value /= roundTrip value) parts)
+      )
+  in snd
+    (if (biggestPart == computedPart)
+        || (abs (fst biggestPart - fst computedPart) > 0.00001)
+      then biggestPart
+      else computedPart
+    )
+
+numBits :: Word
+numBits = 18
+
+wordToDouble :: Word -> Double
+wordToDouble = fromIntegral
+
+maxCompressedValue :: Word
+maxCompressedValue = (2 ^ numBits) - 1
+
+maxValue :: Double
+maxValue = 1.0 / sqrt 2.0
diff --git a/source/library/Rattletrap/Type/RemoteId.hs b/source/library/Rattletrap/Type/RemoteId.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/RemoteId.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.RemoteId
+  ( RemoteId(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word64le
+
+data RemoteId
+  = RemoteIdPlayStation Text [Word8]
+  | RemoteIdPsyNet (Either Word64le (Word64le, Word64le, Word64le, Word64le))
+  | RemoteIdSplitscreen Word32
+  -- ^ Really only 24 bits.
+  | RemoteIdSteam Word64le
+  | RemoteIdSwitch Word64le Word64le Word64le Word64le
+  | RemoteIdXbox Word64le
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''RemoteId)
diff --git a/source/library/Rattletrap/Type/Replay.hs b/source/library/Rattletrap/Type/Replay.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Replay.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Replay
+  ( Replay(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Content
+import Rattletrap.Type.Header
+import Rattletrap.Type.Section
+
+-- | A Rocket League replay.
+data Replay = Replay
+  { replayHeader :: Section Header
+  -- ^ This has most of the high-level metadata.
+  , replayContent :: Section Content
+  -- ^ This has most of the low-level game data.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Replay)
diff --git a/source/library/Rattletrap/Type/Replication.hs b/source/library/Rattletrap/Type/Replication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Replication.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Replication
+  ( Replication(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.ReplicationValue
+
+data Replication = Replication
+  { replicationActorId :: CompressedWord
+  , replicationValue :: ReplicationValue
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Replication)
diff --git a/source/library/Rattletrap/Type/ReplicationValue.hs b/source/library/Rattletrap/Type/ReplicationValue.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ReplicationValue.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ReplicationValue
+  ( ReplicationValue(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.DestroyedReplication
+import Rattletrap.Type.SpawnedReplication
+import Rattletrap.Type.UpdatedReplication
+
+data ReplicationValue
+  = ReplicationValueSpawned SpawnedReplication
+  -- ^ Creates a new actor.
+  | ReplicationValueUpdated UpdatedReplication
+  -- ^ Updates an existing actor.
+  | ReplicationValueDestroyed DestroyedReplication
+  -- ^ Destroys an existing actor.
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''ReplicationValue)
diff --git a/source/library/Rattletrap/Type/ReservationAttribute.hs b/source/library/Rattletrap/Type/ReservationAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/ReservationAttribute.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.ReservationAttribute
+  ( ReservationAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+import Rattletrap.Type.Str
+import Rattletrap.Type.UniqueIdAttribute
+
+data ReservationAttribute = ReservationAttribute
+  { reservationAttributeNumber :: CompressedWord
+  , reservationAttributeUniqueId :: UniqueIdAttribute
+  , reservationAttributeName :: Maybe Str
+  , reservationAttributeUnknown1 :: Bool
+  , reservationAttributeUnknown2 :: Bool
+  , reservationAttributeUnknown3 :: Maybe Word8
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''ReservationAttribute)
diff --git a/source/library/Rattletrap/Type/RigidBodyStateAttribute.hs b/source/library/Rattletrap/Type/RigidBodyStateAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/RigidBodyStateAttribute.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.RigidBodyStateAttribute
+  ( RigidBodyStateAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Rotation
+import Rattletrap.Type.Vector
+
+data RigidBodyStateAttribute = RigidBodyStateAttribute
+  { rigidBodyStateAttributeSleeping :: Bool
+  , rigidBodyStateAttributeLocation :: Vector
+  , rigidBodyStateAttributeRotation :: Rotation
+  , rigidBodyStateAttributeLinearVelocity :: Maybe Vector
+  , rigidBodyStateAttributeAngularVelocity :: Maybe Vector
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''RigidBodyStateAttribute)
diff --git a/source/library/Rattletrap/Type/Rotation.hs b/source/library/Rattletrap/Type/Rotation.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Rotation.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Rotation
+  ( Rotation(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Quaternion
+import Rattletrap.Type.CompressedWordVector
+
+data Rotation
+  = RotationCompressedWordVector CompressedWordVector
+  | RotationQuaternion Quaternion
+  deriving (Eq, Ord, Show)
+
+$(deriveJson ''Rotation)
diff --git a/source/library/Rattletrap/Type/Section.hs b/source/library/Rattletrap/Type/Section.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Section.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Section
+  ( Section(..)
+  , toSection
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+import Rattletrap.Utility.Crc
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Lazy as LazyBytes
+
+-- | A section is a large piece of a 'Rattletrap.Replay.Replay'. It has a
+-- 32-bit size (in bytes), a 32-bit CRC (see "Rattletrap.Utility.Crc"), and then a
+-- bunch of data (the body). This interface is provided so that you don't have
+-- to think about the size and CRC.
+data Section a = Section
+  { sectionSize :: Word32le
+  -- ^ read only
+  , sectionCrc :: Word32le
+  -- ^ read only
+  , sectionBody :: a
+  -- ^ The actual content in the section.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Section)
+
+toSection :: (a -> Binary.Put) -> a -> Section a
+toSection encode body =
+  let bytes = LazyBytes.toStrict . Binary.runPut $ encode body
+  in
+    Section
+      { sectionSize = Word32le . fromIntegral $ Bytes.length bytes
+      , sectionCrc = Word32le $ getCrc32 bytes
+      , sectionBody = body
+      }
diff --git a/source/library/Rattletrap/Type/SpawnedReplication.hs b/source/library/Rattletrap/Type/SpawnedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/SpawnedReplication.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.SpawnedReplication
+  ( SpawnedReplication(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Initialization
+import Rattletrap.Type.Str
+import Rattletrap.Type.Word32le
+
+data SpawnedReplication = SpawnedReplication
+  { spawnedReplicationFlag :: Bool
+  -- ^ Unclear what this is.
+  , spawnedReplicationNameIndex :: Maybe Word32le
+  , spawnedReplicationName :: Maybe Str
+  -- ^ Read-only! Changing a replication's name requires editing the
+  -- 'spawnedReplicationNameIndex' and maybe the class attribute map.
+  , spawnedReplicationObjectId :: Word32le
+  , spawnedReplicationObjectName :: Str
+  -- ^ Read-only! Changing a replication's object requires editing the class
+  -- attribute map.
+  , spawnedReplicationClassName :: Str
+  -- ^ Read-only! Changing a replication's class requires editing the class
+  -- attribute map.
+  , spawnedReplicationInitialization :: Initialization
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''SpawnedReplication)
diff --git a/source/library/Rattletrap/Type/StatEventAttribute.hs b/source/library/Rattletrap/Type/StatEventAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/StatEventAttribute.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.StatEventAttribute
+  ( StatEventAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Int32le
+
+data StatEventAttribute = StatEventAttribute
+  { statEventAttributeUnknown :: Bool
+  , statEventAttributeObjectId :: Int32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''StatEventAttribute)
diff --git a/source/library/Rattletrap/Type/Str.hs b/source/library/Rattletrap/Type/Str.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Str.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Str
+  ( Str(..)
+  , toStr
+  , fromStr
+  )
+where
+
+import Rattletrap.Type.Common
+
+import qualified Data.Text as Text
+
+newtype Str = Str
+  { strValue :: Text
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Str)
+
+toStr :: String -> Str
+toStr string = Str (Text.pack string)
+
+fromStr :: Str -> String
+fromStr text = Text.unpack (strValue text)
diff --git a/source/library/Rattletrap/Type/StringAttribute.hs b/source/library/Rattletrap/Type/StringAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/StringAttribute.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.StringAttribute
+  ( StringAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Str
+
+newtype StringAttribute = StringAttribute
+  { stringAttributeValue :: Str
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''StringAttribute)
diff --git a/source/library/Rattletrap/Type/TeamPaintAttribute.hs b/source/library/Rattletrap/Type/TeamPaintAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/TeamPaintAttribute.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.TeamPaintAttribute
+  ( TeamPaintAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+import Rattletrap.Type.Word8le
+
+data TeamPaintAttribute = TeamPaintAttribute
+  { teamPaintAttributeTeam :: Word8le
+  , teamPaintAttributePrimaryColor :: Word8le
+  , teamPaintAttributeAccentColor :: Word8le
+  , teamPaintAttributePrimaryFinish :: Word32le
+  , teamPaintAttributeAccentFinish :: Word32le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''TeamPaintAttribute)
diff --git a/source/library/Rattletrap/Type/TitleAttribute.hs b/source/library/Rattletrap/Type/TitleAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/TitleAttribute.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.TitleAttribute
+  ( TitleAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Word32le
+
+data TitleAttribute = TitleAttribute
+  { titleAttributeUnknown1 :: Bool
+  , titleAttributeUnknown2 :: Bool
+  , titleAttributeUnknown3 :: Word32le
+  , titleAttributeUnknown4 :: Word32le
+  , titleAttributeUnknown5 :: Word32le
+  , titleAttributeUnknown6 :: Word32le
+  , titleAttributeUnknown7 :: Word32le
+  , titleAttributeUnknown8 :: Bool
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''TitleAttribute)
diff --git a/source/library/Rattletrap/Type/UniqueIdAttribute.hs b/source/library/Rattletrap/Type/UniqueIdAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/UniqueIdAttribute.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.UniqueIdAttribute
+  ( UniqueIdAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.RemoteId
+import Rattletrap.Type.Word8le
+
+data UniqueIdAttribute = UniqueIdAttribute
+  { uniqueIdAttributeSystemId :: Word8le
+  , uniqueIdAttributeRemoteId :: RemoteId
+  , uniqueIdAttributeLocalId :: Word8le
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''UniqueIdAttribute)
diff --git a/source/library/Rattletrap/Type/UpdatedReplication.hs b/source/library/Rattletrap/Type/UpdatedReplication.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/UpdatedReplication.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.UpdatedReplication
+  ( UpdatedReplication(..)
+  )
+where
+
+import Rattletrap.Type.Attribute
+import Rattletrap.Type.Common
+
+newtype UpdatedReplication = UpdatedReplication
+  { updatedReplicationAttributes :: [Attribute]
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''UpdatedReplication)
diff --git a/source/library/Rattletrap/Type/Vector.hs b/source/library/Rattletrap/Type/Vector.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Vector.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Vector
+  ( Vector(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.CompressedWord
+
+data Vector = Vector
+  { vectorSize :: CompressedWord
+  , vectorBias :: Word
+  -- ^ This field is guaranteed to be small. In other words, it won't overflow.
+  -- It's stored as a regular 'Word' rather than something more precise like a
+  -- 'Word8' because it just gets passed to a functions that expect 'Word's.
+  -- There's no reason to do a bunch of conversions.
+  , vectorX :: Int
+  -- ^ See 'vectorBias'.
+  , vectorY :: Int
+  -- ^ See 'vectorBias'.
+  , vectorZ :: Int
+  -- ^ See 'vectorBias'.
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Vector)
diff --git a/source/library/Rattletrap/Type/WeldedInfoAttribute.hs b/source/library/Rattletrap/Type/WeldedInfoAttribute.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/WeldedInfoAttribute.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.WeldedInfoAttribute
+  ( WeldedInfoAttribute(..)
+  )
+where
+
+import Rattletrap.Type.Common
+import Rattletrap.Type.Float32le
+import Rattletrap.Type.Int32le
+import Rattletrap.Type.Int8Vector
+import Rattletrap.Type.Vector
+
+data WeldedInfoAttribute = WeldedInfoAttribute
+  { weldedInfoAttributeActive :: Bool
+  , weldedInfoAttributeActorId :: Int32le
+  , weldedInfoAttributeOffset :: Vector
+  , weldedInfoAttributeMass :: Float32le
+  , weldedInfoAttributeRotation :: Int8Vector
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''WeldedInfoAttribute)
diff --git a/source/library/Rattletrap/Type/Word32le.hs b/source/library/Rattletrap/Type/Word32le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Word32le.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Word32le
+  ( Word32le(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype Word32le = Word32le
+  { word32leValue :: Word32
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Word32le)
diff --git a/source/library/Rattletrap/Type/Word64le.hs b/source/library/Rattletrap/Type/Word64le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Word64le.hs
@@ -0,0 +1,28 @@
+module Rattletrap.Type.Word64le
+  ( Word64le(..)
+  )
+where
+
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+import qualified Data.Scientific as Scientific
+import qualified Data.Text as Text
+import qualified Data.Word as Word
+import qualified Text.Read as Read
+
+newtype Word64le = Word64le
+  { word64leValue :: Word.Word64
+  } deriving (Eq, Ord, Show)
+
+instance Aeson.FromJSON Word64le where
+  parseJSON value = case value of
+    Aeson.String text -> case Read.readEither $ Text.unpack text of
+      Left _ -> Aeson.typeMismatch "Word64le" value
+      Right word64 -> pure $ Word64le word64
+    Aeson.Number number -> case Scientific.toBoundedInteger number of
+      Nothing -> Aeson.typeMismatch "Word64le" value
+      Just word64 -> pure $ Word64le word64
+    _ -> Aeson.typeMismatch "Word64le" value
+
+instance Aeson.ToJSON Word64le where
+  toJSON = Aeson.toJSON . show . word64leValue
diff --git a/source/library/Rattletrap/Type/Word8le.hs b/source/library/Rattletrap/Type/Word8le.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Type/Word8le.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Rattletrap.Type.Word8le
+  ( Word8le(..)
+  )
+where
+
+import Rattletrap.Type.Common
+
+newtype Word8le = Word8le
+  { word8leValue :: Word8
+  } deriving (Eq, Ord, Show)
+
+$(deriveJson ''Word8le)
diff --git a/source/library/Rattletrap/Utility/Bytes.hs b/source/library/Rattletrap/Utility/Bytes.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Utility/Bytes.hs
@@ -0,0 +1,33 @@
+module Rattletrap.Utility.Bytes
+  ( encodeLatin1
+  , padBytes
+  , reverseBytes
+  )
+where
+
+import qualified Data.Bits as Bits
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Char8 as Bytes8
+import qualified Data.Text as Text
+import qualified Data.Word as Word
+
+encodeLatin1 :: Text.Text -> Bytes.ByteString
+encodeLatin1 text = Bytes8.pack (Text.unpack text)
+
+padBytes :: Integral a => a -> Bytes.ByteString -> Bytes.ByteString
+padBytes size bytes =
+  bytes <> Bytes.replicate (fromIntegral size - Bytes.length bytes) 0x00
+
+reverseByte :: Word.Word8 -> Word.Word8
+reverseByte byte =
+  Bits.shiftR (byte Bits..&. Bits.bit 7) 7
+    + Bits.shiftR (byte Bits..&. Bits.bit 6) 5
+    + Bits.shiftR (byte Bits..&. Bits.bit 5) 3
+    + Bits.shiftR (byte Bits..&. Bits.bit 4) 1
+    + Bits.shiftL (byte Bits..&. Bits.bit 3) 1
+    + Bits.shiftL (byte Bits..&. Bits.bit 2) 3
+    + Bits.shiftL (byte Bits..&. Bits.bit 1) 5
+    + Bits.shiftL (byte Bits..&. Bits.bit 0) 7
+
+reverseBytes :: Bytes.ByteString -> Bytes.ByteString
+reverseBytes = Bytes.map reverseByte
diff --git a/source/library/Rattletrap/Utility/Crc.hs b/source/library/Rattletrap/Utility/Crc.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Utility/Crc.hs
@@ -0,0 +1,47 @@
+module Rattletrap.Utility.Crc
+  ( getCrc32
+  )
+where
+
+import Rattletrap.Data
+
+import qualified Data.Bits as Bits
+import qualified Data.ByteString as Bytes
+import qualified Data.IntMap as IntMap
+import qualified Data.Word as Word
+
+-- | Computes the CRC32 of some bytes. This is done to ensure that the bytes
+-- are valid before trying to parse them.
+--
+-- @
+-- getCrc32 ('Data.ByteString.Lazy.pack' [0x00])
+-- @
+--
+-- This CRC uses an initial value of @0xefcbf201@ and a polynomial of
+-- @0x04c11db7@.
+getCrc32 :: Bytes.ByteString -> Word.Word32
+getCrc32 bytes = do
+  let
+    update = crc32Update crc32Table
+    initial = Bits.complement crc32Initial
+    crc = Bytes.foldl update initial bytes
+  Bits.complement crc
+
+crc32Update
+  :: IntMap.IntMap Word.Word32 -> Word.Word32 -> Word.Word8 -> Word.Word32
+crc32Update table crc byte = do
+  let
+    toWord8 :: (Integral a) => a -> Word.Word8
+    toWord8 = fromIntegral
+    toInt :: (Integral a) => a -> Int
+    toInt = fromIntegral
+    index = toInt (Bits.xor byte (toWord8 (Bits.shiftR crc 24)))
+    left = table IntMap.! index
+    right = Bits.shiftL crc 8
+  Bits.xor left right
+
+crc32Initial :: Word.Word32
+crc32Initial = 0xefcbf201
+
+crc32Table :: IntMap.IntMap Word.Word32
+crc32Table = IntMap.fromDistinctAscList (zip [0 ..] rawCrc32Table)
diff --git a/source/library/Rattletrap/Utility/Helper.hs b/source/library/Rattletrap/Utility/Helper.hs
new file mode 100644
--- /dev/null
+++ b/source/library/Rattletrap/Utility/Helper.hs
@@ -0,0 +1,46 @@
+-- | This module provides helper functions for converting replays to and from
+-- both their binary format and JSON.
+module Rattletrap.Utility.Helper
+  ( decodeReplayFile
+  , encodeReplayJson
+  , decodeReplayJson
+  , encodeReplayFile
+  )
+where
+
+import Rattletrap.Decode.Common
+import Rattletrap.Encode.Content
+import Rattletrap.Decode.Replay
+import Rattletrap.Encode.Replay
+import Rattletrap.Type.Replay
+import Rattletrap.Type.Section
+import Rattletrap.Type.Content
+
+import qualified Data.Aeson as Json
+import qualified Data.Aeson.Encode.Pretty as Json
+import qualified Data.Binary.Put as Binary
+import qualified Data.ByteString as Bytes
+import qualified Data.ByteString.Lazy as LazyBytes
+
+-- | Parses a raw replay.
+decodeReplayFile :: Bool -> Bytes.ByteString -> Either String Replay
+decodeReplayFile fast = runDecode $ decodeReplay fast
+
+-- | Encodes a replay as JSON.
+encodeReplayJson :: Replay -> Bytes.ByteString
+encodeReplayJson = LazyBytes.toStrict . Json.encodePretty' Json.defConfig
+  { Json.confCompare = compare
+  , Json.confIndent = Json.Spaces 2
+  , Json.confTrailingNewline = True
+  }
+
+-- | Parses a JSON replay.
+decodeReplayJson :: Bytes.ByteString -> Either String Replay
+decodeReplayJson = Json.eitherDecodeStrict'
+
+-- | Encodes a raw replay.
+encodeReplayFile :: Bool -> Replay -> Bytes.ByteString
+encodeReplayFile fast replay =
+  LazyBytes.toStrict . Binary.runPut . putReplay $ if fast
+    then replay { replayContent = toSection putContent defaultContent }
+    else replay
diff --git a/source/test-suite/Main.hs b/source/test-suite/Main.hs
new file mode 100644
--- /dev/null
+++ b/source/test-suite/Main.hs
@@ -0,0 +1,187 @@
+module Main
+  ( main
+  )
+where
+
+import qualified Control.Monad as Monad
+import qualified Data.ByteString.Lazy as Bytes
+import qualified Data.Int as Int
+import qualified Data.Word as Word
+import qualified GHC.Clock as Clock
+import qualified Rattletrap
+import qualified System.Exit as Exit
+import qualified System.FilePath as Path
+import qualified System.IO.Temp as Temp
+import qualified System.Mem as Mem
+import qualified Test.HUnit as Test
+import qualified Text.Printf as Printf
+
+main :: IO ()
+main = Temp.withSystemTempDirectory "rattletrap-" (runTests . toTests)
+
+runTests :: Test.Test -> IO ()
+runTests test = do
+  result <- Test.runTestTT test
+  Monad.when
+    (Test.errors result > 0 || Test.failures result > 0)
+    Exit.exitFailure
+
+toTests :: FilePath -> Test.Test
+toTests directory = Test.TestList (fmap (toTest directory) replays)
+
+toTest :: FilePath -> (String, String) -> Test.Test
+toTest directory (uuid, name) = Test.TestLabel
+  (toLabel uuid name)
+  (Test.TestCase (toAssertion directory uuid))
+
+toLabel :: String -> String -> String
+toLabel uuid name = uuid <> ": " <> name
+
+toAssertion :: FilePath -> String -> Test.Assertion
+toAssertion directory uuid = do
+  let
+    inputFile = Path.joinPath ["replays", Path.addExtension uuid ".replay"]
+    jsonFile = Path.joinPath [directory, Path.addExtension uuid ".json"]
+    outputFile = Path.joinPath [directory, Path.addExtension uuid ".replay"]
+  input <- Bytes.readFile inputFile
+  putStrLn ("\t" <> uuid)
+  do
+    (((), allocated), elapsed) <- withElapsed
+      (withAllocations (decode inputFile jsonFile))
+    put "decoding" (Bytes.length input) elapsed allocated
+  do
+    (((), allocated), elapsed) <- withElapsed
+      (withAllocations (encode jsonFile outputFile))
+    put "encoding" (Bytes.length input) elapsed allocated
+  output <- Bytes.readFile outputFile
+  Monad.unless
+    (output == input)
+    (Test.assertFailure "output does not match input")
+
+put :: String -> Int.Int64 -> Word.Word64 -> Int.Int64 -> IO ()
+put label size elapsed allocated = Printf.printf
+  "%s %d byte%s took %d nanosecond%s (%.3f MB/s) and allocated %d byte%s (%d x)\n"
+  label
+  size
+  (if size == 1 then "" else "s")
+  elapsed
+  (if elapsed == 1 then "" else "s")
+  ((1e9 * fromIntegral size) / (1048576 * fromIntegral elapsed) :: Double)
+  allocated
+  (if allocated == 1 then "" else "s")
+  (div allocated size)
+
+decode :: FilePath -> FilePath -> IO ()
+decode input output =
+  Rattletrap.rattletrap "" ["--compact", "--input", input, "--output", output]
+
+encode :: FilePath -> FilePath -> IO ()
+encode input output =
+  Rattletrap.rattletrap "" ["--input", input, "--output", output]
+
+withAllocations :: IO a -> IO (a, Int.Int64)
+withAllocations action = do
+  before <- Mem.getAllocationCounter
+  result <- action
+  after <- Mem.getAllocationCounter
+  pure (result, before - after)
+
+withElapsed :: IO a -> IO (a, Word.Word64)
+withElapsed action = do
+  before <- Clock.getMonotonicTimeNSec
+  result <- action
+  after <- Clock.getMonotonicTimeNSec
+  pure (result, after - before)
+
+replays :: [(String, String)]
+replays =
+  [ ("d1d5", "v1.68")
+  , ("21a8", "v1.66")
+  , ("0008", "a flip time")
+  , ("000b", "nintendo switch")
+  , ("07e9", "a game mode before Neo Tokyo")
+  , ("0ad2", "some Latin-1 text")
+  , ("1205", "rumble mode")
+  , ("160c", "a dedicated server IP")
+  , ("16d5", "new property types")
+  , ("18d6", "an online loadout attribute")
+  , ("1a12", "overtime")
+  , ("1ae4", "a game time")
+  , ("1bc2", "no padding after the frames")
+  , ("1d1d", "a camera pitch")
+  , ("1ec9", "a V1.63 match")
+  , ("1ef9", "a private hoops match")
+  , ("1f37", "splitscreen players")
+  , ("2114", "a match save")
+  , ("2266", "dropshot")
+  , ("22ba", "a vote to forfeit")
+  , ("27b6", "some UTF-16 text")
+  , ("29f5", "frames")
+  , ("2cfe", "a new playstation id")
+  , ("3381", "patch 1.37")
+  , ("372d", "a camera yaw attribute")
+  , ("383e", "older unknown content field")
+  , ("387f", "a frozen attribute")
+  , ("3abd", "rlcs")
+  , ("3ea1", "a custom team name")
+  , ("4126", "a game mode after Neo Tokyo")
+  , ("419a", "a club match")
+  , ("42f0", "reservations after Neo Tokyo")
+  , ("4bc3", "with timed out attribute")
+  , ("504e", "some messages")
+  , ("520e", "no pickup attribute")
+  , ("524f", "quat edge case")
+  , ("52aa", "a match-ending attribute")
+  , ("540d", "a demolish attribute")
+  , ("551c", "private match settings")
+  , ("5a06", "esports items")
+  , ("6210", "different player history key")
+  , ("6320", "a forfeit attribute")
+  , ("6688", "a malformed byte property")
+  , ("6b0d", "patch 1.37")
+  , ("6d1b", "a flip right")
+  , ("6f7c", "a map with numbers")
+  , ("7083", "weird basketball capitalization")
+  , ("7109", "a boost modifier")
+  , ("7256", "special edition")
+  , ("75ce", "primary and secondary titles")
+  , ("7bf6", "an online loadouts attribute")
+  , ("89cb", "remote user data")
+  , ("8ae5", "new painted items")
+  , ("92a6", "with server performance state")
+  , ("946f", "patch 1.43")
+  , ("9704", "a batarang")
+  , ("98e5", "a player using behind view")
+  , ("9eaa", "newer replay without trailing bytes")
+  , ("a09e", "a tournament")
+  , ("a128", "a round count down")
+  , ("a52f", "some more mutators")
+  , ("a558", "extended explosion data")
+  , ("a671", "a waiting player")
+  , ("a676", "new user color")
+  , ("a7f0", "a ready attribute")
+  , ("a9df", "salty shores patch 1.45")
+  , ("aa70", "patch 1.50 - TitleID attribute")
+  , ("afb1", "patch 1.37")
+  , ("b9f9", "a party leader")
+  , ("c14f", "some mutators")
+  , ("c23b", "new psynet id")
+  , ("c837", "a spectator")
+  , ("cc4c", "after Starbase ARC")
+  , ("d044", "hoops mutators")
+  , ("d236", "rlcs s2")
+  , ("d428", "a private hockey match")
+  , ("d52e", "psynet system id")
+  , ("d7fb", "an explosion attribute")
+  , ("db70", "new lag indicator")
+  , ("dcb3", "a pawn type attribute")
+  , ("de56", "a problematic product attribute")
+  , ("e80d", "unlimited time")
+  , ("eae3", "an actor/object ID collision")
+  , ("eae8", "custom team colors")
+  , ("edbb", "remote role")
+  , ("f299", "a location attribute")
+  , ("f7b9", "a hockey game event")
+  , ("f811", "no frames")
+  , ("fdc7", "an MVP")
+  ]
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
--- a/stack.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-resolver: nightly-2019-05-27
diff --git a/tests/Main.hs b/tests/Main.hs
deleted file mode 100644
--- a/tests/Main.hs
+++ /dev/null
@@ -1,186 +0,0 @@
-module Main
-  ( main
-  )
-where
-
-import qualified Control.Monad as Monad
-import qualified Data.ByteString.Lazy as Bytes
-import qualified Data.Int as Int
-import qualified Data.Word as Word
-import qualified GHC.Clock as Clock
-import qualified Rattletrap
-import qualified System.Exit as Exit
-import qualified System.FilePath as Path
-import qualified System.IO.Temp as Temp
-import qualified System.Mem as Mem
-import qualified Test.HUnit as Test
-import qualified Text.Printf as Printf
-
-main :: IO ()
-main = Temp.withSystemTempDirectory "rattletrap-" (runTests . toTests)
-
-runTests :: Test.Test -> IO ()
-runTests test = do
-  result <- Test.runTestTT test
-  Monad.when
-    (Test.errors result > 0 || Test.failures result > 0)
-    Exit.exitFailure
-
-toTests :: FilePath -> Test.Test
-toTests directory = Test.TestList (fmap (toTest directory) replays)
-
-toTest :: FilePath -> (String, String) -> Test.Test
-toTest directory (uuid, name) = Test.TestLabel
-  (toLabel uuid name)
-  (Test.TestCase (toAssertion directory uuid))
-
-toLabel :: String -> String -> String
-toLabel uuid name = uuid <> ": " <> name
-
-toAssertion :: FilePath -> String -> Test.Assertion
-toAssertion directory uuid = do
-  let
-    inputFile = Path.joinPath ["replays", Path.addExtension uuid ".replay"]
-    jsonFile = Path.joinPath [directory, Path.addExtension uuid ".json"]
-    outputFile = Path.joinPath [directory, Path.addExtension uuid ".replay"]
-  input <- Bytes.readFile inputFile
-  putStrLn ("\t" <> uuid)
-  do
-    (((), allocated), elapsed) <- withElapsed
-      (withAllocations (decode inputFile jsonFile))
-    put "decoding" (Bytes.length input) elapsed allocated
-  do
-    (((), allocated), elapsed) <- withElapsed
-      (withAllocations (encode jsonFile outputFile))
-    put "encoding" (Bytes.length input) elapsed allocated
-  output <- Bytes.readFile outputFile
-  Monad.unless
-    (output == input)
-    (Test.assertFailure "output does not match input")
-
-put :: String -> Int.Int64 -> Word.Word64 -> Int.Int64 -> IO ()
-put label size elapsed allocated = Printf.printf
-  "%s %d byte%s took %d nanosecond%s (%.3f MB/s) and allocated %d byte%s (%d x)\n"
-  label
-  size
-  (if size == 1 then "" else "s")
-  elapsed
-  (if elapsed == 1 then "" else "s")
-  ((1e9 * fromIntegral size) / (1048576 * fromIntegral elapsed) :: Double)
-  allocated
-  (if allocated == 1 then "" else "s")
-  (div allocated size)
-
-decode :: FilePath -> FilePath -> IO ()
-decode input output =
-  Rattletrap.rattletrap "" ["--compact", "--input", input, "--output", output]
-
-encode :: FilePath -> FilePath -> IO ()
-encode input output =
-  Rattletrap.rattletrap "" ["--input", input, "--output", output]
-
-withAllocations :: IO a -> IO (a, Int.Int64)
-withAllocations action = do
-  before <- Mem.getAllocationCounter
-  result <- action
-  after <- Mem.getAllocationCounter
-  pure (result, before - after)
-
-withElapsed :: IO a -> IO (a, Word.Word64)
-withElapsed action = do
-  before <- Clock.getMonotonicTimeNSec
-  result <- action
-  after <- Clock.getMonotonicTimeNSec
-  pure (result, after - before)
-
-replays :: [(String, String)]
-replays =
-  [ ("21a8", "v1.66")
-  , ("0008", "a flip time")
-  , ("000b", "nintendo switch")
-  , ("07e9", "a game mode before Neo Tokyo")
-  , ("0ad2", "some Latin-1 text")
-  , ("1205", "rumble mode")
-  , ("160c", "a dedicated server IP")
-  , ("16d5", "new property types")
-  , ("18d6", "an online loadout attribute")
-  , ("1a12", "overtime")
-  , ("1ae4", "a game time")
-  , ("1bc2", "no padding after the frames")
-  , ("1d1d", "a camera pitch")
-  , ("1ec9", "a V1.63 match")
-  , ("1ef9", "a private hoops match")
-  , ("1f37", "splitscreen players")
-  , ("2114", "a match save")
-  , ("2266", "dropshot")
-  , ("22ba", "a vote to forfeit")
-  , ("27b6", "some UTF-16 text")
-  , ("29f5", "frames")
-  , ("2cfe", "a new playstation id")
-  , ("3381", "patch 1.37")
-  , ("372d", "a camera yaw attribute")
-  , ("383e", "older unknown content field")
-  , ("387f", "a frozen attribute")
-  , ("3abd", "rlcs")
-  , ("3ea1", "a custom team name")
-  , ("4126", "a game mode after Neo Tokyo")
-  , ("419a", "a club match")
-  , ("42f0", "reservations after Neo Tokyo")
-  , ("4bc3", "with timed out attribute")
-  , ("504e", "some messages")
-  , ("520e", "no pickup attribute")
-  , ("524f", "quat edge case")
-  , ("52aa", "a match-ending attribute")
-  , ("540d", "a demolish attribute")
-  , ("551c", "private match settings")
-  , ("5a06", "esports items")
-  , ("6210", "different player history key")
-  , ("6320", "a forfeit attribute")
-  , ("6688", "a malformed byte property")
-  , ("6b0d", "patch 1.37")
-  , ("6d1b", "a flip right")
-  , ("6f7c", "a map with numbers")
-  , ("7083", "weird basketball capitalization")
-  , ("7109", "a boost modifier")
-  , ("7256", "special edition")
-  , ("75ce", "primary and secondary titles")
-  , ("7bf6", "an online loadouts attribute")
-  , ("89cb", "remote user data")
-  , ("8ae5", "new painted items")
-  , ("92a6", "with server performance state")
-  , ("946f", "patch 1.43")
-  , ("9704", "a batarang")
-  , ("98e5", "a player using behind view")
-  , ("9eaa", "newer replay without trailing bytes")
-  , ("a09e", "a tournament")
-  , ("a128", "a round count down")
-  , ("a52f", "some more mutators")
-  , ("a558", "extended explosion data")
-  , ("a671", "a waiting player")
-  , ("a676", "new user color")
-  , ("a7f0", "a ready attribute")
-  , ("a9df", "salty shores patch 1.45")
-  , ("aa70", "patch 1.50 - TitleID attribute")
-  , ("afb1", "patch 1.37")
-  , ("b9f9", "a party leader")
-  , ("c14f", "some mutators")
-  , ("c23b", "new psynet id")
-  , ("c837", "a spectator")
-  , ("cc4c", "after Starbase ARC")
-  , ("d044", "hoops mutators")
-  , ("d236", "rlcs s2")
-  , ("d428", "a private hockey match")
-  , ("d52e", "psynet system id")
-  , ("d7fb", "an explosion attribute")
-  , ("db70", "new lag indicator")
-  , ("dcb3", "a pawn type attribute")
-  , ("de56", "a problematic product attribute")
-  , ("e80d", "unlimited time")
-  , ("eae3", "an actor/object ID collision")
-  , ("eae8", "custom team colors")
-  , ("edbb", "remote role")
-  , ("f299", "a location attribute")
-  , ("f7b9", "a hockey game event")
-  , ("f811", "no frames")
-  , ("fdc7", "an MVP")
-  ]
