packages feed

octane 0.10.0 → 0.11.0

raw patch · 124 files changed

+4974/−5856 lines, 124 filesdep +doctestdep +http-clientdep +http-client-tlsdep −autoexporterdep −scientificdep ~basedep ~binarydep ~bytestringsetup-changed

Dependencies added: doctest, http-client, http-client-tls, unordered-containers

Dependencies removed: autoexporter, scientific

Dependency ranges changed: base, binary, bytestring, containers

Files

Setup.hs view
@@ -1,4 +1,5 @@ import qualified Distribution.Simple + main :: IO () main = Distribution.Simple.defaultMain
− benchmark/Benchmark.hs
@@ -1,5 +0,0 @@-import qualified Criterion.Main-import qualified OctaneBench--main :: IO ()-main = Criterion.Main.defaultMain [OctaneBench.benchmarks]
+ benchmark/Main.hs view
@@ -0,0 +1,14 @@+module Main (main) where++import qualified Criterion.Main as Criterion+import qualified OctaneBench+++main :: IO ()+main = Criterion.defaultMain bench+++bench :: [Criterion.Benchmark]+bench =+    [ OctaneBench.bench+    ]
− benchmark/Octane/MainBench.hs
@@ -1,8 +0,0 @@-module Octane.MainBench (benchmarks) where--import Criterion--benchmarks :: Benchmark-benchmarks = bgroup "Main"-    [-    ]
− benchmark/Octane/Type/ActorBench.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.ActorBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Actor"-    [ bench "decode basic" (nf decodeActor "\-        \\1\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Actor-        (Text "")-        (Int32 0)))-    ]--decodeActor :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Actor)-decodeActor = Binary.decodeOrFail
− benchmark/Octane/Type/CacheItemBench.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.CacheItemBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "CacheItem"-    [ bench "decode basic" (nf decodeCacheItem "\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (CacheItem-        (Int32 0)-        (Int32 0)-        (Int32 0)-        (List [])))-    ]--decodeCacheItem :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, CacheItem)-decodeCacheItem = Binary.decodeOrFail
− benchmark/Octane/Type/CachePropertyBench.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.CachePropertyBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "CacheProperty"-    [ bench "decode basic" (nf decodeCacheProperty "\-        \\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (CacheProperty-        (Int32 0)-        (Int32 0)))-    ]--decodeCacheProperty :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, CacheProperty)-decodeCacheProperty = Binary.decodeOrFail
− benchmark/Octane/Type/KeyFrameBench.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.KeyFrameBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "KeyFrame"-    [ bench "decode basic" (nf decodeKeyFrame "\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (KeyFrame-        (Float32 0.0)-        (Int32 0)-        (Int32 0)))-    ]--decodeKeyFrame :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, KeyFrame)-decodeKeyFrame = Binary.decodeOrFail
− benchmark/Octane/Type/MarkBench.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.MarkBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Mark"-    [ bench "decode basic" (nf decodeMark "\-        \\1\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Mark-        (Text "")-        (Int32 0)))-    ]--decodeMark :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Mark)-decodeMark = Binary.decodeOrFail
− benchmark/Octane/Type/MessageBench.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.MessageBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Message"-    [ bench "decode basic" (nf decodeMessage "\-        \\0\0\0\0\-        \\1\0\0\0\0\-        \\1\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Message-        (Int32 0)-        (Text "")-        (Text "")))-    ]--decodeMessage :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Message)-decodeMessage = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/BooleanBench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.BooleanBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Boolean"-    [ bench "decode basic" (nf decodeBoolean "\0")-    , bench "encode basic" (nf Binary.encode (Boolean False))-    ]--decodeBoolean :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Boolean)-decodeBoolean = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/DictionaryBench.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.DictionaryBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Map as Map--benchmarks :: Benchmark-benchmarks = bgroup "Dictionary"-    [ bench "decode basic" (nf decodeBooleanDictionary "\5\0\0\0None\0")-    , bench "encode basic" (nf Binary.encode (Dictionary Map.empty :: Dictionary Boolean))-    ]--decodeDictionary :: (Binary.Binary a) => BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Dictionary a)-decodeDictionary = Binary.decodeOrFail--decodeBooleanDictionary :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Dictionary Boolean)-decodeBooleanDictionary = decodeDictionary
− benchmark/Octane/Type/Primitive/Float32Bench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Float32Bench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Float32"-    [ bench "decode basic" (nf decodeFloat32 "\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Float32 0.0))-    ]--decodeFloat32 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Float32)-decodeFloat32 = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/Int32Bench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Int32Bench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Int32"-    [ bench "decode basic" (nf decodeInt32 "\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Int32 0))-    ]--decodeInt32 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int32)-decodeInt32 = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/Int64Bench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Int64Bench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Int64"-    [ bench "decode basic" (nf decodeInt64 "\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Int64 0))-    ]--decodeInt64 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int64)-decodeInt64 = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/ListBench.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.ListBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "List"-    [ bench "decode basic" (nf decodeBooleanList "\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (List [] :: List Boolean))-    ]--decodeList :: (Binary.Binary a) => BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, List a)-decodeList = Binary.decodeOrFail--decodeBooleanList :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, List Boolean)-decodeBooleanList = decodeList
− benchmark/Octane/Type/Primitive/StreamBench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.StreamBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Stream"-    [ bench "decode basic" (nf decodeStream "\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Stream ""))-    ]--decodeStream :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Stream)-decodeStream = Binary.decodeOrFail
− benchmark/Octane/Type/Primitive/TextBench.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.TextBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Text"-    [ bench "decode basic" (nf decodeText "\1\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Text ""))-    ]--decodeText :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Text)-decodeText = Binary.decodeOrFail
− benchmark/Octane/Type/PrimitiveBench.hs
@@ -1,24 +0,0 @@-module Octane.Type.PrimitiveBench (benchmarks) where--import Criterion--import qualified Octane.Type.Primitive.BooleanBench-import qualified Octane.Type.Primitive.DictionaryBench-import qualified Octane.Type.Primitive.Float32Bench-import qualified Octane.Type.Primitive.ListBench-import qualified Octane.Type.Primitive.TextBench-import qualified Octane.Type.Primitive.StreamBench-import qualified Octane.Type.Primitive.Int32Bench-import qualified Octane.Type.Primitive.Int64Bench--benchmarks :: Benchmark-benchmarks = bgroup "Primitive"-    [ Octane.Type.Primitive.BooleanBench.benchmarks-    , Octane.Type.Primitive.DictionaryBench.benchmarks-    , Octane.Type.Primitive.Float32Bench.benchmarks-    , Octane.Type.Primitive.ListBench.benchmarks-    , Octane.Type.Primitive.TextBench.benchmarks-    , Octane.Type.Primitive.StreamBench.benchmarks-    , Octane.Type.Primitive.Int32Bench.benchmarks-    , Octane.Type.Primitive.Int64Bench.benchmarks-    ]
− benchmark/Octane/Type/PropertyBench.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.PropertyBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--benchmarks :: Benchmark-benchmarks = bgroup "Property"-    [ bgroup "Array"-        [ bench "decode basic" (nf decodeProperty "\-            \\14\0\0\0ArrayProperty\0\-            \\0\0\0\0\0\0\0\0\-            \\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (ArrayProperty-            (Int64 0)-            (List [])))-        ]-    , bgroup "Bool"-        [ bench "decode basic" (nf decodeProperty "\-            \\13\0\0\0BoolProperty\0\-            \\0\0\0\0\0\0\0\0\-            \\0")-        , bench "encode basic" (nf Binary.encode (BoolProperty-            (Int64 0)-            (Boolean False)))-        ]-    , bgroup "Byte"-        [ bench "decode basic" (nf decodeProperty "\-            \\13\0\0\0ByteProperty\0\-            \\0\0\0\0\0\0\0\0\-            \\1\0\0\0\0\-            \\1\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (ByteProperty-            (Int64 0)-            (Text "", Text "")))-        ]-    , bgroup "Float"-        [ bench "decode basic" (nf decodeProperty "\-            \\14\0\0\0FloatProperty\0\-            \\4\0\0\0\0\0\0\0\-            \\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (FloatProperty-            (Int64 4)-            (Float32 0.0)))-        ]-    , bgroup "Int"-        [ bench "decode basic" (nf decodeProperty "\-            \\12\0\0\0IntProperty\0\-            \\4\0\0\0\0\0\0\0\-            \\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (IntProperty-            (Int64 4)-            (Int32 0)))-        ]-    , bgroup "Name"-        [ bench "decode basic" (nf decodeProperty "\-            \\13\0\0\0NameProperty\0\-            \\0\0\0\0\0\0\0\0\-            \\1\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (NameProperty-            (Int64 0)-            (Text "")))-        ]-    , bgroup "QWord"-        [ bench "decode basic" (nf decodeProperty "\-            \\14\0\0\0QWordProperty\0\-            \\8\0\0\0\0\0\0\0\-            \\0\0\0\0\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (QWordProperty-            (Int64 8)-            (Int64 0)))-        ]-    , bgroup "String"-        [ bench "decode basic" (nf decodeProperty "\-            \\12\0\0\0StrProperty\0\-            \\0\0\0\0\0\0\0\0\-            \\1\0\0\0\0")-        , bench "encode basic" (nf Binary.encode (StrProperty-            (Int64 0)-            (Text "")))-        ]-    ]--decodeProperty :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Property)-decodeProperty = Binary.decodeOrFail
− benchmark/Octane/Type/ReplayBench.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.ReplayBench (benchmarks) where--import Criterion-import Octane--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Map as Map--benchmarks :: Benchmark-benchmarks = bgroup "Replay"-    [ bench "decode basic" (nf decodeReplay "\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\1\0\0\0\0\-        \\5\0\0\0None\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0\-        \\0\0\0\0")-    , bench "encode basic" (nf Binary.encode (Replay-        (Word32 0)-        (Word32 0)-        (Int32 0)-        (Int32 0)-        (Text "")-        (Dictionary Map.empty)-        (Word32 0)-        (Word32 0)-        (List [])-        (List [])-        (Stream "")-        (List [])-        (List [])-        (List [])-        (List [])-        (List [])-        (List [])-        (List [])))-    ]--decodeReplay :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Replay)-decodeReplay = Binary.decodeOrFail
− benchmark/Octane/TypeBench.hs
@@ -1,26 +0,0 @@-module Octane.TypeBench (benchmarks) where--import Criterion--import qualified Octane.Type.ActorBench-import qualified Octane.Type.CacheItemBench-import qualified Octane.Type.CachePropertyBench-import qualified Octane.Type.KeyFrameBench-import qualified Octane.Type.MarkBench-import qualified Octane.Type.MessageBench-import qualified Octane.Type.PrimitiveBench-import qualified Octane.Type.PropertyBench-import qualified Octane.Type.ReplayBench--benchmarks :: Benchmark-benchmarks = bgroup "Type"-    [ Octane.Type.ActorBench.benchmarks-    , Octane.Type.CacheItemBench.benchmarks-    , Octane.Type.CachePropertyBench.benchmarks-    , Octane.Type.KeyFrameBench.benchmarks-    , Octane.Type.MarkBench.benchmarks-    , Octane.Type.MessageBench.benchmarks-    , Octane.Type.PrimitiveBench.benchmarks-    , Octane.Type.PropertyBench.benchmarks-    , Octane.Type.ReplayBench.benchmarks-    ]
benchmark/OctaneBench.hs view
@@ -1,12 +1,9 @@-module OctaneBench (benchmarks) where+module OctaneBench (bench) where -import Criterion+import qualified Criterion -import qualified Octane.MainBench-import qualified Octane.TypeBench -benchmarks :: Benchmark-benchmarks = bgroup "Octane"-    [ Octane.MainBench.benchmarks-    , Octane.TypeBench.benchmarks+bench :: Criterion.Benchmark+bench = Criterion.bgroup "Octane"+    [     ]
− executable/Executable.hs
@@ -1,3 +0,0 @@-module Main (module Octane) where--import Octane (main)
+ executable/Main.hs view
@@ -0,0 +1,3 @@+module Main (module Octane) where++import Octane (main)
library/Octane.hs view
@@ -1,15 +1,11 @@ module Octane     ( module Octane.Data-    , module Octane.FullReplay     , module Octane.Main-    , module Octane.Parser     , module Octane.Type-    , module Octane.Version+    , module Octane.Utility     ) where  import Octane.Data-import Octane.FullReplay import Octane.Main-import Octane.Parser import Octane.Type-import Octane.Version+import Octane.Utility
− library/Octane/Analyzer.hs
@@ -1,233 +0,0 @@-module Octane.Analyzer where--import Data.Function ((&))--import qualified Data.Binary as Binary-import qualified Data.Map.Strict as Map-import qualified Data.Maybe as Maybe-import qualified Data.Set as Set-import qualified Data.Text as Text-import qualified Octane.Parser as Parser-import qualified Octane.Type as Type-import qualified System.Environment as Environment--type Point = (Int, Int, Int)-type Points = [Point]-type Frames = [Parser.Frame]-type ActorId = Int---- { car actor id => player actor id }-getCarActorIds :: Frames -> Map.Map ActorId ActorId-getCarActorIds frames = frames-    & concatMap Parser.frameReplications-    & filter (\ replication -> replication-        & Parser.replicationClassName-        & flip Set.member carClassNames)-    & Maybe.mapMaybe (\ replication -> let-        carActorId = replication-            & Parser.replicationActorId-        maybeProperty = replication-            & Parser.replicationProperties-            & Map.lookup playerReplicationInfoPropertyName-        in case maybeProperty of-            Nothing -> Nothing-            Just property -> case property of-                Parser.PFlaggedInt _ playerActorId -> Just (carActorId, Type.fromInt32 playerActorId)-                _ -> Nothing)-    & Map.fromList--getBallActorIds :: Frames -> Set.Set ActorId-getBallActorIds frames = frames-    & concatMap Parser.frameReplications-    & filter (\ replication -> replication-        & Parser.replicationClassName-        & (== ballClassName))-    & map (\ replication -> replication-        & Parser.replicationActorId)-    & Set.fromList---- { player actor id => player name }-getPlayerActorIds :: Frames -> Map.Map ActorId Text.Text-getPlayerActorIds frames = frames-    & concatMap Parser.frameReplications-    & filter (\ replication -> replication-        & Parser.replicationClassName-        & (== playerClassName))-    & Maybe.mapMaybe (\ replication -> let-        actorId = replication-            & Parser.replicationActorId-        maybeProperty = replication-            & Parser.replicationProperties-            & Map.lookup playerNamePropertyName-        in case maybeProperty of-            Nothing -> Nothing-            Just property -> case property of-                Parser.PString name -> Just (actorId, Type.unpackText name)-                _ -> Nothing)-    & Map.fromList--getRigidBodyStatesForActorId :: Frames -> ActorId -> [(Parser.Time, Point)]-getRigidBodyStatesForActorId frames actorId = frames-    & concatMap (\ frame -> let-        time = Parser.frameTime frame-        in frame-            & Parser.frameReplications-            & filter (\ replication -> replication-                & Parser.replicationActorId-                & (== actorId))-            & map Parser.replicationProperties-            & Maybe.mapMaybe (\ properties -> properties-                & Map.lookup rbsPropertyName)-            & Maybe.mapMaybe (\ property -> case property of-                Parser.PRigidBodyState _ location _ _ _ -> Just location-                _ -> Nothing)-            & map (\ (Parser.Vector x y z) -> (x, y, z))-            & map (\ point -> (time, point)))--getBallRigidBodyStates :: Frames -> [[(Parser.Time, Point)]]-getBallRigidBodyStates frames = frames-    & getBallActorIds-    & Set.toAscList-    & map (\ actorId -> actorId-        & getRigidBodyStatesForActorId frames)--getBallLocations :: Frames -> Points-getBallLocations frames = frames-    & getBallRigidBodyStates-    & concat-    & map snd--carClassNames :: Set.Set Text.Text-carClassNames =-    [ "TAGame.Car_Season_TA"-    , "TAGame.Car_TA"-    ] & map Text.pack & Set.fromList--ballClassName :: Text.Text-ballClassName = Text.pack "TAGame.Ball_TA"--playerClassName :: Text.Text-playerClassName = Text.pack "TAGame.PRI_TA"--playerReplicationInfoPropertyName :: Text.Text-playerReplicationInfoPropertyName = Text.pack "Engine.Pawn:PlayerReplicationInfo"--rbsPropertyName :: Text.Text-rbsPropertyName = Text.pack "TAGame.RBActor_TA:ReplicatedRBState"--playerNamePropertyName :: Text.Text-playerNamePropertyName = Text.pack "Engine.PlayerReplicationInfo:PlayerName"--getDistance :: Point -> Point -> Float-getDistance (x1, y1, z1) (x2, y2, z2) = let-    dx = fromIntegral (x2 - x1)-    dy = fromIntegral (y2 - y1)-    dz = fromIntegral (z2 - z1)-    in sqrt (dx ** 2 + dy ** 2 + dz ** 2)--getBallDistances :: Points -> [Float]-getBallDistances points = points-    & zip (drop 1 points)-    & map (\ (p2, p1) -> getDistance p1 p2)--getBallDistance :: Points -> Float-getBallDistance points = points-    & getBallDistances-    & sum--getBallSpeeds :: Points -> [Float]-getBallSpeeds points = points-    & getBallDistances-    & map (\ distance -> distance / 0.04 {- the average delta -})--getHisto :: (a -> Int) -> Int -> Int -> [a] -> (Int, Int, Int)-getHisto fromPoint bottom top points = let-    range = abs (top - bottom)-    third = quot range 3-    values = map fromPoint points-    in foldr-        ((\ value (low, mid, high) ->-            if value > top - third then (low, mid, high + 1)-            else if value < bottom + third then (low + 1, mid, high)-            else (low, mid + 1, high)))-        (0, 0, 0)-        values--getXHisto :: Points -> (Int, Int, Int) -- not sure of the direction... left to right?-getXHisto points = getHisto (\ (x, _, _) -> x) (-4500) 4500 points--getYHisto :: Points -> (Int, Int, Int) -- not sure of the direction... orange to blue?-getYHisto points = getHisto (\ (_, y, _) -> y) (-5200) 5200 points--getZHisto :: Points -> (Int, Int, Int) -- bottom to top-getZHisto points = getHisto (\ (_, _, z) -> z) 0 2000 points--getSpeedHisto :: [Float] -> (Int, Int, Int) -- slow to fast-getSpeedHisto speeds = getHisto round 0 2000 speeds--analyze :: FilePath -> IO ()-analyze file = do-    replay <- Binary.decodeFile file-    let frames = Parser.parseFrames replay--    putStr "Number of frames: "-    print (length frames)--    let ballLocations = getBallLocations frames-    putStr "Number of ball replications: "-    print (length ballLocations) -- 7702--    let ballDistance = getBallDistance ballLocations-    putStr "Total distance traveled by the ball: "-    print ballDistance -- 323759.94--    let (xs, ys, zs) = unzip3 ballLocations-    let minX = minimum xs-    let maxX = maximum xs-    putStr "(Minimum X value, maximum X value): "-    print (minX, maxX) -- (-4004,4003) [wasteland (-4451,4458)]--    let minY = minimum ys-    let maxY = maximum ys-    putStr "(Minimum Y value, maximum Y value): "-    print (minY, maxY) -- (-5215,5214)--    let minZ = minimum zs-    let maxZ = maximum zs-    putStr "(Minimum Z value, maximum Z value): "-    print (minZ, maxZ) -- (88,1943)--    let xHisto = getXHisto ballLocations-    putStr "(Left, middle, right): "-    print xHisto -- (2241,2237,3224)--    let yHisto = getYHisto ballLocations-    putStr "(Orange, middle, blue): "-    print yHisto -- (3731,1504,2467)--    let zHisto = getZHisto ballLocations-    putStr "(Bottom, middle, top): "-    print zHisto -- (6967,507,228)--    let ballSpeeds = getBallSpeeds ballLocations-    let minSpeed = minimum ballSpeeds-    let maxSpeed = maximum ballSpeeds-    putStr "(Minimum speed, maximum speed): "-    print (minSpeed, maxSpeed) -- (0.0,130894.555)--    let speedHisto = getSpeedHisto ballSpeeds-    putStr "(Slow, medium, fast): "-    print speedHisto -- (2250,3630,1821)--    let playerActorIds = getPlayerActorIds frames-    putStrLn "Players:"-    playerActorIds & Map.toList & mapM_ print--    let carActorIds = getCarActorIds frames-    putStrLn "Cars:"-    carActorIds & Map.toList & mapM_ print--main :: IO ()-main = do-    args <- Environment.getArgs-    mapM_ analyze args
library/Octane/Data.hs view
@@ -1,1294 +1,27 @@--- | This module contains largely static data about Rocket League itself, such--- as which classes objects belong to.-module Octane.Data where--import Data.Function ((&))-import Data.Monoid ((<>))--import qualified Data.Bimap as Bimap-import qualified Data.Map.Strict as Map-import qualified Data.Set as Set-import qualified Data.Text as Text---- * Classes---- | A map from class names to a set of object names that are that class. This--- is typically not useful by itself, as it is used to build the--- 'objectToClass' map.-classToObjects :: Map.Map Text.Text (Set.Set Text.Text)-classToObjects = let-    normal =-        [ ( "TAGame.Ball_TA",-            [ "Archetypes.Ball.Ball_Default"-            , "Archetypes.Ball.Ball_Basketball"-            , "Archetypes.Ball.Ball_Puck"-            , "Archetypes.Ball.CubeBall"-            ])-        , ( "TAGame.CameraSettingsActor_TA",-            [ "TAGame.CameraSettingsActor_TA:PRI"-            , "TAGame.Default__CameraSettingsActor_TA"-            ])-        , ( "TAGame.CarComponent_Boost_TA",-            [ "Archetypes.CarComponents.CarComponent_Boost"-            ])-        , ( "TAGame.CarComponent_Dodge_TA",-            [ "Archetypes.CarComponents.CarComponent_Dodge"-            ])-        , ( "TAGame.CarComponent_DoubleJump_TA",-            [ "Archetypes.CarComponents.CarComponent_DoubleJump"-            ])-        , ( "TAGame.CarComponent_FlipCar_TA",-            [ "Archetypes.CarComponents.CarComponent_FlipCar"-            ])-        , ( "TAGame.CarComponent_Jump_TA",-            [ "Archetypes.CarComponents.CarComponent_Jump"-            ])-        , ( "TAGame.Car_TA",-            [ "Archetypes.Car.Car_Default"-            , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"-            ])-        , ( "TAGame.GameEvent_Season_TA",-            [ "Archetypes.GameEvent.GameEvent_Season"-            ])-        , ( "TAGame.GameEvent_SoccarPrivate_TA",-            [ "Archetypes.GameEvent.GameEvent_BasketballPrivate"-            , "Archetypes.GameEvent.GameEvent_HockeyPrivate"-            , "Archetypes.GameEvent.GameEvent_SoccarPrivate"-            ])-        , ( "TAGame.GameEvent_SoccarSplitscreen_TA",-            [ "Archetypes.GameEvent.GameEvent_BasketballSplitscreen"-            , "Archetypes.GameEvent.GameEvent_HockeySplitscreen"-            , "Archetypes.GameEvent.GameEvent_SoccarSplitscreen"-            ])-        , ( "TAGame.GameEvent_Soccar_TA",-            [ "Archetypes.GameEvent.GameEvent_Soccar"-            , "Archetypes.GameEvent.GameEvent_Basketball"-            ])-        , ( "TAGame.GRI_TA",-            [ "GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype"-            , "Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype"-            , "GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype"-            , "GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype"-            ])-        , ( "TAGame.PRI_TA",-            [ "TAGame.Default__PRI_TA"-            ])-        , ( "TAGame.Team_Soccar_TA",-            [ "Archetypes.Teams.Team"-            ])-        , ( "TAGame.VoteActor_TA",-            [ "TAGame.Default__VoteActor_TA"-            ])-        ] & map (\ (klass, objects) ->-            ( Text.pack klass-            , objects & map Text.pack & Set.fromList-            ))-    special =-        [ ("TAGame.CrowdActor_TA", ".TheWorld:PersistentLevel.CrowdActor_TA_")-        , ("TAGame.CrowdManager_TA", ".TheWorld:PersistentLevel.CrowdManager_TA_")-        , ("TAGame.VehiclePickup_Boost_TA", ".TheWorld:PersistentLevel.VehiclePickup_Boost_TA_")-        ] & map (\ (klass, suffix) ->-            ( Text.pack klass-            , levels & Set.map (\ level -> level <> Text.pack suffix)-            ))-    in Map.fromList (normal ++ special)---- | A set of classes that have an initial location vector.-locationClasses :: Set.Set Text.Text-locationClasses =-    [ "TAGame.Ball_TA"-    , "TAGame.CameraSettingsActor_TA"-    , "TAGame.CarComponent_Boost_TA"-    , "TAGame.CarComponent_Dodge_TA"-    , "TAGame.CarComponent_DoubleJump_TA"-    , "TAGame.CarComponent_FlipCar_TA"-    , "TAGame.CarComponent_Jump_TA"-    , "TAGame.Car_Season_TA"-    , "TAGame.Car_TA"-    , "TAGame.GRI_TA"-    , "TAGame.GameEvent_Season_TA"-    , "TAGame.GameEvent_SoccarPrivate_TA"-    , "TAGame.GameEvent_SoccarSplitscreen_TA"-    , "TAGame.GameEvent_Soccar_TA"-    , "TAGame.PRI_TA"-    , "TAGame.Team_Soccar_TA"-    ] & map Text.pack & Set.fromList---- | A set of classes that have an initial rotation vector.-rotationClasses :: Set.Set Text.Text-rotationClasses =-    [ "TAGame.Ball_TA"-    , "TAGame.Car_Season_TA"-    , "TAGame.Car_TA"-    ] & map Text.pack & Set.fromList---- * Levels---- | A set of level names. This is typically not useful by itself, as it is--- used to build the 'classToObjects' map. If you are going to use it, be--- aware that many of the levels are duplicated with strange capitalization.--- For example, this set contains both @Wasteland_P@ and @Wasteland_p@.-levels :: Set.Set Text.Text-levels =-    [ "EuroStadium_Rainy_P"-    , "HoopsStadium_P"-    , "Park_Night_P"-    , "Park_Rainy_P"-    , "Stadium_p"-    , "TrainStation_Night_P"-    , "TrainStation_P"-    , "Trainstation_Night_P"-    , "UtopiaStadium_Dusk_P"-    , "UtopiaStadium_Dusk_p"-    , "UtopiaStadium_P"-    , "Utopiastadium_p"-    , "Wasteland_P"-    , "Wasteland_p"-    , "eurostad_oob_audio_map"-    , "eurostadium_p"-    , "eurostadium_rainy_audio"-    , "hoopsstadium_sfx"-    , "labs_cosmic_p"-    , "labs_doublegoal_p"-    , "labs_underpass_p"-    , "labs_utopia_p"-    , "park_night_sfx"-    , "park_p"-    , "park_rainy_sfx"-    , "park_sfx"-    , "stadium_oob_audio_map"-    , "stadium_p"-    , "stadium_winter_p"-    , "trainstation_p"-    , "utopiastadium_p"-    , "utopiastadium_sfx"-    , "wasteland_sfx"-    ] & map Text.pack & Set.fromList---- * Objects---- | A map from object names to their class names. Note that any trailing--- numbers have been stripped from the object names. So--- @Archetypes.Teams.Team0@ is in this map as @Archetypes.Teams.Team@.-objectToClass :: Map.Map Text.Text Text.Text-objectToClass = Map.foldrWithKey-    (\ klass objects m -> objects-        & Set.map (\ object -> (object, klass))-        & Set.toList-        & Map.fromList-        & Map.union m)-    Map.empty-    classToObjects---- * Properties---- | A set of properties that are booleans.-booleanProperties :: Set.Set Text.Text-booleanProperties =-    [ "Engine.Actor:bBlockActors"-    , "Engine.Actor:bCollideActors"-    , "Engine.Actor:bCollideWorld"-    , "Engine.Actor:bHardAttach"-    , "Engine.Actor:bHidden"-    , "Engine.Actor:bNetOwner"-    , "Engine.Actor:bProjTarget"-    , "Engine.Actor:bTearOff"-    , "Engine.GameReplicationInfo:bMatchIsOver"-    , "Engine.Pawn:bCanSwatTurn"-    , "Engine.Pawn:bRootMotionFromInterpCurve"-    , "Engine.Pawn:bSimulateGravity"-    , "Engine.PlayerReplicationInfo:bAdmin"-    , "Engine.PlayerReplicationInfo:bBot"-    , "Engine.PlayerReplicationInfo:bFromPreviousLevel"-    , "Engine.PlayerReplicationInfo:bIsSpectator"-    , "Engine.PlayerReplicationInfo:bOnlySpectator"-    , "Engine.PlayerReplicationInfo:bOutOfLives"-    , "Engine.PlayerReplicationInfo:bReadyToPlay"-    , "Engine.PlayerReplicationInfo:bWaitingPlayer"-    , "ProjectX.GRI_X:bGameStarted"-    , "TAGame.CameraSettingsActor_TA:bUsingBehindView"-    , "TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera"-    , "TAGame.CarComponent_Boost_TA:bNoBoost"-    , "TAGame.CarComponent_Boost_TA:bUnlimitedBoost"-    , "TAGame.CarComponent_FlipCar_TA:bFlipRight"-    , "TAGame.GameEvent_Soccar_TA:bBallHasBeenHit"-    , "TAGame.GameEvent_Soccar_TA:bOverTime"-    , "TAGame.GameEvent_TA:bHasLeaveMatchPenalty"-    , "TAGame.GameEvent_Team_TA:bDisableMutingOtherTeam"-    , "TAGame.PRI_TA:bIsInSplitScreen"-    , "TAGame.PRI_TA:bMatchMVP"-    , "TAGame.PRI_TA:bOnlineLoadoutSet"-    , "TAGame.PRI_TA:bReady"-    , "TAGame.PRI_TA:bUsingBehindView"-    , "TAGame.PRI_TA:bUsingFreecam"-    , "TAGame.PRI_TA:bUsingSecondaryCamera"-    , "TAGame.PRI_TA:bVoteToForfeitDisabled"-    , "TAGame.RBActor_TA:bFrozen"-    , "TAGame.RBActor_TA:bReplayActor"-    , "TAGame.Vehicle_TA:bDriving"-    , "TAGame.Vehicle_TA:bReplicatedHandbrake"-    ] & map Text.pack & Set.fromList---- | A set of properties that are bytes.-byteProperties :: Set.Set Text.Text-byteProperties =-    [ "Engine.PlayerReplicationInfo:Ping"-    , "TAGame.Ball_TA:HitTeamNum"-    , "TAGame.CameraSettingsActor_TA:CameraPitch"-    , "TAGame.CameraSettingsActor_TA:CameraYaw"-    , "TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount"-    , "TAGame.CarComponent_TA:ReplicatedActive"-    , "TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam"-    , "TAGame.GameEvent_TA:ReplicatedStateIndex"-    , "TAGame.PRI_TA:CameraPitch"-    , "TAGame.PRI_TA:CameraYaw"-    , "TAGame.Vehicle_TA:ReplicatedSteer"-    , "TAGame.Vehicle_TA:ReplicatedThrottle"-    ] & map Text.pack & Set.fromList---- | A set of properties that are camera settings.-camSettingsProperties :: Set.Set Text.Text-camSettingsProperties =-    [ "TAGame.CameraSettingsActor_TA:ProfileSettings"-    , "TAGame.PRI_TA:CameraSettings"-    ] & map Text.pack & Set.fromList---- | A set of properties that are demolitions.-demolishProperties :: Set.Set Text.Text-demolishProperties =-    [ "TAGame.Car_TA:ReplicatedDemolish"-    ] & map Text.pack & Set.fromList---- | A set of properties that are enumerations.-enumProperties :: Set.Set Text.Text-enumProperties =-    [ "Engine.Actor:Role"-    ] & map Text.pack & Set.fromList---- | A set of properties that are explosions.-explosionProperties :: Set.Set Text.Text-explosionProperties =-    [ "TAGame.Ball_TA:ReplicatedExplosionData"-    ] & map Text.pack & Set.fromList---- | A set of properties that are flagged integers.-flaggedIntProperties :: Set.Set Text.Text-flaggedIntProperties =-    [ "Engine.Actor:Owner"-    , "Engine.Actor:ReplicatedCollisionType"-    , "Engine.GameReplicationInfo:GameClass"-    , "Engine.Pawn:PlayerReplicationInfo"-    , "Engine.PlayerReplicationInfo:Team"-    , "Engine.TeamInfo:TeamIndex"-    , "TAGame.Ball_TA:GameEvent"-    , "TAGame.CameraSettingsActor_TA:PRI"-    , "TAGame.CarComponent_TA:Vehicle"-    , "TAGame.CrowdActor_TA:GameEvent"-    , "TAGame.CrowdActor_TA:ReplicatedOneShotSound"-    , "TAGame.CrowdManager_TA:GameEvent"-    , "TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound"-    , "TAGame.PRI_TA:PersistentCamera"-    , "TAGame.PRI_TA:ReplicatedGameEvent"-    , "TAGame.Team_TA:GameEvent"-    , "TAGame.Team_TA:LogoData"-    ] & map Text.pack & Set.fromList---- | A set of properties that are floats.-floatProperties :: Set.Set Text.Text-floatProperties =-    [ "Engine.Actor:DrawScale"-    , "TAGame.Ball_TA:ReplicatedAddedCarBounceScale"-    , "TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale"-    , "TAGame.Ball_TA:ReplicatedBallScale"-    , "TAGame.Ball_TA:ReplicatedWorldBounceScale"-    , "TAGame.CarComponent_Boost_TA:BoostModifier"-    , "TAGame.CarComponent_Boost_TA:RechargeDelay"-    , "TAGame.CarComponent_Boost_TA:RechargeRate"-    , "TAGame.CarComponent_FlipCar_TA:FlipCarTime"-    , "TAGame.CrowdActor_TA:ModifiedNoise"-    ] & map Text.pack & Set.fromList---- | A set of properties that are game modes.-gameModeProperties :: Set.Set Text.Text-gameModeProperties =-    [ "TAGame.GameEvent_TA:GameMode"-    ] & map Text.pack & Set.fromList---- | A set of properties that are integers.-intProperties :: Set.Set Text.Text-intProperties =-    [ "Engine.PlayerReplicationInfo:PlayerID"-    , "Engine.PlayerReplicationInfo:Score"-    , "Engine.TeamInfo:Score"-    , "ProjectX.GRI_X:ReplicatedGameMutatorIndex"-    , "ProjectX.GRI_X:ReplicatedGamePlaylist"-    , "TAGame.CrowdActor_TA:ReplicatedCountDownNumber"-    , "TAGame.GameEvent_Soccar_TA:RoundNum"-    , "TAGame.GameEvent_Soccar_TA:SecondsRemaining"-    , "TAGame.GameEvent_TA:BotSkill"-    , "TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining"-    , "TAGame.GameEvent_TA:ReplicatedStateName"-    , "TAGame.GameEvent_Team_TA:MaxTeamSize"-    , "TAGame.PRI_TA:MatchAssists"-    , "TAGame.PRI_TA:MatchGoals"-    , "TAGame.PRI_TA:MatchSaves"-    , "TAGame.PRI_TA:MatchScore"-    , "TAGame.PRI_TA:MatchShots"-    , "TAGame.PRI_TA:Title"-    , "TAGame.PRI_TA:TotalXP"-    , "TAGame.Team_Soccar_TA:GameScore"-    ] & map Text.pack & Set.fromList---- | A set of properties that are online loadouts.-loadoutOnlineProperties :: Set.Set Text.Text-loadoutOnlineProperties =-    [ "TAGame.PRI_TA:ClientLoadoutOnline"-    ] & map Text.pack & Set.fromList---- | A set of properties that are loadouts.-loadoutProperties :: Set.Set Text.Text-loadoutProperties =-    [ "TAGame.PRI_TA:ClientLoadout"-    ] & map Text.pack & Set.fromList---- | A set of properties that are locations.-locationProperties :: Set.Set Text.Text-locationProperties =-    [ "Engine.Actor:RelativeLocation"-    , "TAGame.CarComponent_Dodge_TA:DodgeTorque"-    ] & map Text.pack & Set.fromList---- | A set of properties that are music stingers.-musicStingerProperties :: Set.Set Text.Text-musicStingerProperties =-    [ "TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger"-    ] & map Text.pack & Set.fromList---- | A set of properties that are pickups.-pickupProperties :: Set.Set Text.Text-pickupProperties =-    [ "TAGame.VehiclePickup_TA:ReplicatedPickupData"-    ] & map Text.pack & Set.fromList---- | A set of properties that are private match settings.-privateMatchSettingsProperties :: Set.Set Text.Text-privateMatchSettingsProperties =-    [ "TAGame.GameEvent_SoccarPrivate_TA:MatchSettings"-    ] & map Text.pack & Set.fromList---- | A set of properties that are Q words, whatever those are.-qWordProperties :: Set.Set Text.Text-qWordProperties =-    [ "ProjectX.GRI_X:GameServerID"-    ] & map Text.pack & Set.fromList---- | A set of properties that are relation rotations.-relativeRotationProperties :: Set.Set Text.Text-relativeRotationProperties =-    [ "Engine.Actor:RelativeRotation"-    ] & map Text.pack & Set.fromList---- | A set of properties that are reservations.-reservationProperties :: Set.Set Text.Text-reservationProperties =-    [ "ProjectX.GRI_X:Reservations"-    ] & map Text.pack & Set.fromList---- | A set of properties that are rigid body states.-rigidBodyStateProperties :: Set.Set Text.Text-rigidBodyStateProperties =-    [ "TAGame.RBActor_TA:ReplicatedRBState"-    ] & map Text.pack & Set.fromList---- | A set of properties that are strings.-stringProperties :: Set.Set Text.Text-stringProperties =-    [ "Engine.GameReplicationInfo:ServerName"-    , "Engine.PlayerReplicationInfo:PlayerName"-    , "Engine.PlayerReplicationInfo:RemoteUserData"-    , "TAGame.GRI_TA:NewDedicatedServerIP"-    , "TAGame.Team_TA:CustomTeamName"-    ] & map Text.pack & Set.fromList---- | A set of properties that are team paints.-teamPaintProperties :: Set.Set Text.Text-teamPaintProperties =-    [ "TAGame.Car_TA:TeamPaint"-    ] & map Text.pack & Set.fromList---- | A set of properties that are unique IDs.-uniqueIdProperties :: Set.Set Text.Text-uniqueIdProperties =-    [ "Engine.PlayerReplicationInfo:UniqueId"-    -- , "TAGame.PRI_TA:PartyLeader"-    ] & map Text.pack & Set.fromList---- * Garage---- | A one-to-one mapping between body IDs and their names.-bodies :: Bimap.Bimap Int Text.Text-bodies =-    [ ("Armadillo", 625) -- GearsCar-    , ("Backfire", 21)-    , ("Batmobile", 803) -- Darkcar-    , ("Breakout", 22) -- Force-    , ("DeLorean Time Machine", 597) -- GreyCar-    , ("Dominus", 403) -- MuscleCar-    , ("Gizmo", 26) -- Spark-    , ("Grog", 607) -- WastelandTruck-    , ("Hogsticker", 723) -- Warthog-    , ("Hotshot", 29) -- Torment-    , ("Merc", 30) -- Vanquish-    , ("Octane", 23)-    , ("Paladin", 24) -- Orion-    , ("Ripper", 600) -- Interceptor-    , ("Road Hog", 25) -- Rhino-    , ("Scarab", 404)-    , ("Sweet Tooth", 27)-    , ("Takumi", 402) -- Import-    , ("Venom", 31)-    , ("X-Devil", 28) -- Torch-    , ("Zippy", 523)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between decal IDs and their names.-decals :: Bimap.Bimap Int Text.Text-decals =-    [ ( "Backfire",-        [ ("Flames", 288)-        , ("Lightning", 289)-        , ("Skulls", 290)-        , ("Stars", 291)-        , ("Stripes", 292)-        , ("Tech", 293)-        , ("Wings", 294)-        ])-    , ( "Breakout",-        [ ("Flames", 295)-        , ("Lightning", 296)-        , ("Skulls", 297)-        , ("Stars", 298)-        , ("Stripes", 299)-        , ("Tech", 300)-        , ("Wings", 301)-        ])-    , ( "Dominus",-        [ ("Flames", 504)-        , ("Scorpions", 506) -- Skorpion-        , ("Skulls", 507)-        , ("Stripes", 508)-        , ("Tats", 509) -- Tribal-        , ("Wings", 510)-        ])-    , ( "Gizmo",-        [ ("Flames", 323)-        , ("Lightning", 324)-        , ("Skulls", 325)-        , ("Stars", 326)-        , ("Stripes", 327)-        , ("Tech", 328)-        , ("Wings", 329)-        ])-    , ( "Grog",-        [ ("Bomber", 674)-        , ("Cyclops", 694)-        , ("Lepus", 675) -- Bunny-        , ("Stripes", 687)-        , ("Tagged", 695)-        , ("Tribal", 696)-        ])-    , ( "Hotshot",-        [ ("Flames", 337)-        , ("Lightning", 338)-        , ("Skulls", 339)-        , ("Stars", 340)-        , ("Stripes", 341)-        , ("Tech", 342)-        , ("Wings", 343)-        ])-    , ( "Merc",-        [ ("Dots", 344)-        , ("Flames", 345)-        , ("Lightning", 346)-        , ("Skulls", 347)-        , ("Stars", 348)-        , ("Stripes", 349)-        , ("Wings", 350)-        ])-    , ( "Octane",-        [ ("Flames", 302)-        , ("Lightning", 303)-        , ("Skulls", 304)-        , ("Stars", 305)-        , ("Stripes", 306)-        , ("Tech", 307)-        , ("Wings", 308)-        ])-    , ( "Paladin",-        [ ("Flames", 309)-        , ("Lightning", 310)-        , ("Skulls", 311)-        , ("Stars", 312)-        , ("Stripes", 313)-        , ("Tech", 314)-        , ("Wings", 315)-        ])-    , ( "Ripper",-        [ ("Bomber", 701) -- Sandgirl-        , ("Flames", 669)-        , ("Ockie", 700) -- Getkraken-        , ("Shot Fox", 670) -- Kitsune-        , ("Spikes", 671)-        , ("Tribal", 672)-        ])-    , ( "Road Hog",-        [ ("Flames", 316)-        , ("Lightning", 317)-        , ("Skulls", 318)-        , ("Stars", 319)-        , ("Stripes", 320)-        , ("Tech", 321) -- TechSplat-        , ("Wings", 322)-        ])-    , ( "Scarab",-        [ ("Bomani", 585) -- Clouds-        , ("Derby Girl", 592) -- RollerDerby-        , ("Flames", 591)-        , ("Hearts", 586)-        , ("Tiger", 587)-        , ("Tribal", 588)-        ])-    , ( "Takumi",-        [ ("Chaser", 503) -- Wisp-        , ("Copycat", 497) -- Gato-        , ("Crazy-8", 502) -- Technologic-        , ("Gaki", 499) -- Ohai-        , ("Reiko", 498) -- Nitrous-        , ("Stripes", 501)-        ])-    , ( "Venom",-        [ ("Dots", 351)-        , ("Flames", 352)-        , ("Lightning", 353)-        , ("Skulls", 354)-        , ("Stripes", 355)-        , ("Tagged", 356)-        , ("Wings", 357)-        ])-    , ( "X-Devil",-        [ ("Flames", 330)-        , ("Lightning", 331)-        , ("Skulls", 332)-        , ("Stars", 333)-        , ("Stripes", 334)-        , ("Tech", 335)-        , ("Wings", 336)-        ])-    , ( "Zippy",-        [ ("Caboodle", 589) -- HeckTec-        , ("Callous", 590) -- MeanMachine-        , ("Flames", 583)-        , ("Hearts", 581)-        , ("Leopard", 584)-        , ("Tiger", 582)-        ])-    ]-        & map (\ (body, values) -> values &-            map (\ (v, k) -> (k, v ++ " [" ++ body ++ "]")))-        & concat-        & map (\ (k, v) -> (k, Text.pack v))-        & Bimap.fromList-        & Bimap.insert 0 (Text.pack "None")---- | A one-to-one mapping between wheels IDs and their names.-wheels :: Bimap.Bimap Int Text.Text-wheels =-    [ ("(Alpha Reward) Goldstone", 358) -- AlphaRim-    , ("Alchemist", 374) -- Revolution-    , ("Almas", 364) -- Crypt-    , ("Armadillo", 618)-    , ("Batmobile", 874) -- Darkcar-    , ("Bender", 360) -- Ballistic-    , ("Carriage", 549)-    , ("Cog", 716) -- TODO: What is this called in game?-    , ("Cristiano", 386) -- SoccerBall-    , ("DeLorean Time Machine", 609) -- GreyCar-    , ("Dieci", 363) -- Caliber-    , ("Falco", 382) -- Vulcan-    , ("Foreman", 372) -- OffRoad-    , ("Grog", 613) -- WastelandTruck-    , ("Hogsticker", 719) -- Warthog-    , ("Invader", 380) -- Triad-    , ("Lowrider", 369)-    , ("Lucci", 361) -- Bling-    , ("Mountaineer", 375) -- SnowTire-    , ("Neptune", 370) -- Mob-    , ("Octavian", 379) -- Tarantula-    , ("OEM", 376) -- Star-    , ("Rat Rod", 366) -- Dynamo-    , ("Ripper", 690) -- Spiked-    , ("Scarab", 540) -- Scarab-    , ("Servergate", 519) -- SkullxBones-    , ("Spinner", 388) -- Spinner-    , ("Spyder", 371) -- Ninja-    , ("Stallion", 365) -- DeepDish-    , ("Stern", 368) -- Hydra-    , ("Sunburt", 383) -- Wynd-    , ("Sweet Tooth", 378)-    , ("Tempest", 377) -- Storm-    , ("Tomahawk", 362) -- Brink-    , ("Trahere", 367) -- Forge-    , ("Tunica", 359) -- Atlantis-    , ("Veloce", 373) -- Pedigree-    , ("Vortex", 381)-    , ("Zippy", 518) -- SingleSpoke-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between rocket trail IDs and their names.-rocketTrails :: Bimap.Bimap Int Text.Text-rocketTrails =-    [ ("(Alpha Reward) Gold Rush", 32) -- AlphaReward-    , ("Accelerato", 524) -- MusicalNotes-    , ("Batmobile", 868) -- DarkCar-    , ("Battle-Stars", 545)-    , ("Bubbles", 33)-    , ("Burnout", 387)-    , ("Candy Corn", 578)-    , ("Confetti", 34)-    , ("Datastream", 35) -- Digital-    , ("Flamethrower Blue", 37)-    , ("Flamethrower Green", 38)-    , ("Flamethrower Pink", 39)-    , ("Flamethrower Purple", 40)-    , ("Flamethrower Red", 41)-    , ("Flamethrower", 36)-    , ("Flowers", 42)-    , ("Grass", 43)-    , ("Hydro", 69) -- Water-    , ("Ion Blue", 45) -- LightTrail-    , ("Ion Green", 46)-    , ("Ion Pink", 47)-    , ("Ion Purple", 48)-    , ("Ion Red", 49)-    , ("Ion Yellow", 50)-    , ("Money", 51)-    , ("Nitrous", 384)-    , ("Nuts & Bolts", 544)-    , ("OutaTime", 626) -- GreyCar-    , ("Plasma", 52)-    , ("Portal - Conversion Gel", 651)-    , ("Portal - Propulsion Gel", 652)-    , ("Portal - Reuplsion Gel", 653)-    , ("Rainbow", 59)-    , ("Sacred", 44) -- HolyLight-    , ("Sandstorm", 664)-    , ("Slime", 60)-    , ("Snowflakes", 61)-    , ("Sparkles", 62)-    , ("Standard Blue", 64)-    , ("Standard Pink", 65)-    , ("Standard Purple", 66)-    , ("Standard Red", 63)-    , ("Standard Yellow", 67)-    , ("Sweet Tooth", 68)-    , ("Thermal Blue", 54)-    , ("Thermal Green", 55)-    , ("Thermal Pink", 56)-    , ("Thermal Purple", 57)-    , ("Thermal Yellow", 58)-    , ("Thermal", 53) -- Propulsion-    , ("Xmas", 635) -- Presents-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between antenna IDs and their names.-antennas :: Bimap.Bimap Int Text.Text-antennas =-    [ ("None", 0)-    , ("(Beta Reward) Gold Nugget", 6)-    , ("8-Ball", 1)-    , ("Alien", 753)-    , ("Balloon Dog", 752)-    , ("Batman", 804)-    , ("Blue Chequered Flag", 284)-    , ("Bomb Pole", 614)-    , ("Calavera", 574) -- DOTDSkull-    , ("Camo Flag", 285)-    , ("Candle", 757)-    , ("Candy Cane", 649)-    , ("Chick Magnet", 392)-    , ("Cupcake", 393)-    , ("Dave's Bread", 391)-    , ("deadmau5", 781) -- Mau5-    , ("Disco Ball", 800)-    , ("Disconnect", 394)-    , ("Dollar Sign", 3)-    , ("Donut", 395)-    , ("Foam Finger", 398)-    , ("Fuzzy Brute", 593) -- FurballFrakenstein-    , ("Fuzzy Vamp", 595) -- FurballVampire-    , ("Genie Lamp", 798)-    , ("Gingerbread Man", 5)-    , ("Heart", 7)-    , ("Holiday Gift", 650) -- ChristmasGift-    , ("Horseshoe", 8)-    , ("Hula Girl", 762)-    , ("Jolly Roger", 217) -- Pirate-    , ("Lightning Bolt", 9)-    , ("Moai", 522)-    , ("Parrot", 750)-    , ("Peace", 11)-    , ("Piñata", 400)-    , ("Planetoid", 12) -- Saturn-    , ("Radioactive", 615) -- RadiationSymbol-    , ("Rainbow", 490)-    , ("Reddit Snoo", 629)-    , ("Retro Ball - Urban", 575)-    , ("Retro Ball - Utopia", 576)-    , ("Retro Ball - Wasteland", 577)-    , ("Rocket", 755)-    , ("Rose", 785)-    , ("Rubber Duckie", 779)-    , ("Safety Flag", 286) -- OrangeNylon-    , ("Skull", 13)-    , ("Smiley", 14)-    , ("Snowman", 15)-    , ("Soccer Ball", 16)-    , ("Star", 17)-    , ("Sunflower", 786)-    , ("Superman", 805)-    , ("Sweet Tooth", 18)-    , ("Tennis Ball", 19)-    , ("The Game Awards - Statue", 655) -- VGA_Statue-    , ("UFO", 20)-    , ("Venus Flytrap", 788)-    , ("Waffle", 401)-    , ("White Flag", 491)-    , ("Wonder Woman", 806)-    -- TODO: What are these called in game?-    , ("Community", 527)-    , ("ESL_Champ", 537)-    , ("Eyeball", 396)-    , ("Moderator", 528)-    , ("PanicButton", 829)-    , ("Psyonix", 287)-    , ("TourneyChamp", 539)-    , ("Translator", 530)-    -- Community Flags-    , ("9GAG", 799)-    , ("AbleGamers", 814)-    , ("Achievement Hunter", 610)-    , ("Angry Army", 548) -- AgryJoe-    , ("Anne Munition", 531)-    , ("Day[9]TV", 654)-    , ("Destructoid", 724)-    , ("EMD1", 532) -- EatMyDiction-    , ("ESL", 536)-    , ("Funhaus", 611)-    , ("Gamespot", 725)-    , ("Gassy Mexican", 511)-    , ("Gfinity", 732)-    , ("GoldGlove", 728)-    , ("IGN", 802)-    , ("itmeJP", 533)-    , ("LIRIK", 513)-    , ("lolRenaynay", 628)-    , ("MLG", 538)-    , ("NeoGAF", 616)-    , ("NVIDIA", 808)-    , ("Operation Sports", 726)-    , ("PC Gamer", 667)-    , ("Polaris", 608)-    , ("Razer", 573)-    , ("Reddit", 630)-    , ("RL Garage", 645)-    , ("Rocket Beans TV", 869)-    , ("Rooster Teeth", 612)-    , ("Saudi Gamer", 534)-    , ("Serious Gaming", 572)-    , ("Something Awful", 699)-    , ("Spooky1611", 514)-    , ("Team Fat", 220)-    , ("Team PowA", 515)-    , ("The Attack", 535)-    , ("The Game Awards - Logo", 693) -- VGX-    , ("Twitch", 221)-    , ("YouTube Gaming", 516)-    -- Country Flags-    , ("Afghanistan", 407)-    , ("Albania", 408)-    , ("Algeria", 124)-    , ("American Samoa", 409)-    , ("Andorra", 830)-    , ("Angola", 410)-    , ("Anguilla", 831)-    , ("Antigua & Barbuda", 832)-    , ("Argentina", 125)-    , ("Armenia", 411)-    , ("Aruba", 833)-    , ("Australia", 126)-    , ("Austria", 127)-    , ("Azerbaijan", 412)-    , ("Bahamas", 834)-    , ("Bahrain", 550)-    , ("Bangladesh", 413)-    , ("Barbados", 835)-    , ("Belarus", 414)-    , ("Belgium", 128)-    , ("Belize", 551)-    , ("Benin", 415)-    , ("Bermuda", 836)-    , ("Bhutan", 552)-    , ("Bolivia", 416)-    , ("Bosnia and Herzegovina", 129)-    , ("Botswana", 553)-    , ("Brazil", 130)-    , ("British Virgin Islands", 837)-    , ("Bulgaria", 131)-    , ("Burkina Faso", 417)-    , ("Burma", 418)-    , ("Burundi", 419)-    , ("Cambodia", 420)-    , ("Cameroon", 132)-    , ("Canada", 133)-    , ("Cape Verde Islands", 134)-    , ("Cayman Islands", 838)-    , ("Central African Republic", 540)-    , ("Chad", 422)-    , ("Chile", 135)-    , ("China", 136)-    , ("Chinese Taipei", 198)-    , ("Colombia", 137)-    , ("Comoros", 554)-    , ("Congo", 138)-    , ("Congo DR", 423)-    , ("Cook Islands", 839)-    , ("Costa Rica", 139)-    , ("Croatia", 140)-    , ("Cuba", 424)-    , ("Curacao", 840)-    , ("Cyprus", 141)-    , ("Czech Republic", 142)-    , ("Côte d'Ivoire", 165)-    , ("Denmark", 143)-    , ("Djibouti", 555)-    , ("Dominica", 841)-    , ("Dominican Republic", 425)-    , ("East Timor", 556)-    , ("Ecuador", 144)-    , ("Egypt", 426)-    , ("El Salvador", 145)-    , ("England", 146)-    , ("Equatorial Guinea", 147)-    , ("Eritrea", 427)-    , ("Estonia", 428)-    , ("Ethiopia", 429)-    , ("Falkland Islands", 842)-    , ("Faroe Islands", 843)-    , ("Fiji", 557)-    , ("Finland", 148)-    , ("France", 149)-    , ("Gabon", 430)-    , ("Gambia", 558)-    , ("Georgia", 431)-    , ("Germany", 150)-    , ("Ghana", 151)-    , ("Gibraltar", 844)-    , ("Greece", 152)-    , ("Grenada", 863)-    , ("Guam", 432)-    , ("Guatemala", 153)-    , ("Guinea", 154)-    , ("Guinea Bissau", 559)-    , ("Guyana", 560)-    , ("Haiti", 433)-    , ("Honduras", 155)-    , ("Hong Kong", 156)-    , ("Hungary", 157)-    , ("Iceland", 158)-    , ("India", 159)-    , ("Indonesia", 160)-    , ("Iran", 161)-    , ("Iraq", 434)-    , ("Ireland", 162)-    , ("Isle Of Man", 845)-    , ("Israel", 163)-    , ("Italy", 164)-    , ("Jamaica", 166)-    , ("Japan", 167)-    , ("Jordan", 435)-    , ("Kazakhstan", 436)-    , ("Kenya", 168)-    , ("Kiribati", 846)-    , ("Kosovo", 561)-    , ("Kuwait", 437)-    , ("Kyrgyzstan", 438)-    , ("Laos", 439)-    , ("Latvia", 440)-    , ("Lebanon", 441)-    , ("Lesotho", 562)-    , ("Liberia", 442)-    , ("Libya", 443)-    , ("Lithuania", 444)-    , ("Luxembourg", 169)-    , ("Macau", 563)-    , ("Macedonia", 445)-    , ("Madagascar", 446)-    , ("Malawi", 447)-    , ("Malaysia", 170)-    , ("Maldives", 847)-    , ("Mali", 448)-    , ("Malta", 171)-    , ("Marshall Islands", 848)-    , ("Mauritania", 449)-    , ("Mauritius", 564)-    , ("Mexico", 172)-    , ("Micronesia", 849)-    , ("Moldova", 450)-    , ("Mongolia", 451)-    , ("Montenegro", 452)-    , ("Montserrat", 864)-    , ("Morocco", 453)-    , ("Mozambique", 454)-    , ("Namibia", 457)-    , ("Nepal", 458)-    , ("Netherlands", 173)-    , ("New Caledonia", 850)-    , ("New Zealand", 174)-    , ("Nicaragua", 175)-    , ("Nigeria", 176)-    , ("North Korea", 460)-    , ("North Mariana Islands", 456)-    , ("Northern Ireland", 177)-    , ("Norway", 178)-    , ("Oman", 461)-    , ("Palestine", 462)-    , ("Palau", 851)-    , ("Palestine", 462)-    , ("Panama", 180)-    , ("Papua New Guinea", 463)-    , ("Paraguay", 464)-    , ("Peru", 181)-    , ("Philippines", 182)-    , ("Poland", 183)-    , ("Portugal", 184)-    , ("Puerto Rico", 465)-    , ("Puntland", 565)-    , ("Qatar", 466)-    , ("Republic of Niger", 459)-    , ("Romania", 185)-    , ("Russia", 186)-    , ("Rwanda", 467)-    , ("Réunion", 566)-    , ("Saint Kitts & Nevis", 852)-    , ("Saint Lucia", 853)-    , ("Saint Vincent", 854)-    , ("Samoa", 855)-    , ("San Marino", 856)-    , ("Sao Tome", 857)-    , ("Saudi Arabia", 468)-    , ("Scotland", 187)-    , ("Senegal", 188)-    , ("Serbia", 189)-    , ("Seychelles", 858)-    , ("Sierra Leone", 469)-    , ("Singapore", 190)-    , ("Slovakia", 191)-    , ("Slovenia", 192)-    , ("Solomon Islands", 567)-    , ("Somalia", 470)-    , ("Somaliland", 568)-    , ("South Africa", 193)-    , ("South Korea", 194)-    , ("South Sudan", 471)-    , ("Spain", 195)-    , ("Sri Lanka", 389)-    , ("Sudan", 472)-    , ("Suriname", 569)-    , ("Swaziland", 570)-    , ("Sweden", 196)-    , ("Switzerland", 197)-    , ("Syria", 473)-    , ("Tahiti", 859)-    , ("Tajikistan", 474)-    , ("Tanzania", 475)-    , ("Thailand", 199)-    , ("Togo", 476)-    , ("Tonga", 730)-    , ("Trinidad and Tobago", 477)-    , ("Tunisia", 200)-    , ("Turkey", 201)-    , ("Turkmenistan", 478)-    , ("Turks & Caicos", 860)-    , ("Uganda", 479)-    , ("Ukraine", 204)-    , ("United Arab Emirates", 202)-    , ("United Kingdom", 203)-    , ("United States", 206)-    , ("Uruguay", 502)-    , ("US Virgin Islands", 480)-    , ("Uzbekistan", 481)-    , ("Vanuatu", 861)-    , ("Vatican City", 862)-    , ("Venezuela", 207)-    , ("Vietnam", 208)-    , ("Wales", 209)-    , ("Western Sahara", 571)-    , ("Yemen", 210)-    , ("Zambia", 482)-    , ("Zimbabwe", 483)-    -- NBA Flags-    , ("NBA", 1263)-    , ("Atlanta Hawks", 1245)-    , ("Boston Celtics", 1246)-    , ("Brooklyn Nets", 1247)-    , ("Charlotte Hornets", 1248)-    , ("Chicago Bulls", 1249)-    , ("Cleveland Cavaliers", 1250)-    , ("Dallas Mavericks", 1251)-    , ("Denver Nuggets", 1252)-    , ("Detroit Pistons", 1253)-    , ("Golden State Warriors", 1254)-    , ("Houston Rockets", 1255)-    , ("Indian Pacers", 1256)-    , ("Los Angeles Clippers", 1257)-    , ("Los Angeles Lakers", 1258)-    , ("Memphis Grizzlies", 1259)-    , ("Miami Heat", 1260)-    , ("Milwaukee Bucks", 1261)-    , ("Minnesota Timberwolves", 1262)-    , ("New Orleans Pelicans", 1264)-    , ("New Tork Knicks", 1265)-    , ("Oklahoma City Thunder", 1266)-    , ("Orlando Magic", 1267)-    , ("Philadelphia 76ers", 1268)-    , ("Phoenix Suns", 1269)-    , ("Portland Trail Blazers", 1270)-    , ("Sacramento Kings", 1271)-    , ("San Antonio Spurs", 1272)-    , ("Toronto Raptors", 1273)-    , ("Utah Jazz", 1274)-    , ("Washington Wizards", 1275)-    -- Video Games-    , ("Blacklight", 385)-    , ("Blacklight: Retribution", 211)-    , ("Chivalry - Agatha Knights", 525)-    , ("Chivalry - Mason Order", 526)-    , ("Edge Of Space", 213)-    , ("Euro Truck Simulator Rig", 813) -- ETS2-    , ("Fallout - Vault Boy", 673)-    , ("Fenix Rage", 216)-    , ("Goat Simulator - G2", 927)-    , ("Goat Simulator - Goatenna", 867)-    , ("Oddworld - Abe", 678)-    , ("Oddworld - Molluck", 679)-    , ("Oddworld - Necrum", 632)-    , ("Oddworld - RuptureFarms", 633)-    , ("Portal - Aperture Laboratories", 656)-    , ("Portal - Cake Sticker", 657)-    , ("Portal - Companion Cube", 681)-    , ("Portal - PotatOS", 698)-    , ("Portal - Wheatley", 686) -- Portal_PC-    , ("Shadowgate", 218)-    , ("Strike Vector EX", 219)-    , ("Unreal", 222)-    , ("Unreal Frag Center", 485)-    , ("Unreal Tournament", 489) -- UTBlue-    , ("Unreal Tournament (Classic)", 487) -- UT-    , ("Unreal Tournament - Blue", 484) -- Epic_BlueFlag-    , ("Unreal Tournament - Flak Shell", 397)-    , ("Unreal Tournament - Red", 486) -- Epic_RedFlag-    , ("Unreal Tournament 2004", 488)-    , ("Warframe", 223)-    , ("Warframe - Chroma", 682)-    , ("Warframe - Excalibur", 684)-    , ("Warframe - Loki", 683)-    , ("Witcher Medallion", 749)-    , ("Worms W.M.D", 1008)-    , ("Worms W.M.D. Grenade", 1000)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between topper IDs and their names.-toppers :: Bimap.Bimap Int Text.Text-toppers =-    [ ("None", 0)-    , ("(Alpha Reward) Gold Cap", 224) -- AlphaHat-    , ("Antlers", 774)-    , ("Beret", 775)-    , ("Biker Cap", 776)-    , ("Birthday Cake", 789)-    , ("Blitzen", 640) -- ReindeerAntlers-    , ("Bobby Helmet", 227) -- BPH-    , ("Boombox", 521)-    , ("Bowler", 745) -- GentlemanHat-    , ("Brodie Helmet", 743) -- DoughboyHelmet-    , ("Brünnhilde", 241) -- Viking_Helmet-    , ("Captain's Hat", 741)-    , ("Cavalier", 580) -- Tricorn-    , ("Chainsaw", 795)-    , ("Chef's Hat", 756)-    , ("Cherry Top", 237) -- PoliceSiren-    , ("Christmas Tree", 639) -- RegularChristmasTree-    , ("Cockroach", 769)-    , ("Cow Skull", 685)-    , ("deadmau5", 796)-    , ("Deerstalker", 792)-    , ("Derby", 790)-    , ("Devil Horns", 229)-    , ("Fez", 230)-    , ("Fire Helmet", 231)-    , ("Foam Hat", 390) -- TourneyWinner-    , ("Fruit Hat", 744)-    , ("Graduation Cap", 778)-    , ("Halo", 232)-    , ("Hard Hat", 233)-    , ("Homburg", 742) -- DetectiveHat-    , ("Locomotive", 547) -- TrainConductor-    , ("Mariachi Hat", 234)-    , ("Mohawk", 638)-    , ("Mouse Trap", 782) -- RatTrap-    , ("Paper Boat", 770)-    , ("Pirate's Hat", 235)-    , ("Pixelated Shades", 494) -- DWI-    , ("Pizza Topper", 236)-    , ("Plunger", 772)-    , ("Police Hat", 746)-    , ("Portal - Cake", 661)-    , ("Propellerhead", 225) -- Beanie-    , ("Pumpkin", 546) -- CatOLantern-    , ("Rasta", 771)-    , ("Rhino Horns", 780)-    , ("Royal Crown", 228)-    , ("Sad Sapling", 641) -- SadChristmasTree-    , ("Santa", 642)-    , ("Season 1 - Bronze", 705)-    , ("Season 1 - Gold", 706)-    , ("Season 1 - Platinum", 707)-    , ("Season 1 - Silver", 708)-    , ("Shark Fin", 579)-    , ("Shuriken", 773)-    , ("Sombrero", 238)-    , ("Taxi Topper", 239)-    , ("Tiara", 495)-    , ("Top Hat", 240)-    , ("Traffic Cone", 747)-    , ("Unicorn", 748) -- Unihorn-    , ("Witch's Hat", 242)-    , ("Wizard Hat", 243)-    , ("Work Boot", 740)-    , ("Worms W.M.D.", 1028)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between paint finish IDs and their names.-finishes :: Bimap.Bimap Int Text.Text-finishes =-    [ ("Brushed Metal", 266)-    , ("Camo", 541)-    , ("Carbon Fiber", 272) -- GlossyCarbonFiber-    , ("Corroded Metal", 268) -- Corroded-    , ("DeLorean Time Machine", 623) -- GreyCar-    , ("Glossy", 270) -- Default-    , ("Matte", 273)-    , ("Metallic", 274)-    , ("Metallic Pearl", 275)-    , ("Pearlescent", 276)-    , ("Semigloss", 277) -- Plastic-    , ("Sun-Damaged", 542) -- Cracked-    , ("Toon Glossy", 279)-    , ("Toon Matte", 281)-    , ("Toon Wood", 280)-    , ("Wood", 283)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- * Misc---- | A one-to-one mapping between bot IDs and their names.-bots :: Bimap.Bimap Int Text.Text-bots =-    [ ("Astro", 70)-    , ("Bane", 71)-    , ("Beast", 72)-    , ("Blade", 73)-    , ("C-Block", 74)-    , ("Captain", 75)-    , ("Caveman", 76)-    , ("Cougar", 77)-    , ("Devil", 78)-    , ("Diesel", 79)-    , ("Dotty", 80)-    , ("Dude", 81)-    , ("Duke", 82)-    , ("Dynamite", 83)-    , ("Flame", 84)-    , ("Flash", 85)-    , ("Fossil", 86)-    , ("Fury", 87)-    , ("Hawk", 88)-    , ("Hunter", 89)-    , ("Imp", 90)-    , ("Jet", 91)-    , ("JM", 92)-    , ("Kidd", 93)-    , ("Laser", 94)-    , ("Lightning", 95)-    , ("Lucky", 96)-    , ("Middy", 97)-    , ("Mountain", 98)-    , ("Nitro", 99)-    , ("Outlaw", 100)-    , ("Phoenix", 101)-    , ("Raja", 102)-    , ("Razor", 103)-    , ("Retro", 104)-    , ("Roundhouse", 105)-    , ("Sabre", 106)-    , ("Samson", 107)-    , ("Savage", 108)-    , ("Shadow", 109)-    , ("Shield", 110)-    , ("Siren", 111)-    , ("Sky", 112)-    , ("Squall", 113)-    , ("Tank", 114)-    , ("Thunder", 115)-    , ("Titan", 116)-    , ("Turbo", 117)-    , ("Tusk", 118)-    , ("Viper", 119)-    , ("Warrior", 120)-    , ("Wolf", 121)-    , ("Yuri", 122)-    , ("Zap", 123)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList---- | A one-to-one mapping between logo IDs and their names.-logos :: Bimap.Bimap Int Text.Text-logos =-    [ ("Barracudas", 244)-    , ("Blacklight", 245)-    , ("Bombers", 246)-    , ("Cyclones", 247)-    , ("Destroyers", 248)-    , ("Dragons", 249)-    , ("Express", 250)-    , ("Guardians", 251)-    , ("Knights", 252)-    , ("Kodiaks", 253)-    , ("Mammoths", 254)-    , ("Monarchs", 255)-    , ("Phantoms", 256)-    , ("Pharaohs", 257)-    , ("Pioneers", 258)-    , ("Ravagers", 259)-    , ("Rebels", 260)-    , ("Rovers", 261)-    , ("Scorpions", 262)-    , ("Skyhawks", 263)-    , ("Wolves", 264)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList--gameModes :: Bimap.Bimap Int Text.Text-gameModes =-    [ ("Soccar", 0)-    , ("Hockey", 1)-    , ("Hoops", 2)-    ] & map (\ (v, k) -> (k, Text.pack v)) & Bimap.fromList+module Octane.Data+    ( module Octane.Data.Antennas+    , module Octane.Data.Bodies+    , module Octane.Data.Bots+    , module Octane.Data.Classes+    , module Octane.Data.Decals+    , module Octane.Data.Finishes+    , module Octane.Data.GameModes+    , module Octane.Data.Logos+    , module Octane.Data.Properties+    , module Octane.Data.RocketTrails+    , module Octane.Data.Toppers+    , module Octane.Data.Wheels+    ) where++import Octane.Data.Antennas+import Octane.Data.Bodies+import Octane.Data.Bots+import Octane.Data.Classes+import Octane.Data.Decals+import Octane.Data.Finishes+import Octane.Data.GameModes+import Octane.Data.Logos+import Octane.Data.Properties+import Octane.Data.RocketTrails+import Octane.Data.Toppers+import Octane.Data.Wheels
+ library/Octane/Data/Antennas.hs view
@@ -0,0 +1,435 @@+module Octane.Data.Antennas (antennas) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between antenna IDs and their names.+antennas :: Bimap.Bimap Int StrictText.Text+antennas =+    [ ("None", 0)+    , ("(Beta Reward) Gold Nugget", 6)+    , ("8-Ball", 1)+    , ("Alien", 753)+    , ("Balloon Dog", 752)+    , ("Batman", 804)+    , ("Blue Chequered Flag", 284)+    , ("Bomb Pole", 614)+    , ("Calavera", 574) -- DOTDSkull+    , ("Camo Flag", 285)+    , ("Candle", 757)+    , ("Candy Cane", 649)+    , ("Chick Magnet", 392)+    , ("Cupcake", 393)+    , ("Dave's Bread", 391)+    , ("deadmau5", 781) -- Mau5+    , ("Disco Ball", 800)+    , ("Disconnect", 394)+    , ("Dollar Sign", 3)+    , ("Donut", 395)+    , ("Foam Finger", 398)+    , ("Fuzzy Brute", 593) -- FurballFrakenstein+    , ("Fuzzy Vamp", 595) -- FurballVampire+    , ("Genie Lamp", 798)+    , ("Gingerbread Man", 5)+    , ("Heart", 7)+    , ("Holiday Gift", 650) -- ChristmasGift+    , ("Horseshoe", 8)+    , ("Hula Girl", 762)+    , ("Jolly Roger", 217) -- Pirate+    , ("Lightning Bolt", 9)+    , ("Moai", 522)+    , ("Parrot", 750)+    , ("Peace", 11)+    , ("Piñata", 400)+    , ("Planetoid", 12) -- Saturn+    , ("Radioactive", 615) -- RadiationSymbol+    , ("Rainbow", 490)+    , ("Reddit Snoo", 629)+    , ("Retro Ball - Urban", 575)+    , ("Retro Ball - Utopia", 576)+    , ("Retro Ball - Wasteland", 577)+    , ("Rocket", 755)+    , ("Rose", 785)+    , ("Rubber Duckie", 779)+    , ("Safety Flag", 286) -- OrangeNylon+    , ("Skull", 13)+    , ("Smiley", 14)+    , ("Snowman", 15)+    , ("Soccer Ball", 16)+    , ("Star", 17)+    , ("Sunflower", 786)+    , ("Superman", 805)+    , ("Sweet Tooth", 18)+    , ("Tennis Ball", 19)+    , ("The Game Awards - Statue", 655) -- VGA_Statue+    , ("UFO", 20)+    , ("Venus Flytrap", 788)+    , ("Waffle", 401)+    , ("White Flag", 491)+    , ("Wonder Woman", 806)+    -- TODO: What are these called in game?+    , ("Community", 527)+    , ("ESL_Champ", 537)+    , ("Eyeball", 396)+    , ("Moderator", 528)+    , ("PanicButton", 829)+    , ("Psyonix", 287)+    , ("TourneyChamp", 539)+    , ("Translator", 530)+    ]+        ++ communityFlags+        ++ countryFlags+        ++ videoGames+        ++ nbaFlags+        & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList+++communityFlags :: [(String, Int)]+communityFlags =+    [ ("9GAG", 799)+    , ("AbleGamers", 814)+    , ("Achievement Hunter", 610)+    , ("Angry Army", 548) -- AgryJoe+    , ("Anne Munition", 531)+    , ("Day[9]TV", 654)+    , ("Destructoid", 724)+    , ("EMD1", 532) -- EatMyDiction+    , ("ESL", 536)+    , ("Funhaus", 611)+    , ("Gamespot", 725)+    , ("Gassy Mexican", 511)+    , ("Gfinity", 732)+    , ("GoldGlove", 728)+    , ("IGN", 802)+    , ("itmeJP", 533)+    , ("LIRIK", 513)+    , ("lolRenaynay", 628)+    , ("MLG", 538)+    , ("NeoGAF", 616)+    , ("NVIDIA", 808)+    , ("Operation Sports", 726)+    , ("PC Gamer", 667)+    , ("Polaris", 608)+    , ("Razer", 573)+    , ("Reddit", 630)+    , ("RL Garage", 645)+    , ("Rocket Beans TV", 869)+    , ("Rooster Teeth", 612)+    , ("Saudi Gamer", 534)+    , ("Serious Gaming", 572)+    , ("Something Awful", 699)+    , ("Spooky1611", 514)+    , ("Team Fat", 220)+    , ("Team PowA", 515)+    , ("The Attack", 535)+    , ("The Game Awards - Logo", 693) -- VGX+    , ("Twitch", 221)+    , ("YouTube Gaming", 516)+    ]+++countryFlags :: [(String, Int)]+countryFlags =+    [ ("Afghanistan", 407)+    , ("Albania", 408)+    , ("Algeria", 124)+    , ("American Samoa", 409)+    , ("Andorra", 830)+    , ("Angola", 410)+    , ("Anguilla", 831)+    , ("Antigua & Barbuda", 832)+    , ("Argentina", 125)+    , ("Armenia", 411)+    , ("Aruba", 833)+    , ("Australia", 126)+    , ("Austria", 127)+    , ("Azerbaijan", 412)+    , ("Bahamas", 834)+    , ("Bahrain", 550)+    , ("Bangladesh", 413)+    , ("Barbados", 835)+    , ("Belarus", 414)+    , ("Belgium", 128)+    , ("Belize", 551)+    , ("Benin", 415)+    , ("Bermuda", 836)+    , ("Bhutan", 552)+    , ("Bolivia", 416)+    , ("Bosnia and Herzegovina", 129)+    , ("Botswana", 553)+    , ("Brazil", 130)+    , ("British Virgin Islands", 837)+    , ("Bulgaria", 131)+    , ("Burkina Faso", 417)+    , ("Burma", 418)+    , ("Burundi", 419)+    , ("Cambodia", 420)+    , ("Cameroon", 132)+    , ("Canada", 133)+    , ("Cape Verde Islands", 134)+    , ("Cayman Islands", 838)+    , ("Central African Republic", 540)+    , ("Chad", 422)+    , ("Chile", 135)+    , ("China", 136)+    , ("Chinese Taipei", 198)+    , ("Colombia", 137)+    , ("Comoros", 554)+    , ("Congo", 138)+    , ("Congo DR", 423)+    , ("Cook Islands", 839)+    , ("Costa Rica", 139)+    , ("Croatia", 140)+    , ("Cuba", 424)+    , ("Curacao", 840)+    , ("Cyprus", 141)+    , ("Czech Republic", 142)+    , ("Côte d'Ivoire", 165)+    , ("Denmark", 143)+    , ("Djibouti", 555)+    , ("Dominica", 841)+    , ("Dominican Republic", 425)+    , ("East Timor", 556)+    , ("Ecuador", 144)+    , ("Egypt", 426)+    , ("El Salvador", 145)+    , ("England", 146)+    , ("Equatorial Guinea", 147)+    , ("Eritrea", 427)+    , ("Estonia", 428)+    , ("Ethiopia", 429)+    , ("Falkland Islands", 842)+    , ("Faroe Islands", 843)+    , ("Fiji", 557)+    , ("Finland", 148)+    , ("France", 149)+    , ("Gabon", 430)+    , ("Gambia", 558)+    , ("Georgia", 431)+    , ("Germany", 150)+    , ("Ghana", 151)+    , ("Gibraltar", 844)+    , ("Greece", 152)+    , ("Grenada", 863)+    , ("Guam", 432)+    , ("Guatemala", 153)+    , ("Guinea", 154)+    , ("Guinea Bissau", 559)+    , ("Guyana", 560)+    , ("Haiti", 433)+    , ("Honduras", 155)+    , ("Hong Kong", 156)+    , ("Hungary", 157)+    , ("Iceland", 158)+    , ("India", 159)+    , ("Indonesia", 160)+    , ("Iran", 161)+    , ("Iraq", 434)+    , ("Ireland", 162)+    , ("Isle Of Man", 845)+    , ("Israel", 163)+    , ("Italy", 164)+    , ("Jamaica", 166)+    , ("Japan", 167)+    , ("Jordan", 435)+    , ("Kazakhstan", 436)+    , ("Kenya", 168)+    , ("Kiribati", 846)+    , ("Kosovo", 561)+    , ("Kuwait", 437)+    , ("Kyrgyzstan", 438)+    , ("Laos", 439)+    , ("Latvia", 440)+    , ("Lebanon", 441)+    , ("Lesotho", 562)+    , ("Liberia", 442)+    , ("Libya", 443)+    , ("Lithuania", 444)+    , ("Luxembourg", 169)+    , ("Macau", 563)+    , ("Macedonia", 445)+    , ("Madagascar", 446)+    , ("Malawi", 447)+    , ("Malaysia", 170)+    , ("Maldives", 847)+    , ("Mali", 448)+    , ("Malta", 171)+    , ("Marshall Islands", 848)+    , ("Mauritania", 449)+    , ("Mauritius", 564)+    , ("Mexico", 172)+    , ("Micronesia", 849)+    , ("Moldova", 450)+    , ("Mongolia", 451)+    , ("Montenegro", 452)+    , ("Montserrat", 864)+    , ("Morocco", 453)+    , ("Mozambique", 454)+    , ("Namibia", 457)+    , ("Nepal", 458)+    , ("Netherlands", 173)+    , ("New Caledonia", 850)+    , ("New Zealand", 174)+    , ("Nicaragua", 175)+    , ("Nigeria", 176)+    , ("North Korea", 460)+    , ("North Mariana Islands", 456)+    , ("Northern Ireland", 177)+    , ("Norway", 178)+    , ("Oman", 461)+    , ("Palestine", 462)+    , ("Palau", 851)+    , ("Palestine", 462)+    , ("Panama", 180)+    , ("Papua New Guinea", 463)+    , ("Paraguay", 464)+    , ("Peru", 181)+    , ("Philippines", 182)+    , ("Poland", 183)+    , ("Portugal", 184)+    , ("Puerto Rico", 465)+    , ("Puntland", 565)+    , ("Qatar", 466)+    , ("Republic of Niger", 459)+    , ("Romania", 185)+    , ("Russia", 186)+    , ("Rwanda", 467)+    , ("Réunion", 566)+    , ("Saint Kitts & Nevis", 852)+    , ("Saint Lucia", 853)+    , ("Saint Vincent", 854)+    , ("Samoa", 855)+    , ("San Marino", 856)+    , ("Sao Tome", 857)+    , ("Saudi Arabia", 468)+    , ("Scotland", 187)+    , ("Senegal", 188)+    , ("Serbia", 189)+    , ("Seychelles", 858)+    , ("Sierra Leone", 469)+    , ("Singapore", 190)+    , ("Slovakia", 191)+    , ("Slovenia", 192)+    , ("Solomon Islands", 567)+    , ("Somalia", 470)+    , ("Somaliland", 568)+    , ("South Africa", 193)+    , ("South Korea", 194)+    , ("South Sudan", 471)+    , ("Spain", 195)+    , ("Sri Lanka", 389)+    , ("Sudan", 472)+    , ("Suriname", 569)+    , ("Swaziland", 570)+    , ("Sweden", 196)+    , ("Switzerland", 197)+    , ("Syria", 473)+    , ("Tahiti", 859)+    , ("Tajikistan", 474)+    , ("Tanzania", 475)+    , ("Thailand", 199)+    , ("Togo", 476)+    , ("Tonga", 730)+    , ("Trinidad and Tobago", 477)+    , ("Tunisia", 200)+    , ("Turkey", 201)+    , ("Turkmenistan", 478)+    , ("Turks & Caicos", 860)+    , ("Uganda", 479)+    , ("Ukraine", 204)+    , ("United Arab Emirates", 202)+    , ("United Kingdom", 203)+    , ("United States", 206)+    , ("Uruguay", 502)+    , ("US Virgin Islands", 480)+    , ("Uzbekistan", 481)+    , ("Vanuatu", 861)+    , ("Vatican City", 862)+    , ("Venezuela", 207)+    , ("Vietnam", 208)+    , ("Wales", 209)+    , ("Western Sahara", 571)+    , ("Yemen", 210)+    , ("Zambia", 482)+    , ("Zimbabwe", 483)+    ]+++nbaFlags :: [(String, Int)]+nbaFlags =+    [ ("NBA", 1263)+    , ("Atlanta Hawks", 1245)+    , ("Boston Celtics", 1246)+    , ("Brooklyn Nets", 1247)+    , ("Charlotte Hornets", 1248)+    , ("Chicago Bulls", 1249)+    , ("Cleveland Cavaliers", 1250)+    , ("Dallas Mavericks", 1251)+    , ("Denver Nuggets", 1252)+    , ("Detroit Pistons", 1253)+    , ("Golden State Warriors", 1254)+    , ("Houston Rockets", 1255)+    , ("Indian Pacers", 1256)+    , ("Los Angeles Clippers", 1257)+    , ("Los Angeles Lakers", 1258)+    , ("Memphis Grizzlies", 1259)+    , ("Miami Heat", 1260)+    , ("Milwaukee Bucks", 1261)+    , ("Minnesota Timberwolves", 1262)+    , ("New Orleans Pelicans", 1264)+    , ("New Tork Knicks", 1265)+    , ("Oklahoma City Thunder", 1266)+    , ("Orlando Magic", 1267)+    , ("Philadelphia 76ers", 1268)+    , ("Phoenix Suns", 1269)+    , ("Portland Trail Blazers", 1270)+    , ("Sacramento Kings", 1271)+    , ("San Antonio Spurs", 1272)+    , ("Toronto Raptors", 1273)+    , ("Utah Jazz", 1274)+    , ("Washington Wizards", 1275)+    ]+++videoGames :: [(String, Int)]+videoGames =+    [ ("Blacklight", 385)+    , ("Blacklight: Retribution", 211)+    , ("Chivalry - Agatha Knights", 525)+    , ("Chivalry - Mason Order", 526)+    , ("Edge Of Space", 213)+    , ("Euro Truck Simulator Rig", 813) -- ETS2+    , ("Fallout - Vault Boy", 673)+    , ("Fenix Rage", 216)+    , ("Goat Simulator - G2", 927)+    , ("Goat Simulator - Goatenna", 867)+    , ("Oddworld - Abe", 678)+    , ("Oddworld - Molluck", 679)+    , ("Oddworld - Necrum", 632)+    , ("Oddworld - RuptureFarms", 633)+    , ("Portal - Aperture Laboratories", 656)+    , ("Portal - Cake Sticker", 657)+    , ("Portal - Companion Cube", 681)+    , ("Portal - PotatOS", 698)+    , ("Portal - Wheatley", 686) -- Portal_PC+    , ("Shadowgate", 218)+    , ("Strike Vector EX", 219)+    , ("Unreal", 222)+    , ("Unreal Frag Center", 485)+    , ("Unreal Tournament", 489) -- UTBlue+    , ("Unreal Tournament (Classic)", 487) -- UT+    , ("Unreal Tournament - Blue", 484) -- Epic_BlueFlag+    , ("Unreal Tournament - Flak Shell", 397)+    , ("Unreal Tournament - Red", 486) -- Epic_RedFlag+    , ("Unreal Tournament 2004", 488)+    , ("Warframe", 223)+    , ("Warframe - Chroma", 682)+    , ("Warframe - Excalibur", 684)+    , ("Warframe - Loki", 683)+    , ("Witcher Medallion", 749)+    , ("Worms W.M.D", 1008)+    , ("Worms W.M.D. Grenade", 1000)+    ]
+ library/Octane/Data/Bodies.hs view
@@ -0,0 +1,33 @@+module Octane.Data.Bodies (bodies) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between body IDs and their names.+bodies :: Bimap.Bimap Int StrictText.Text+bodies =+    [ ("Armadillo", 625) -- GearsCar+    , ("Backfire", 21)+    , ("Batmobile", 803) -- Darkcar+    , ("Breakout", 22) -- Force+    , ("DeLorean Time Machine", 597) -- GreyCar+    , ("Dominus", 403) -- MuscleCar+    , ("Gizmo", 26) -- Spark+    , ("Grog", 607) -- WastelandTruck+    , ("Hogsticker", 723) -- Warthog+    , ("Hotshot", 29) -- Torment+    , ("Merc", 30) -- Vanquish+    , ("Octane", 23)+    , ("Paladin", 24) -- Orion+    , ("Ripper", 600) -- Interceptor+    , ("Road Hog", 25) -- Rhino+    , ("Scarab", 404)+    , ("Sweet Tooth", 27)+    , ("Takumi", 402) -- Import+    , ("Venom", 31)+    , ("X-Devil", 28) -- Torch+    , ("Zippy", 523)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Bots.hs view
@@ -0,0 +1,66 @@+module Octane.Data.Bots (bots) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between bot IDs and their names.+bots :: Bimap.Bimap Int StrictText.Text+bots =+    [ ("Astro", 70)+    , ("Bane", 71)+    , ("Beast", 72)+    , ("Blade", 73)+    , ("C-Block", 74)+    , ("Captain", 75)+    , ("Caveman", 76)+    , ("Cougar", 77)+    , ("Devil", 78)+    , ("Diesel", 79)+    , ("Dotty", 80)+    , ("Dude", 81)+    , ("Duke", 82)+    , ("Dynamite", 83)+    , ("Flame", 84)+    , ("Flash", 85)+    , ("Fossil", 86)+    , ("Fury", 87)+    , ("Hawk", 88)+    , ("Hunter", 89)+    , ("Imp", 90)+    , ("Jet", 91)+    , ("JM", 92)+    , ("Kidd", 93)+    , ("Laser", 94)+    , ("Lightning", 95)+    , ("Lucky", 96)+    , ("Middy", 97)+    , ("Mountain", 98)+    , ("Nitro", 99)+    , ("Outlaw", 100)+    , ("Phoenix", 101)+    , ("Raja", 102)+    , ("Razor", 103)+    , ("Retro", 104)+    , ("Roundhouse", 105)+    , ("Sabre", 106)+    , ("Samson", 107)+    , ("Savage", 108)+    , ("Shadow", 109)+    , ("Shield", 110)+    , ("Siren", 111)+    , ("Sky", 112)+    , ("Squall", 113)+    , ("Tank", 114)+    , ("Thunder", 115)+    , ("Titan", 116)+    , ("Turbo", 117)+    , ("Tusk", 118)+    , ("Viper", 119)+    , ("Warrior", 120)+    , ("Wolf", 121)+    , ("Yuri", 122)+    , ("Zap", 123)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Classes.hs view
@@ -0,0 +1,174 @@+module Octane.Data.Classes+    ( objectToClass+    , locationClasses+    , rotationClasses+    ) where++import Data.Function ((&))+import Data.Monoid ((<>))++import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.Text as StrictText+++-- | A map from object names to their class names. Note that any trailing+-- numbers have been stripped from the object names. So+-- @Archetypes.Teams.Team0@ is in this map as @Archetypes.Teams.Team@.+objectToClass :: Map.Map StrictText.Text StrictText.Text+objectToClass = Map.foldrWithKey+    (\ klass objects m -> objects+        & Set.map (\ object -> (object, klass))+        & Set.toList+        & Map.fromList+        & Map.union m)+    Map.empty+    classToObjects+++-- | A set of classes that have an initial location vector.+locationClasses :: Set.Set StrictText.Text+locationClasses =+    [ "TAGame.Ball_TA"+    , "TAGame.CameraSettingsActor_TA"+    , "TAGame.CarComponent_Boost_TA"+    , "TAGame.CarComponent_Dodge_TA"+    , "TAGame.CarComponent_DoubleJump_TA"+    , "TAGame.CarComponent_FlipCar_TA"+    , "TAGame.CarComponent_Jump_TA"+    , "TAGame.Car_Season_TA"+    , "TAGame.Car_TA"+    , "TAGame.GRI_TA"+    , "TAGame.GameEvent_Season_TA"+    , "TAGame.GameEvent_SoccarPrivate_TA"+    , "TAGame.GameEvent_SoccarSplitscreen_TA"+    , "TAGame.GameEvent_Soccar_TA"+    , "TAGame.PRI_TA"+    , "TAGame.Team_Soccar_TA"+    ] & map StrictText.pack & Set.fromList+++-- | A set of classes that have an initial rotation vector.+rotationClasses :: Set.Set StrictText.Text+rotationClasses =+    [ "TAGame.Ball_TA"+    , "TAGame.Car_Season_TA"+    , "TAGame.Car_TA"+    ] & map StrictText.pack & Set.fromList+++classToObjects :: Map.Map StrictText.Text (Set.Set StrictText.Text)+classToObjects = let+    normal =+        [ ( "TAGame.Ball_TA",+            [ "Archetypes.Ball.Ball_Default"+            , "Archetypes.Ball.Ball_Basketball"+            , "Archetypes.Ball.Ball_Puck"+            , "Archetypes.Ball.CubeBall"+            ])+        , ( "TAGame.CameraSettingsActor_TA",+            [ "TAGame.CameraSettingsActor_TA:PRI"+            , "TAGame.Default__CameraSettingsActor_TA"+            ])+        , ( "TAGame.CarComponent_Boost_TA",+            [ "Archetypes.CarComponents.CarComponent_Boost"+            ])+        , ( "TAGame.CarComponent_Dodge_TA",+            [ "Archetypes.CarComponents.CarComponent_Dodge"+            ])+        , ( "TAGame.CarComponent_DoubleJump_TA",+            [ "Archetypes.CarComponents.CarComponent_DoubleJump"+            ])+        , ( "TAGame.CarComponent_FlipCar_TA",+            [ "Archetypes.CarComponents.CarComponent_FlipCar"+            ])+        , ( "TAGame.CarComponent_Jump_TA",+            [ "Archetypes.CarComponents.CarComponent_Jump"+            ])+        , ( "TAGame.Car_TA",+            [ "Archetypes.Car.Car_Default"+            , "Archetypes.GameEvent.GameEvent_Season:CarArchetype"+            ])+        , ( "TAGame.GameEvent_Season_TA",+            [ "Archetypes.GameEvent.GameEvent_Season"+            ])+        , ( "TAGame.GameEvent_SoccarPrivate_TA",+            [ "Archetypes.GameEvent.GameEvent_BasketballPrivate"+            , "Archetypes.GameEvent.GameEvent_HockeyPrivate"+            , "Archetypes.GameEvent.GameEvent_SoccarPrivate"+            ])+        , ( "TAGame.GameEvent_SoccarSplitscreen_TA",+            [ "Archetypes.GameEvent.GameEvent_BasketballSplitscreen"+            , "Archetypes.GameEvent.GameEvent_HockeySplitscreen"+            , "Archetypes.GameEvent.GameEvent_SoccarSplitscreen"+            ])+        , ( "TAGame.GameEvent_Soccar_TA",+            [ "Archetypes.GameEvent.GameEvent_Soccar"+            , "Archetypes.GameEvent.GameEvent_Basketball"+            ])+        , ( "TAGame.GRI_TA",+            [ "GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype"+            , "Gameinfo_Hockey.GameInfo.Gameinfo_Hockey:GameReplicationInfoArchetype"+            , "GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype"+            , "GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype"+            ])+        , ( "TAGame.PRI_TA",+            [ "TAGame.Default__PRI_TA"+            ])+        , ( "TAGame.Team_Soccar_TA",+            [ "Archetypes.Teams.Team"+            ])+        , ( "TAGame.VoteActor_TA",+            [ "TAGame.Default__VoteActor_TA"+            ])+        ] & map (\ (klass, objects) ->+            ( StrictText.pack klass+            , objects & map StrictText.pack & Set.fromList+            ))+    special =+        [ ("TAGame.CrowdActor_TA", ".TheWorld:PersistentLevel.CrowdActor_TA_")+        , ("TAGame.CrowdManager_TA", ".TheWorld:PersistentLevel.CrowdManager_TA_")+        , ("TAGame.VehiclePickup_Boost_TA", ".TheWorld:PersistentLevel.VehiclePickup_Boost_TA_")+        ] & map (\ (klass, suffix) ->+            ( StrictText.pack klass+            , levels & Set.map (\ level -> level <> StrictText.pack suffix)+            ))+    in Map.fromList (normal ++ special)+++levels :: Set.Set StrictText.Text+levels =+    [ "EuroStadium_Rainy_P"+    , "HoopsStadium_P"+    , "Park_Night_P"+    , "Park_Rainy_P"+    , "Stadium_p"+    , "TrainStation_Night_P"+    , "TrainStation_P"+    , "Trainstation_Night_P"+    , "UtopiaStadium_Dusk_P"+    , "UtopiaStadium_Dusk_p"+    , "UtopiaStadium_P"+    , "Utopiastadium_p"+    , "Wasteland_P"+    , "Wasteland_p"+    , "eurostad_oob_audio_map"+    , "eurostadium_p"+    , "eurostadium_rainy_audio"+    , "hoopsstadium_sfx"+    , "labs_cosmic_p"+    , "labs_doublegoal_p"+    , "labs_underpass_p"+    , "labs_utopia_p"+    , "park_night_sfx"+    , "park_p"+    , "park_rainy_sfx"+    , "park_sfx"+    , "stadium_oob_audio_map"+    , "stadium_p"+    , "stadium_winter_p"+    , "trainstation_p"+    , "utopiastadium_p"+    , "utopiastadium_sfx"+    , "wasteland_sfx"+    ] & map StrictText.pack & Set.fromList
+ library/Octane/Data/Decals.hs view
@@ -0,0 +1,220 @@+module Octane.Data.Decals (decals) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between decal IDs and their names.+decals :: Bimap.Bimap Int StrictText.Text+decals =+    [ ("Backfire", backfireDecals)+    , ("Breakout", breakoutDecals)+    , ("Dominus", dominusDecals)+    , ("Gizmo", gizmoDecals)+    , ("Grog", grogDecals)+    , ("Hotshot", hotshotDecals)+    , ("Merc", mercDecals)+    , ("Octane", octaneDecals)+    , ("Paladin", paladinDecals)+    , ("Ripper", ripperDecals)+    , ("Road Hog", roadHogDecals)+    , ("Scarab", scarabDecals)+    , ("Takumi", takumiDecals)+    , ("Venom", venomDecals)+    , ("X-Devil", xDevilDecals)+    , ("Zippy", zippyDecals)+    ]+        & map (\ (body, values) -> values &+            map (\ (v, k) -> (k, v ++ " [" ++ body ++ "]")))+        & concat+        & map (\ (k, v) -> (k, StrictText.pack v))+        & Bimap.fromList+        & Bimap.insert 0 (StrictText.pack "None")+++backfireDecals :: [(String, Int)]+backfireDecals =+    [ ("Flames", 288)+    , ("Lightning", 289)+    , ("Skulls", 290)+    , ("Stars", 291)+    , ("Stripes", 292)+    , ("Tech", 293)+    , ("Wings", 294)+    ]+++breakoutDecals :: [(String, Int)]+breakoutDecals =+    [ ("Flames", 295)+    , ("Lightning", 296)+    , ("Skulls", 297)+    , ("Stars", 298)+    , ("Stripes", 299)+    , ("Tech", 300)+    , ("Wings", 301)+    ]+++dominusDecals :: [(String, Int)]+dominusDecals =+    [ ("Flames", 504)+    , ("Scorpions", 506) -- Skorpion+    , ("Skulls", 507)+    , ("Stripes", 508)+    , ("Tats", 509) -- Tribal+    , ("Wings", 510)+    ]+++gizmoDecals :: [(String, Int)]+gizmoDecals =+    [ ("Flames", 323)+    , ("Lightning", 324)+    , ("Skulls", 325)+    , ("Stars", 326)+    , ("Stripes", 327)+    , ("Tech", 328)+    , ("Wings", 329)+    ]+++grogDecals :: [(String, Int)]+grogDecals =+    [ ("Bomber", 674)+    , ("Cyclops", 694)+    , ("Lepus", 675) -- Bunny+    , ("Stripes", 687)+    , ("Tagged", 695)+    , ("Tribal", 696)+    ]+++hotshotDecals :: [(String, Int)]+hotshotDecals =+    [ ("Flames", 337)+    , ("Lightning", 338)+    , ("Skulls", 339)+    , ("Stars", 340)+    , ("Stripes", 341)+    , ("Tech", 342)+    , ("Wings", 343)+    ]+++mercDecals :: [(String, Int)]+mercDecals =+    [ ("Dots", 344)+    , ("Flames", 345)+    , ("Lightning", 346)+    , ("Skulls", 347)+    , ("Stars", 348)+    , ("Stripes", 349)+    , ("Wings", 350)+    ]+++octaneDecals :: [(String, Int)]+octaneDecals =+    [ ("Flames", 302)+    , ("Lightning", 303)+    , ("Skulls", 304)+    , ("Stars", 305)+    , ("Stripes", 306)+    , ("Tech", 307)+    , ("Wings", 308)+    ]+++paladinDecals :: [(String, Int)]+paladinDecals =+    [ ("Flames", 309)+    , ("Lightning", 310)+    , ("Skulls", 311)+    , ("Stars", 312)+    , ("Stripes", 313)+    , ("Tech", 314)+    , ("Wings", 315)+    ]+++ripperDecals :: [(String, Int)]+ripperDecals =+    [ ("Bomber", 701) -- Sandgirl+    , ("Flames", 669)+    , ("Ockie", 700) -- Getkraken+    , ("Shot Fox", 670) -- Kitsune+    , ("Spikes", 671)+    , ("Tribal", 672)+    ]+++roadHogDecals :: [(String, Int)]+roadHogDecals =+    [ ("Flames", 316)+    , ("Lightning", 317)+    , ("Skulls", 318)+    , ("Stars", 319)+    , ("Stripes", 320)+    , ("Tech", 321) -- TechSplat+    , ("Wings", 322)+    ]+++scarabDecals :: [(String, Int)]+scarabDecals =+    [ ("Bomani", 585) -- Clouds+    , ("Derby Girl", 592) -- RollerDerby+    , ("Flames", 591)+    , ("Hearts", 586)+    , ("Tiger", 587)+    , ("Tribal", 588)+    ]+++takumiDecals :: [(String, Int)]+takumiDecals =+    [ ("Chaser", 503) -- Wisp+    , ("Copycat", 497) -- Gato+    , ("Crazy-8", 502) -- Technologic+    , ("Gaki", 499) -- Ohai+    , ("Reiko", 498) -- Nitrous+    , ("Stripes", 501)+    ]+++venomDecals :: [(String, Int)]+venomDecals =+    [ ("Dots", 351)+    , ("Flames", 352)+    , ("Lightning", 353)+    , ("Skulls", 354)+    , ("Stripes", 355)+    , ("Tagged", 356)+    , ("Wings", 357)+    ]+++xDevilDecals :: [(String, Int)]+xDevilDecals =+    [ ("Flames", 330)+    , ("Lightning", 331)+    , ("Skulls", 332)+    , ("Stars", 333)+    , ("Stripes", 334)+    , ("Tech", 335)+    , ("Wings", 336)+    ]+++zippyDecals :: [(String, Int)]+zippyDecals =+    [ ("Caboodle", 589) -- HeckTec+    , ("Callous", 590) -- MeanMachine+    , ("Flames", 583)+    , ("Hearts", 581)+    , ("Leopard", 584)+    , ("Tiger", 582)+    ]
+ library/Octane/Data/Finishes.hs view
@@ -0,0 +1,28 @@+module Octane.Data.Finishes (finishes) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between paint finish IDs and their names.+finishes :: Bimap.Bimap Int StrictText.Text+finishes =+    [ ("Brushed Metal", 266)+    , ("Camo", 541)+    , ("Carbon Fiber", 272) -- GlossyCarbonFiber+    , ("Corroded Metal", 268) -- Corroded+    , ("DeLorean Time Machine", 623) -- GreyCar+    , ("Glossy", 270) -- Default+    , ("Matte", 273)+    , ("Metallic", 274)+    , ("Metallic Pearl", 275)+    , ("Pearlescent", 276)+    , ("Semigloss", 277) -- Plastic+    , ("Sun-Damaged", 542) -- Cracked+    , ("Toon Glossy", 279)+    , ("Toon Matte", 281)+    , ("Toon Wood", 280)+    , ("Wood", 283)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/GameModes.hs view
@@ -0,0 +1,15 @@+module Octane.Data.GameModes (gameModes) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between game mode IDs and their names.+gameModes :: Bimap.Bimap Int StrictText.Text+gameModes =+    [ ("Soccar", 0)+    , ("Hockey", 1)+    , ("Hoops", 2)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Logos.hs view
@@ -0,0 +1,33 @@+module Octane.Data.Logos (logos) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between logo IDs and their names.+logos :: Bimap.Bimap Int StrictText.Text+logos =+    [ ("Barracudas", 244)+    , ("Blacklight", 245)+    , ("Bombers", 246)+    , ("Cyclones", 247)+    , ("Destroyers", 248)+    , ("Dragons", 249)+    , ("Express", 250)+    , ("Guardians", 251)+    , ("Knights", 252)+    , ("Kodiaks", 253)+    , ("Mammoths", 254)+    , ("Monarchs", 255)+    , ("Phantoms", 256)+    , ("Pharaohs", 257)+    , ("Pioneers", 258)+    , ("Ravagers", 259)+    , ("Rebels", 260)+    , ("Rovers", 261)+    , ("Scorpions", 262)+    , ("Skyhawks", 263)+    , ("Wolves", 264)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Properties.hs view
@@ -0,0 +1,294 @@+module Octane.Data.Properties+    ( booleanProperties+    , byteProperties+    , camSettingsProperties+    , demolishProperties+    , enumProperties+    , explosionProperties+    , flaggedIntProperties+    , floatProperties+    , gameModeProperties+    , intProperties+    , loadoutOnlineProperties+    , loadoutProperties+    , locationProperties+    , musicStingerProperties+    , pickupProperties+    , privateMatchSettingsProperties+    , qWordProperties+    , relativeRotationProperties+    , reservationProperties+    , rigidBodyStateProperties+    , stringProperties+    , teamPaintProperties+    , uniqueIdProperties+    ) where++import Data.Function ((&))++import qualified Data.Set as Set+import qualified Data.Text as StrictText+++-- | A set of properties that are booleans.+booleanProperties :: Set.Set StrictText.Text+booleanProperties =+    [ "Engine.Actor:bBlockActors"+    , "Engine.Actor:bCollideActors"+    , "Engine.Actor:bCollideWorld"+    , "Engine.Actor:bHardAttach"+    , "Engine.Actor:bHidden"+    , "Engine.Actor:bNetOwner"+    , "Engine.Actor:bProjTarget"+    , "Engine.Actor:bTearOff"+    , "Engine.GameReplicationInfo:bMatchIsOver"+    , "Engine.Pawn:bCanSwatTurn"+    , "Engine.Pawn:bRootMotionFromInterpCurve"+    , "Engine.Pawn:bSimulateGravity"+    , "Engine.PlayerReplicationInfo:bAdmin"+    , "Engine.PlayerReplicationInfo:bBot"+    , "Engine.PlayerReplicationInfo:bFromPreviousLevel"+    , "Engine.PlayerReplicationInfo:bIsSpectator"+    , "Engine.PlayerReplicationInfo:bOnlySpectator"+    , "Engine.PlayerReplicationInfo:bOutOfLives"+    , "Engine.PlayerReplicationInfo:bReadyToPlay"+    , "Engine.PlayerReplicationInfo:bWaitingPlayer"+    , "ProjectX.GRI_X:bGameStarted"+    , "TAGame.CameraSettingsActor_TA:bUsingBehindView"+    , "TAGame.CameraSettingsActor_TA:bUsingSecondaryCamera"+    , "TAGame.CarComponent_Boost_TA:bNoBoost"+    , "TAGame.CarComponent_Boost_TA:bUnlimitedBoost"+    , "TAGame.CarComponent_FlipCar_TA:bFlipRight"+    , "TAGame.GameEvent_Soccar_TA:bBallHasBeenHit"+    , "TAGame.GameEvent_Soccar_TA:bOverTime"+    , "TAGame.GameEvent_TA:bHasLeaveMatchPenalty"+    , "TAGame.GameEvent_Team_TA:bDisableMutingOtherTeam"+    , "TAGame.PRI_TA:bIsInSplitScreen"+    , "TAGame.PRI_TA:bMatchMVP"+    , "TAGame.PRI_TA:bOnlineLoadoutSet"+    , "TAGame.PRI_TA:bReady"+    , "TAGame.PRI_TA:bUsingBehindView"+    , "TAGame.PRI_TA:bUsingFreecam"+    , "TAGame.PRI_TA:bUsingSecondaryCamera"+    , "TAGame.PRI_TA:bVoteToForfeitDisabled"+    , "TAGame.RBActor_TA:bFrozen"+    , "TAGame.RBActor_TA:bReplayActor"+    , "TAGame.Vehicle_TA:bDriving"+    , "TAGame.Vehicle_TA:bReplicatedHandbrake"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are bytes.+byteProperties :: Set.Set StrictText.Text+byteProperties =+    [ "Engine.PlayerReplicationInfo:Ping"+    , "TAGame.Ball_TA:HitTeamNum"+    , "TAGame.CameraSettingsActor_TA:CameraPitch"+    , "TAGame.CameraSettingsActor_TA:CameraYaw"+    , "TAGame.CarComponent_Boost_TA:ReplicatedBoostAmount"+    , "TAGame.CarComponent_TA:ReplicatedActive"+    , "TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam"+    , "TAGame.GameEvent_TA:ReplicatedStateIndex"+    , "TAGame.PRI_TA:CameraPitch"+    , "TAGame.PRI_TA:CameraYaw"+    , "TAGame.Vehicle_TA:ReplicatedSteer"+    , "TAGame.Vehicle_TA:ReplicatedThrottle"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are camera settings.+camSettingsProperties :: Set.Set StrictText.Text+camSettingsProperties =+    [ "TAGame.CameraSettingsActor_TA:ProfileSettings"+    , "TAGame.PRI_TA:CameraSettings"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are demolitions.+demolishProperties :: Set.Set StrictText.Text+demolishProperties =+    [ "TAGame.Car_TA:ReplicatedDemolish"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are enumerations.+enumProperties :: Set.Set StrictText.Text+enumProperties =+    [ "Engine.Actor:Role"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are explosions.+explosionProperties :: Set.Set StrictText.Text+explosionProperties =+    [ "TAGame.Ball_TA:ReplicatedExplosionData"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are flagged integers.+flaggedIntProperties :: Set.Set StrictText.Text+flaggedIntProperties =+    [ "Engine.Actor:Owner"+    , "Engine.Actor:ReplicatedCollisionType"+    , "Engine.GameReplicationInfo:GameClass"+    , "Engine.Pawn:PlayerReplicationInfo"+    , "Engine.PlayerReplicationInfo:Team"+    , "Engine.TeamInfo:TeamIndex"+    , "TAGame.Ball_TA:GameEvent"+    , "TAGame.CameraSettingsActor_TA:PRI"+    , "TAGame.CarComponent_TA:Vehicle"+    , "TAGame.CrowdActor_TA:GameEvent"+    , "TAGame.CrowdActor_TA:ReplicatedOneShotSound"+    , "TAGame.CrowdManager_TA:GameEvent"+    , "TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound"+    , "TAGame.PRI_TA:PersistentCamera"+    , "TAGame.PRI_TA:ReplicatedGameEvent"+    , "TAGame.Team_TA:GameEvent"+    , "TAGame.Team_TA:LogoData"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are floats.+floatProperties :: Set.Set StrictText.Text+floatProperties =+    [ "Engine.Actor:DrawScale"+    , "TAGame.Ball_TA:ReplicatedAddedCarBounceScale"+    , "TAGame.Ball_TA:ReplicatedBallMaxLinearSpeedScale"+    , "TAGame.Ball_TA:ReplicatedBallScale"+    , "TAGame.Ball_TA:ReplicatedWorldBounceScale"+    , "TAGame.CarComponent_Boost_TA:BoostModifier"+    , "TAGame.CarComponent_Boost_TA:RechargeDelay"+    , "TAGame.CarComponent_Boost_TA:RechargeRate"+    , "TAGame.CarComponent_FlipCar_TA:FlipCarTime"+    , "TAGame.CrowdActor_TA:ModifiedNoise"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are game modes.+gameModeProperties :: Set.Set StrictText.Text+gameModeProperties =+    [ "TAGame.GameEvent_TA:GameMode"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are integers.+intProperties :: Set.Set StrictText.Text+intProperties =+    [ "Engine.PlayerReplicationInfo:PlayerID"+    , "Engine.PlayerReplicationInfo:Score"+    , "Engine.TeamInfo:Score"+    , "ProjectX.GRI_X:ReplicatedGameMutatorIndex"+    , "ProjectX.GRI_X:ReplicatedGamePlaylist"+    , "TAGame.CrowdActor_TA:ReplicatedCountDownNumber"+    , "TAGame.GameEvent_Soccar_TA:RoundNum"+    , "TAGame.GameEvent_Soccar_TA:SecondsRemaining"+    , "TAGame.GameEvent_TA:BotSkill"+    , "TAGame.GameEvent_TA:ReplicatedGameStateTimeRemaining"+    , "TAGame.GameEvent_TA:ReplicatedStateName"+    , "TAGame.GameEvent_Team_TA:MaxTeamSize"+    , "TAGame.PRI_TA:MatchAssists"+    , "TAGame.PRI_TA:MatchGoals"+    , "TAGame.PRI_TA:MatchSaves"+    , "TAGame.PRI_TA:MatchScore"+    , "TAGame.PRI_TA:MatchShots"+    , "TAGame.PRI_TA:Title"+    , "TAGame.PRI_TA:TotalXP"+    , "TAGame.Team_Soccar_TA:GameScore"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are online loadouts.+loadoutOnlineProperties :: Set.Set StrictText.Text+loadoutOnlineProperties =+    [ "TAGame.PRI_TA:ClientLoadoutOnline"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are loadouts.+loadoutProperties :: Set.Set StrictText.Text+loadoutProperties =+    [ "TAGame.PRI_TA:ClientLoadout"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are locations.+locationProperties :: Set.Set StrictText.Text+locationProperties =+    [ "Engine.Actor:RelativeLocation"+    , "TAGame.CarComponent_Dodge_TA:DodgeTorque"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are music stingers.+musicStingerProperties :: Set.Set StrictText.Text+musicStingerProperties =+    [ "TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are pickups.+pickupProperties :: Set.Set StrictText.Text+pickupProperties =+    [ "TAGame.VehiclePickup_TA:ReplicatedPickupData"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are private match settings.+privateMatchSettingsProperties :: Set.Set StrictText.Text+privateMatchSettingsProperties =+    [ "TAGame.GameEvent_SoccarPrivate_TA:MatchSettings"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are qwords.+qWordProperties :: Set.Set StrictText.Text+qWordProperties =+    [ "ProjectX.GRI_X:GameServerID"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are relation rotations.+relativeRotationProperties :: Set.Set StrictText.Text+relativeRotationProperties =+    [ "Engine.Actor:RelativeRotation"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are reservations.+reservationProperties :: Set.Set StrictText.Text+reservationProperties =+    [ "ProjectX.GRI_X:Reservations"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are rigid body states.+rigidBodyStateProperties :: Set.Set StrictText.Text+rigidBodyStateProperties =+    [ "TAGame.RBActor_TA:ReplicatedRBState"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are strings.+stringProperties :: Set.Set StrictText.Text+stringProperties =+    [ "Engine.GameReplicationInfo:ServerName"+    , "Engine.PlayerReplicationInfo:PlayerName"+    , "Engine.PlayerReplicationInfo:RemoteUserData"+    , "TAGame.GRI_TA:NewDedicatedServerIP"+    , "TAGame.Team_TA:CustomTeamName"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are team paints.+teamPaintProperties :: Set.Set StrictText.Text+teamPaintProperties =+    [ "TAGame.Car_TA:TeamPaint"+    ] & map StrictText.pack & Set.fromList+++-- | A set of properties that are unique IDs.+uniqueIdProperties :: Set.Set StrictText.Text+uniqueIdProperties =+    [ "Engine.PlayerReplicationInfo:UniqueId"+    -- , "TAGame.PRI_TA:PartyLeader"+    ] & map StrictText.pack & Set.fromList
+ library/Octane/Data/RocketTrails.hs view
@@ -0,0 +1,63 @@+module Octane.Data.RocketTrails (rocketTrails) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between rocket trail IDs and their names.+rocketTrails :: Bimap.Bimap Int StrictText.Text+rocketTrails =+    [ ("(Alpha Reward) Gold Rush", 32) -- AlphaReward+    , ("Accelerato", 524) -- MusicalNotes+    , ("Batmobile", 868) -- DarkCar+    , ("Battle-Stars", 545)+    , ("Bubbles", 33)+    , ("Burnout", 387)+    , ("Candy Corn", 578)+    , ("Confetti", 34)+    , ("Datastream", 35) -- Digital+    , ("Flamethrower Blue", 37)+    , ("Flamethrower Green", 38)+    , ("Flamethrower Pink", 39)+    , ("Flamethrower Purple", 40)+    , ("Flamethrower Red", 41)+    , ("Flamethrower", 36)+    , ("Flowers", 42)+    , ("Grass", 43)+    , ("Hydro", 69) -- Water+    , ("Ion Blue", 45) -- LightTrail+    , ("Ion Green", 46)+    , ("Ion Pink", 47)+    , ("Ion Purple", 48)+    , ("Ion Red", 49)+    , ("Ion Yellow", 50)+    , ("Money", 51)+    , ("Nitrous", 384)+    , ("Nuts & Bolts", 544)+    , ("OutaTime", 626) -- GreyCar+    , ("Plasma", 52)+    , ("Portal - Conversion Gel", 651)+    , ("Portal - Propulsion Gel", 652)+    , ("Portal - Reuplsion Gel", 653)+    , ("Rainbow", 59)+    , ("Sacred", 44) -- HolyLight+    , ("Sandstorm", 664)+    , ("Slime", 60)+    , ("Snowflakes", 61)+    , ("Sparkles", 62)+    , ("Standard Blue", 64)+    , ("Standard Pink", 65)+    , ("Standard Purple", 66)+    , ("Standard Red", 63)+    , ("Standard Yellow", 67)+    , ("Sweet Tooth", 68)+    , ("Thermal Blue", 54)+    , ("Thermal Green", 55)+    , ("Thermal Pink", 56)+    , ("Thermal Purple", 57)+    , ("Thermal Yellow", 58)+    , ("Thermal", 53) -- Propulsion+    , ("Xmas", 635) -- Presents+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Toppers.hs view
@@ -0,0 +1,78 @@+module Octane.Data.Toppers (toppers) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between topper IDs and their names.+toppers :: Bimap.Bimap Int StrictText.Text+toppers =+    [ ("None", 0)+    , ("(Alpha Reward) Gold Cap", 224) -- AlphaHat+    , ("Antlers", 774)+    , ("Beret", 775)+    , ("Biker Cap", 776)+    , ("Birthday Cake", 789)+    , ("Blitzen", 640) -- ReindeerAntlers+    , ("Bobby Helmet", 227) -- BPH+    , ("Boombox", 521)+    , ("Bowler", 745) -- GentlemanHat+    , ("Brodie Helmet", 743) -- DoughboyHelmet+    , ("Brünnhilde", 241) -- Viking_Helmet+    , ("Captain's Hat", 741)+    , ("Cavalier", 580) -- Tricorn+    , ("Chainsaw", 795)+    , ("Chef's Hat", 756)+    , ("Cherry Top", 237) -- PoliceSiren+    , ("Christmas Tree", 639) -- RegularChristmasTree+    , ("Cockroach", 769)+    , ("Cow Skull", 685)+    , ("deadmau5", 796)+    , ("Deerstalker", 792)+    , ("Derby", 790)+    , ("Devil Horns", 229)+    , ("Fez", 230)+    , ("Fire Helmet", 231)+    , ("Foam Hat", 390) -- TourneyWinner+    , ("Fruit Hat", 744)+    , ("Graduation Cap", 778)+    , ("Halo", 232)+    , ("Hard Hat", 233)+    , ("Homburg", 742) -- DetectiveHat+    , ("Locomotive", 547) -- TrainConductor+    , ("Mariachi Hat", 234)+    , ("Mohawk", 638)+    , ("Mouse Trap", 782) -- RatTrap+    , ("Paper Boat", 770)+    , ("Pirate's Hat", 235)+    , ("Pixelated Shades", 494) -- DWI+    , ("Pizza Topper", 236)+    , ("Plunger", 772)+    , ("Police Hat", 746)+    , ("Portal - Cake", 661)+    , ("Propellerhead", 225) -- Beanie+    , ("Pumpkin", 546) -- CatOLantern+    , ("Rasta", 771)+    , ("Rhino Horns", 780)+    , ("Royal Crown", 228)+    , ("Sad Sapling", 641) -- SadChristmasTree+    , ("Santa", 642)+    , ("Season 1 - Bronze", 705)+    , ("Season 1 - Gold", 706)+    , ("Season 1 - Platinum", 707)+    , ("Season 1 - Silver", 708)+    , ("Shark Fin", 579)+    , ("Shuriken", 773)+    , ("Sombrero", 238)+    , ("Taxi Topper", 239)+    , ("Tiara", 495)+    , ("Top Hat", 240)+    , ("Traffic Cone", 747)+    , ("Unicorn", 748) -- Unihorn+    , ("Witch's Hat", 242)+    , ("Wizard Hat", 243)+    , ("Work Boot", 740)+    , ("Worms W.M.D.", 1028)+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
+ library/Octane/Data/Wheels.hs view
@@ -0,0 +1,51 @@+module Octane.Data.Wheels (wheels) where++import Data.Function ((&))++import qualified Data.Bimap as Bimap+import qualified Data.Text as StrictText+++-- | A one-to-one mapping between wheels IDs and their names.+wheels :: Bimap.Bimap Int StrictText.Text+wheels =+    [ ("(Alpha Reward) Goldstone", 358) -- AlphaRim+    , ("Alchemist", 374) -- Revolution+    , ("Almas", 364) -- Crypt+    , ("Armadillo", 618)+    , ("Batmobile", 874) -- Darkcar+    , ("Bender", 360) -- Ballistic+    , ("Carriage", 549)+    , ("Cog", 716) -- TODO: What is this called in game?+    , ("Cristiano", 386) -- SoccerBall+    , ("DeLorean Time Machine", 609) -- GreyCar+    , ("Dieci", 363) -- Caliber+    , ("Falco", 382) -- Vulcan+    , ("Foreman", 372) -- OffRoad+    , ("Grog", 613) -- WastelandTruck+    , ("Hogsticker", 719) -- Warthog+    , ("Invader", 380) -- Triad+    , ("Lowrider", 369)+    , ("Lucci", 361) -- Bling+    , ("Mountaineer", 375) -- SnowTire+    , ("Neptune", 370) -- Mob+    , ("Octavian", 379) -- Tarantula+    , ("OEM", 376) -- Star+    , ("Rat Rod", 366) -- Dynamo+    , ("Ripper", 690) -- Spiked+    , ("Scarab", 540) -- Scarab+    , ("Servergate", 519) -- SkullxBones+    , ("Spinner", 388) -- Spinner+    , ("Spyder", 371) -- Ninja+    , ("Stallion", 365) -- DeepDish+    , ("Stern", 368) -- Hydra+    , ("Sunburt", 383) -- Wynd+    , ("Sweet Tooth", 378)+    , ("Tempest", 377) -- Storm+    , ("Tomahawk", 362) -- Brink+    , ("Trahere", 367) -- Forge+    , ("Tunica", 359) -- Atlantis+    , ("Veloce", 373) -- Pedigree+    , ("Vortex", 381)+    , ("Zippy", 518) -- SingleSpoke+    ] & map (\ (v, k) -> (k, StrictText.pack v)) & Bimap.fromList
− library/Octane/FullReplay.hs
@@ -1,483 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}--module Octane.FullReplay-    ( FullReplay(..)-    , parseReplay-    , parseReplayFile-    , unsafeParseReplay-    , unsafeParseReplayFile-    ) where--import Data.Aeson ((.=))-import Data.Function ((&))-import Data.Monoid ((<>))-import Prelude ((==), (/=), (&&))--import qualified Control.DeepSeq as DeepSeq-import qualified Control.Monad as Monad-import qualified Data.Aeson as Aeson-import qualified Data.Bimap as Bimap-import qualified Data.Binary as Binary-import qualified Data.ByteString.Lazy as ByteString-import qualified Data.Foldable as Foldable-import qualified Data.IntMap.Strict as IntMap-import qualified Data.Map.Strict as Map-import qualified Data.Text as Text-import qualified Data.Version as Version-import qualified GHC.Generics as Generics-import qualified Octane.Data as Data-import qualified Octane.Parser as Parser-import qualified Octane.Parser.Garage as Garage-import qualified Octane.Type as Type-import qualified Prelude---newtype FullReplay = FullReplay-    { unpackFullReplay :: (Type.Replay, [Parser.Frame])-    } deriving (Generics.Generic, Prelude.Show)--instance DeepSeq.NFData FullReplay--instance Aeson.ToJSON FullReplay where-    toJSON fullReplay = do-        Aeson.object-            [ "Version" .= getVersion fullReplay-            , "Metadata" .= getMetadata fullReplay-            , "Levels" .= getLevels fullReplay-            , "Messages" .= getMessages fullReplay-            , "TickMarks" .= getTickMarks fullReplay-            , "Frames" .= getFrames fullReplay-            ]---newFullReplay :: Type.Replay -> [Parser.Frame] -> FullReplay-newFullReplay replay frames = FullReplay (replay, frames)---getVersion :: FullReplay -> Prelude.String-getVersion fullReplay =-    [ fullReplay-        & unpackFullReplay-        & Prelude.fst-        & Type.replayVersion1-        & Type.unpackInt32-        & Prelude.fromIntegral-    , fullReplay-        & unpackFullReplay-        & Prelude.fst-        & Type.replayVersion2-        & Type.unpackInt32-        & Prelude.fromIntegral-    ] & Version.makeVersion & Version.showVersion---getMetadata :: FullReplay -> Map.Map Text.Text Aeson.Value-getMetadata fullReplay = fullReplay-    & unpackFullReplay-    & Prelude.fst-    & Type.replayProperties-    & Type.unpackDictionary-    & Map.mapKeys Type.unpackText-    & Map.map Aeson.toJSON---getLevels :: FullReplay -> [Text.Text]-getLevels fullReplay = fullReplay-    & unpackFullReplay-    & Prelude.fst-    & Type.replayLevels-    & Type.unpackList-    & Prelude.map Type.unpackText---getMessages :: FullReplay -> Map.Map Text.Text Type.Text-getMessages fullReplay = fullReplay-    & unpackFullReplay-    & Prelude.fst-    & Type.replayMessages-    & Type.unpackList-    & Prelude.map (\ message ->-        ( message-            & Type.messageFrame-            & Type.unpackInt32-            & Prelude.show-            & Text.pack-        , message & Type.messageContent-        ))-    & Map.fromList---getTickMarks :: FullReplay -> Map.Map Text.Text Type.Text-getTickMarks fullReplay = fullReplay-    & unpackFullReplay-    & Prelude.fst-    & Type.replayMarks-    & Type.unpackList-    & Prelude.map (\ mark ->-        ( mark-            & Type.markFrame-            & Type.unpackInt32-            & Prelude.show-            & Text.pack-        , mark & Type.markLabel-        ))-    & Map.fromList---getFrames :: FullReplay -> [Map.Map Text.Text Aeson.Value]-getFrames fullReplay = fullReplay-    & unpackFullReplay-    & Prelude.snd-    & Foldable.foldl'-        (\ (state, frames) frame -> let-            newState = updateState frame state-            minimalFrame = getDelta state frame-            in (newState, minimalFrame : frames))-        (initialState, [])-    & Prelude.snd-    & Prelude.reverse-    & Prelude.map (\ frame -> Map.fromList-        [ ("IsKeyFrame", frame & Parser.frameIsKeyFrame & Aeson.toJSON)-        , ("Number", frame & Parser.frameNumber & Aeson.toJSON)-        , ("Time", frame & Parser.frameTime & Aeson.toJSON)-        , ("Delta", frame & Parser.frameDelta & Aeson.toJSON)-        , ("Spawned", getSpawnedActors frame)-        , ("Updated", getUpdatedActors frame)-        , ("Destroyed", getDestroyedActors frame)-        ])----- { actor id => (alive?, { property name => property value } ) }-type State = IntMap.IntMap (Prelude.Bool, Map.Map Text.Text Parser.PropValue)---initialState :: State-initialState = IntMap.empty---updateState :: Parser.Frame -> State -> State-updateState frame state1 = let-    spawned = frame-        & Parser.frameReplications-        & Prelude.filter (\ replication -> replication-            & Parser.replicationState-            & (== Parser.RSOpening))-        & Prelude.map Parser.replicationActorId-    state2 = spawned-        & Prelude.foldr-            (IntMap.alter (\ maybeValue -> Prelude.Just (case maybeValue of-                Prelude.Nothing -> (Prelude.True, Map.empty)-                Prelude.Just (_, properties) -> (Prelude.True, properties))))-            state1--    destroyed = frame-        & Parser.frameReplications-        & Prelude.filter (\ replication -> replication-            & Parser.replicationState-            & (== Parser.RSClosing))-        & Prelude.map Parser.replicationActorId-    state3 = destroyed-        & Prelude.foldr-            (IntMap.alter (\ maybeValue -> Prelude.Just (case maybeValue of-                Prelude.Nothing -> (Prelude.False, Map.empty)-                Prelude.Just (_, properties) -> (Prelude.False, properties))))-            state2--    updated = frame-        & Parser.frameReplications-        & Prelude.filter (\ replication -> replication-            & Parser.replicationState-            & (== Parser.RSExisting))-    state4 = updated-        & Prelude.foldr-            (\ replication -> IntMap.alter-                (\ maybeValue -> Prelude.Just (case maybeValue of-                    Prelude.Nothing ->-                        (Prelude.True, Parser.replicationProperties replication)-                    Prelude.Just (alive, properties) ->-                        ( alive-                        , Map.union-                            (Parser.replicationProperties replication)-                            properties-                        )))-                (Parser.replicationActorId replication))-            state3--    in state4---getDelta :: State -> Parser.Frame -> Parser.Frame-getDelta state frame = let-    newReplications = frame-        & Parser.frameReplications-        -- Remove replications that aren't actually new.-        & reject (\ replication -> let-            isOpening = Parser.replicationState replication == Parser.RSOpening-            actorId = Parser.replicationActorId replication-            currentState = IntMap.lookup actorId state-            isAlive = Prelude.fmap Prelude.fst currentState-            wasAlreadyAlive = isAlive == Prelude.Just Prelude.True-            in isOpening && wasAlreadyAlive)-        -- Remove properties that haven't changed.-        & Prelude.map (\ replication ->-            if Parser.replicationState replication == Parser.RSExisting-            then let-                actorId = Parser.replicationActorId replication-                currentState = IntMap.findWithDefault-                    (Prelude.True, Map.empty) actorId state-                currentProperties = Prelude.snd currentState-                newProperties = Parser.replicationProperties replication-                changes = newProperties-                    & Map.filterWithKey (\ name newValue -> let-                        oldValue = Map.lookup name currentProperties-                        in Prelude.Just newValue /= oldValue)-                in replication { Parser.replicationProperties = changes }-            else replication)-    in frame { Parser.frameReplications = newReplications }---reject :: (a -> Prelude.Bool) -> [a] -> [a]-reject p xs = Prelude.filter (\ x -> Prelude.not (p x)) xs---getSpawnedActors :: Parser.Frame -> Aeson.Value-getSpawnedActors frame = frame-    & Parser.frameReplications-    & Prelude.filter (\ replication -> replication-        & Parser.replicationState-        & (== Parser.RSOpening))-    & Prelude.map (\ replication ->-        ( replication-            & Parser.replicationActorId-            & Prelude.show-            & Text.pack-        , Aeson.object-            [ ("Name", replication-                & Parser.replicationObjectName-                & Aeson.toJSON)-            , ("Class", replication-                & Parser.replicationClassName-                & Aeson.toJSON)-            , ("Position", replication-                & Parser.replicationInitialization-                & Prelude.fmap Parser.classInitLocation-                & Monad.join-                & Aeson.toJSON)-            , ("Rotation", replication-                & Parser.replicationInitialization-                & Prelude.fmap Parser.classInitRotation-                & Monad.join-                & Aeson.toJSON)-            ]-        ))-    & Aeson.object---getUpdatedActors :: Parser.Frame -> Aeson.Value-getUpdatedActors frame = frame-    & Parser.frameReplications-    & Prelude.filter (\ replication -> replication-        & Parser.replicationState-        & (== Parser.RSExisting))-    & Prelude.map (\ replication ->-        ( replication-            & Parser.replicationActorId-            & Prelude.show-            & Text.pack-        , replication-            & Parser.replicationProperties-            & Map.map (\ property -> Aeson.object-                [ ("Type", getPropertyName property)-                , ("Value", getPropertyValue property)-                ])-        ))-    & reject (\ (_, properties) -> Map.null properties)-    & Prelude.map (\ (actorId, properties) ->-        (actorId, Aeson.toJSON properties))-    & Aeson.object---getPropertyName :: Parser.PropValue -> Aeson.Value-getPropertyName property = case property of-    Parser.PBoolean _ -> "Boolean"-    Parser.PByte _ -> "Byte"-    Parser.PCamSettings _ _ _ _ _ _ -> "CameraSettings"-    Parser.PDemolish _ _ _ _ _ _ -> "Demolition"-    Parser.PEnum _ _ -> "Enum"-    Parser.PExplosion _ _ _ -> "Explosion"-    Parser.PFlaggedInt _ _ -> "FlaggedInt"-    Parser.PFloat _ -> "Float"-    Parser.PGameMode _ -> "GameMode"-    Parser.PInt _ -> "Int"-    Parser.PLoadout _ _ _ _ _ _ _ _ _ -> "Loadout"-    Parser.PLoadoutOnline _ _ _ _ -> "OnlineLoadout"-    Parser.PLocation _ -> "Position"-    Parser.PMusicStinger _ _ _ -> "MusicStinger"-    Parser.PPickup _ _ _ -> "Pickup"-    Parser.PPrivateMatchSettings _ _ _ _ _ _ -> "PrivateMatchSettings"-    Parser.PQWord _ _ -> "QWord"-    Parser.PRelativeRotation _ -> "RelativeRotation"-    Parser.PReservation _ _ _ _ _ _ _ -> "Reservation"-    Parser.PRigidBodyState _ _ _ _ _ -> "RigidBodyState"-    Parser.PString _ -> "String"-    Parser.PTeamPaint _ _ _ _ _ -> "Paint"-    Parser.PUniqueId _ _ _ -> "UniqueId"---getPropertyValue :: Parser.PropValue -> Aeson.Value-getPropertyValue property = case property of-    Parser.PBoolean x -> Aeson.toJSON x-    Parser.PByte x -> Aeson.toJSON x-    Parser.PCamSettings fov height angle distance stiffness swivelSpeed -> Aeson.object-        [ ("FOV", Aeson.toJSON fov)-        , ("Height", Aeson.toJSON height)-        , ("Angle", Aeson.toJSON angle)-        , ("Distance", Aeson.toJSON distance)-        , ("Stiffness", Aeson.toJSON stiffness)-        , ("SwivelSpeed", Aeson.toJSON swivelSpeed)-        ]-    Parser.PDemolish a b c d e f -> Aeson.toJSON (a, b, c, d, e, f)-    Parser.PEnum x y -> Aeson.toJSON (x, y)-    Parser.PExplosion a b c -> Aeson.toJSON (a, b, c)-    Parser.PFlaggedInt x y -> Aeson.toJSON (x, y)-    Parser.PFloat x -> Aeson.toJSON x-    Parser.PGameMode gameMode -> Aeson.object-        [ ("Id", Aeson.toJSON gameMode)-        , ("Name", Data.gameModes-            & Bimap.lookup (Prelude.fromIntegral gameMode)-            & (\ x -> x :: Prelude.Maybe Text.Text)-            & Aeson.toJSON)-        ]-    Parser.PInt x -> Aeson.toJSON x-    Parser.PLoadout version body decal wheels rocketTrail antenna topper x y -> Aeson.object-        [ ("Version", Aeson.toJSON version)-        , ("Body", Aeson.object-            [ ("Id", Aeson.toJSON body)-            , ("Name", body & Garage.getBody & Aeson.toJSON)-            ])-        , ("Decal", Aeson.object-            [ ("Id", Aeson.toJSON decal)-            , ("Name", decal & Garage.getDecal & Aeson.toJSON)-            ])-        , ("Wheels", Aeson.object-            [ ("Id", Aeson.toJSON wheels)-            , ("Name", wheels & Garage.getWheels & Aeson.toJSON)-            ])-        , ("RocketTrail", Aeson.object-            [ ("Id", Aeson.toJSON rocketTrail)-            , ("Name", rocketTrail & Garage.getRocketTrail & Aeson.toJSON)-            ])-        , ("Antenna", Aeson.object-            [ ("Id", Aeson.toJSON antenna)-            , ("Name", antenna & Garage.getAntenna & Aeson.toJSON)-            ])-        , ("Topper", Aeson.object-            [ ("Id", Aeson.toJSON topper)-            , ("Name", topper & Garage.getTopper & Aeson.toJSON)-            ])-        , ("Unknown1", Aeson.toJSON x)-        , ("Unknown2", Aeson.toJSON y)-        ]-    Parser.PLoadoutOnline a b c d -> Aeson.toJSON (a, b, c, d)-    Parser.PLocation x -> Aeson.toJSON x-    Parser.PMusicStinger a b c -> Aeson.toJSON (a, b, c)-    Parser.PPickup a b c -> Aeson.toJSON (a, b, c)-    Parser.PPrivateMatchSettings mutators joinableBy maxPlayers name password x -> Aeson.object-        [ ("Mutators", Aeson.toJSON mutators)-        , ("JoinableBy", Aeson.toJSON joinableBy)-        , ("MaxPlayers", Aeson.toJSON maxPlayers)-        , ("Name", Aeson.toJSON name)-        , ("Password", Aeson.toJSON password)-        , ("Unknown", Aeson.toJSON x)-        ]-    Parser.PQWord a b -> Aeson.toJSON (a, b)-    Parser.PRelativeRotation x -> Aeson.toJSON x-    Parser.PReservation number systemId remoteId localId name x y -> Aeson.object-        [ ("Number", Aeson.toJSON number)-        , ("SystemId", Aeson.toJSON systemId)-        , ("RemoteId", Aeson.toJSON remoteId)-        , ("LocalId", Aeson.toJSON localId)-        , ("Name", Aeson.toJSON name)-        , ("Unknown1", Aeson.toJSON x)-        , ("Unknown2", Aeson.toJSON y)-        ]-    Parser.PRigidBodyState sleeping position rotation linear angular -> Aeson.object-        [ ("Sleeping", Aeson.toJSON sleeping)-        , ("Position", Aeson.toJSON position)-        , ("Rotation", Aeson.toJSON rotation)-        , ("LinearVelocity", Aeson.toJSON linear)-        , ("AngularVelocity", Aeson.toJSON angular)-        ]-    Parser.PString x -> Aeson.toJSON x-    Parser.PTeamPaint team color1 color2 finish1 finish2 -> Aeson.object-        [ ("Team", Aeson.toJSON team)-        , ("PrimaryColor", Aeson.toJSON color1)-        , ("AccentColor", Aeson.toJSON color2)-        , ("PrimaryFinish", Aeson.object-            [ ("Id", Aeson.toJSON finish1)-            , ("Name", finish1 & Garage.getFinish & Aeson.toJSON)-            ])-        , ("AccentFinish", Aeson.object-            [ ("Id", Aeson.toJSON finish2)-            , ("Name", finish2 & Garage.getFinish & Aeson.toJSON)-            ])-        ]-    Parser.PUniqueId systemId remoteId localId -> Aeson.object-        [ ("System", case systemId of-            0 -> "Local"-            1 -> "Steam"-            2 -> "PlayStation"-            4 -> "Xbox"-            _ -> Aeson.String ("Unknown system " <> Text.pack (Prelude.show systemId)))-        , ("Remote", case remoteId of-            Parser.SplitscreenId x -> Aeson.toJSON x-            Parser.SteamId x -> Aeson.toJSON x-            Parser.PlayStationId x -> Aeson.toJSON x-            Parser.XboxId x -> Aeson.toJSON x)-        , ("Local", Aeson.toJSON localId)-        ]--getDestroyedActors :: Parser.Frame -> Aeson.Value-getDestroyedActors frame = frame-    & Parser.frameReplications-    & Prelude.filter (\ replication -> replication-        & Parser.replicationState-        & (== Parser.RSClosing))-    & Prelude.map Parser.replicationActorId-    & Aeson.toJSON---parseReplay :: ByteString.ByteString -> Prelude.Either Text.Text FullReplay-parseReplay bytes = do-    case Binary.decodeOrFail bytes of-        Prelude.Left (_, _, message) -> do-            Prelude.Left (Text.pack message)-        Prelude.Right (_, _, replay) -> do-            let frames = Parser.parseFrames replay-            Prelude.Right (newFullReplay replay frames)---parseReplayFile :: Prelude.FilePath -> Prelude.IO (Prelude.Either Text.Text FullReplay)-parseReplayFile file = do-    result <- Binary.decodeFileOrFail file-    case result of-        Prelude.Left (_, message) -> do-            Prelude.pure (Prelude.Left (Text.pack message))-        Prelude.Right replay -> do-            let frames = Parser.parseFrames replay-            Prelude.pure (Prelude.Right (newFullReplay replay frames))---unsafeParseReplay :: ByteString.ByteString -> FullReplay-unsafeParseReplay bytes = do-    let replay = Binary.decode bytes-    let frames = Parser.parseFrames replay-    newFullReplay replay frames---unsafeParseReplayFile :: Prelude.FilePath -> Prelude.IO FullReplay-unsafeParseReplayFile file = do-    replay <- Binary.decodeFile file-    let frames = Parser.parseFrames replay-    Prelude.pure (newFullReplay replay frames)
− library/Octane/Json.hs
@@ -1,9 +0,0 @@-module Octane.Json where--import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson--toJsonOptions :: String -> Aeson.Options-toJsonOptions name = Aeson.defaultOptions-    { Aeson.fieldLabelModifier = Aeson.camelTo2 '_' . drop (length name)-    }
library/Octane/Main.hs view
@@ -1,16 +1,77 @@ module Octane.Main (main) where -import qualified Control.DeepSeq as DeepSeq import qualified Data.Aeson as Aeson-import qualified Data.ByteString.Lazy.Char8 as ByteString-import qualified Octane.FullReplay as FullReplay-import qualified Prelude+import qualified Data.Binary as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Network.HTTP.Client as Client+import qualified Network.HTTP.Client.TLS as TLS+import qualified Octane.Type.Replay as Replay import qualified System.Environment as Environment  -main :: Prelude.IO ()+-- | Octane's command-line entrypoint. It has three modes:+--+-- 1.  If no arguments are given, it will read from 'System.IO.stdin' and write+--     to a JSON object to 'System.IO.stdout'.+--+--     > octane < a.replay > replay.json+--+-- 2.  If one argument is given, it will assume that argument is a path to a+--     replay. Both local and remote (HTTP or HTTPS) paths are supported.+--     Octane will read from the path and write a JSON object to+--     'System.IO.stdout'.+--+--     > octane a.replay > replay.json+--+--     > octane https://media.rocketleaguereplays.com/uploads/replay_files/8D0940554D285C3F45109F85C79396A2.replay > replay.json+--+-- 3.  If multiple arguments are given, it will assume that those arguments are+--     paths to replays, read from them, and write a JSON array of objects to+--     'System.IO.stdout'.+--+--     > octane first.replay second.replay > replays.json+main :: IO () main = do-    [file] <- Environment.getArgs-    fullReplay <- FullReplay.unsafeParseReplayFile file-    DeepSeq.deepseq fullReplay (Prelude.pure ())-    ByteString.putStrLn (Aeson.encode fullReplay)+    manager <- Client.newManager TLS.tlsManagerSettings+    TLS.setGlobalManager manager++    args <- Environment.getArgs+    case args of+        [] -> main0+        [x] -> main1 x+        xs -> main2 xs+++main0 :: IO ()+main0 = do+    LazyBytes.interact (\ input -> do+        let replay = Binary.decode input+        Aeson.encode (replay :: Replay.Replay))+++main1 :: String -> IO ()+main1 x = do+    replay <- decode x+    let output = Aeson.encode replay+    LazyBytes.putStr output+++main2 :: [String] -> IO ()+main2 xs = do+    replays <- mapM decode xs+    let output = Aeson.encode replays+    LazyBytes.putStr output+++decode :: String -> IO Replay.Replay+decode x = do+    case Client.parseUrl x of+        Nothing -> do+            let file = x+            Binary.decodeFile file+        Just request -> do+            manager <- TLS.getGlobalManager+            response <- Client.httpLbs request manager+            let input = Client.responseBody response+            let replay = Binary.decode input+            pure replay
− library/Octane/Parser.hs
@@ -1,823 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Parser where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as Bits-import qualified Data.Binary.Get as Binary-import qualified Data.Bits as Bits-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSL-import qualified Data.IntMap.Strict as IntMap-import qualified Data.Map.Strict as Map-import qualified Data.Set as Set-import qualified Data.Text as Text-import qualified Data.Word as Word-import qualified GHC.Generics as Generics-import qualified Octane.Data as Data-import qualified Octane.Json as Json-import qualified Octane.Parser.ClassPropertyMap as CPM-import qualified Octane.Type as Type-import qualified Octane.Utility as Utility-import qualified Text.Printf as Printf--parseFrames :: Type.Replay -> [Frame]-parseFrames replay = let-    numFrames = replay-        & Type.replayProperties-        & Type.unpackDictionary-        & Map.lookup ("NumFrames" & Text.pack & Type.Text)-        & (\ property -> case property of-            Just (Type.IntProperty _ x) -> x & Type.unpackInt32 & fromIntegral-            _ -> 0)-    get = replay & extractContext & getFrames 0 numFrames & Bits.runBitGet-    stream = replay & Type.replayStream & Type.unpackStream & BSL.fromStrict-    (_context, frames) = Binary.runGet get stream-    in frames--getFrames :: Int -> Int -> Context -> Bits.BitGet (Context, [Frame])-getFrames number numFrames context = do-    if number >= numFrames-    then return (context, [])-    else do-        isEmpty <- Bits.isEmpty-        if isEmpty-        then return (context, [])-        else do-            maybeFrame <- getMaybeFrame context number-            case maybeFrame of-                Nothing -> return (context, [])-                Just (newContext, frame) -> do-                    (newerContext, frames) <- getFrames (number + 1) numFrames newContext-                    return (newerContext, (frame : frames))--getMaybeFrame :: Context -> Int -> Bits.BitGet (Maybe (Context, Frame))-getMaybeFrame context number = do-    time <- getFloat32-    delta <- getFloat32-    if time == 0 && delta == 0-    then return Nothing-    else if time < 0.001 || delta < 0.001-    then fail ("parsing previous frame probably failed. time: " ++ show time ++ ", delta: " ++ show delta)-    else do-        (newContext, frame) <- getFrame context number time delta-        return (Just (newContext, frame))--getFrame :: Context -> Int -> Time -> Delta -> Bits.BitGet (Context, Frame)-getFrame context number time delta = do-    (newContext, replications) <- getReplications context-    let frame =-            Frame-            { frameNumber = number-            , frameIsKeyFrame = context & contextKeyFrames & Set.member number-            , frameTime = time-            , frameDelta = delta-            , frameReplications = replications-            }-    (newContext, frame) & DeepSeq.force & return--getReplications :: Context -> Bits.BitGet (Context, [Replication])-getReplications context = do-    maybeReplication <- getMaybeReplication context-    case maybeReplication of-        Nothing -> return (context, [])-        Just (newContext, replication) -> do-            (newerContext, replications) <- getReplications newContext-            return (newerContext, replication : replications)--getMaybeReplication :: Context -> Bits.BitGet (Maybe (Context, Replication))-getMaybeReplication context = do-    hasReplication <- getBool-    if Type.unpackBoolean hasReplication-        then do-            (newContext,replication) <- getReplication context-            return (Just (newContext, replication))-        else return Nothing--getReplication :: Context -> Bits.BitGet (Context, Replication)-getReplication context = do-    actorId <- getActorId-    isOpen <- getBool-    let go =-            if Type.unpackBoolean isOpen-                then getOpenReplication-                else getClosedReplication-    go context actorId--getOpenReplication :: Context-                   -> ActorId-                   -> Bits.BitGet (Context, Replication)-getOpenReplication context actorId = do-    isNew <- getBool-    let go =-            if Type.unpackBoolean isNew-                then getNewReplication-                else getExistingReplication-    go context actorId--getNewReplication :: Context-                  -> ActorId-                  -> Bits.BitGet (Context, Replication)-getNewReplication context actorId = do-    unknownFlag <- getBool-    if Type.unpackBoolean unknownFlag-        then fail "the unknown flag in a new replication is true! what does it mean?"-        else pure ()-    objectId <- getInt32-    objectName <- case context & contextObjectMap & IntMap.lookup (Type.fromInt32 objectId) of-        Nothing -> fail ("could not find object name for id " ++ show objectId)-        Just x -> pure x-    (classId, className) <- case CPM.getClass (contextObjectMap context) Data.objectToClass (contextClassMap context) (Type.fromInt32 objectId) of-        Nothing -> fail ("could not find class for object id " ++ show objectId)-        Just x -> pure x-    classInit <- getClassInit className-    let thing = Thing-            { thingFlag = unknownFlag-            , thingObjectId = objectId-            , thingObjectName = objectName-            , thingClassId = classId-            , thingClassName = className-            , thingClassInit = classInit-            }-    let things = contextThings context-    let newThings = IntMap.insert actorId thing things-    let newContext = context { contextThings = newThings }-    return-        ( newContext-        , Replication-          { replicationActorId = actorId-          , replicationObjectName = objectName-          , replicationClassName = className-          , replicationState = RSOpening-          , replicationInitialization = Just classInit-          , replicationProperties = Map.empty-          })--getExistingReplication :: Context-                       -> ActorId-                       -> Bits.BitGet (Context, Replication)-getExistingReplication context actorId = do-    thing <- case context & contextThings & IntMap.lookup actorId of-        Nothing -> fail ("could not find thing for actor id " ++ show actorId)-        Just x -> pure x-    props <- getProps context thing-    return (context, Replication-        { replicationActorId = actorId-        , replicationObjectName = thingObjectName thing-        , replicationClassName = thingClassName thing-        , replicationState = RSExisting-        , replicationInitialization = Nothing-        , replicationProperties = props-        })--getClosedReplication :: Context-                     -> ActorId-                     -> Bits.BitGet (Context, Replication)-getClosedReplication context actorId = do-    thing <- case context & contextThings & IntMap.lookup actorId of-        Nothing -> fail ("could not find thing for actor id " ++ show actorId)-        Just x -> pure x-    let newThings = context & contextThings & IntMap.delete actorId-    let newContext = context { contextThings = newThings }-    return-        ( newContext-        , Replication-          { replicationActorId = actorId-          , replicationObjectName = thingObjectName thing-          , replicationClassName = thingClassName thing-          , replicationState = RSClosing-          , replicationInitialization = Nothing-          , replicationProperties = Map.empty-          })--getProps :: Context -> Thing -> Bits.BitGet (Map.Map Text.Text PropValue)-getProps context thing = do-    maybeProp <- getMaybeProp context thing-    case maybeProp of-        Nothing -> return Map.empty-        Just prop -> do-            let k = propName prop-            let v = propValue prop-            let m = Map.singleton k v-            props <- getProps context thing-            return (Map.union m props)--getMaybeProp :: Context -> Thing -> Bits.BitGet (Maybe Prop)-getMaybeProp context thing = do-    hasProp <- getBool-    if Type.unpackBoolean hasProp-    then do-        prop <- getProp context thing-        return (Just prop)-    else return Nothing--getProp :: Context -> Thing -> Bits.BitGet Prop-getProp context thing = do-    let classId = thing & thingClassId-    props <- case context & contextClassPropertyMap & IntMap.lookup classId of-        Nothing -> fail ("could not find property map for class id " ++ show classId)-        Just x -> pure x-    let maxId = props & IntMap.keys & (0 :) & maximum-    pid <- getInt maxId-    name <- case props & IntMap.lookup pid of-        Nothing -> fail ("could not find property name for property id " ++ show pid)-        Just x -> pure x-    value <- getPropValue name-    return (Prop { propName = name, propValue = value })------getPropValue :: Text.Text -> Bits.BitGet PropValue-getPropValue name = case Map.lookup name propertyNameToGet of-    Nothing -> fail ("don't know how to read property " ++ show name)-    Just get -> get--propertyNameToGet :: Map.Map Text.Text (Bits.BitGet PropValue)-propertyNameToGet =-    [ (Data.booleanProperties, getBooleanProperty)-    , (Data.byteProperties, getByteProperty)-    , (Data.camSettingsProperties, getCamSettingsProperty)-    , (Data.demolishProperties, getDemolishProperty)-    , (Data.enumProperties, getEnumProperty)-    , (Data.explosionProperties, getExplosionProperty)-    , (Data.flaggedIntProperties, getFlaggedIntProperty)-    , (Data.floatProperties, getFloatProperty)-    , (Data.gameModeProperties, getGameModeProperty)-    , (Data.intProperties, getIntProperty)-    , (Data.loadoutOnlineProperties, getLoadoutOnlineProperty)-    , (Data.loadoutProperties, getLoadoutProperty)-    , (Data.locationProperties, getLocationProperty)-    , (Data.musicStingerProperties, getMusicStingerProperty)-    , (Data.pickupProperties, getPickupProperty)-    , (Data.privateMatchSettingsProperties, getPrivateMatchSettingsProperty)-    , (Data.qWordProperties, getQWordProperty)-    , (Data.relativeRotationProperties, getRelativeRotationProperty)-    , (Data.reservationProperties, getReservationProperty)-    , (Data.rigidBodyStateProperties, getRigidBodyStateProperty)-    , (Data.stringProperties, getStringProperty)-    , (Data.teamPaintProperties, getTeamPaintProperty)-    , (Data.uniqueIdProperties, getUniqueIdProperty)-    , (Set.fromList [Text.pack "TAGame.PRI_TA:PartyLeader"], getPartyLeaderProperty)-    ]-        & concatMap (\ (ks, v) -> ks & Set.toList & map (\ k -> (k, v)))-        & Map.fromList--getBooleanProperty :: Bits.BitGet PropValue-getBooleanProperty = do-    bool <- getBool-    return (PBoolean bool)--getByteProperty :: Bits.BitGet PropValue-getByteProperty = do-    word <- getWord8-    return (PByte word)--getCamSettingsProperty :: Bits.BitGet PropValue-getCamSettingsProperty = do-    fov <- getFloat32-    height <- getFloat32-    angle <- getFloat32-    distance <- getFloat32-    stiffness <- getFloat32-    swivelSpeed <- getFloat32-    return (PCamSettings fov height angle distance stiffness swivelSpeed)--getDemolishProperty :: Bits.BitGet PropValue-getDemolishProperty = do-    atkFlag <- getBool-    atk <- getInt32-    vicFlag <- getBool-    vic <- getInt32-    vec1 <- getVector-    vec2 <- getVector-    return (PDemolish atkFlag atk vicFlag vic vec1 vec2)--getEnumProperty :: Bits.BitGet PropValue-getEnumProperty = do-    x <- Bits.getWord16be 10-    y <- if x == 1023-        then getBool-        else fail ("unexpected enum value " ++ show x)-    return (PEnum x y)--getExplosionProperty :: Bits.BitGet PropValue-getExplosionProperty = do-    noGoal <- getBool-    a <- if Type.unpackBoolean noGoal-        then return Nothing-        else fmap Just getInt32-    b <- getVector-    return (PExplosion noGoal a b)--getFlaggedIntProperty :: Bits.BitGet PropValue-getFlaggedIntProperty = do-    flag <- getBool-    int <- getInt32-    return (PFlaggedInt flag int)--getFloatProperty :: Bits.BitGet PropValue-getFloatProperty = do-    float <- getFloat32-    return (PFloat float)--getGameModeProperty :: Bits.BitGet PropValue-getGameModeProperty = do-    x <- Bits.getWord8 2-    return (PGameMode x)--getIntProperty :: Bits.BitGet PropValue-getIntProperty = do-    int <- getInt32-    return (PInt int)--getLoadoutOnlineProperty :: Bits.BitGet PropValue-getLoadoutOnlineProperty = do-    version <- getInt32-    x <- getInt32-    y <- getInt32-    z <- if version >= 12-        then do-            value <- getInt8-            return (Just value)-        else return Nothing-    return (PLoadoutOnline version x y z)--getLoadoutProperty :: Bits.BitGet PropValue-getLoadoutProperty = do-    version <- getInt8-    body <- getInt32-    decal <- getInt32-    wheels <- getInt32-    rocketTrail <- getInt32-    antenna <- getInt32-    topper <- getInt32-    g <- getInt32-    h <- if version > 10-        then do-            value <- getInt32-            return (Just value)-        else return Nothing-    return (PLoadout version body decal wheels rocketTrail antenna topper g h)--getLocationProperty :: Bits.BitGet PropValue-getLocationProperty = do-    vector <- getVector-    return (PLocation vector)--getMusicStingerProperty :: Bits.BitGet PropValue-getMusicStingerProperty = do-    flag <- getBool-    cue <- getInt32-    trigger <- getInt8-    return (PMusicStinger flag cue trigger)--getPickupProperty :: Bits.BitGet PropValue-getPickupProperty = do-    instigator <- getBool-    instigatorId <- if Type.unpackBoolean instigator-        then fmap Just getInt32-        else return Nothing-    pickedUp <- getBool-    return (PPickup instigator instigatorId pickedUp)--getPrivateMatchSettingsProperty :: Bits.BitGet PropValue-getPrivateMatchSettingsProperty = do-    mutators <- getText-    joinableBy <- getInt32-    maxPlayers <- getInt32-    gameName <- getText-    password <- getText-    flag <- getBool-    return (PPrivateMatchSettings mutators joinableBy maxPlayers gameName password flag)--getQWordProperty :: Bits.BitGet PropValue-getQWordProperty = do-    x <- getInt32-    y <- getInt32-    return (PQWord x y)--getRelativeRotationProperty :: Bits.BitGet PropValue-getRelativeRotationProperty = do-    vector <- getFloatVector-    return (PRelativeRotation vector)--getReservationProperty :: Bits.BitGet PropValue-getReservationProperty = do-    -- I think this is the connection order. The first player to connect-    -- gets number 0, and it goes up from there. The maximum is 7, which-    -- would be a full 4x4 game.-    number <- getInt7-    (systemId, remoteId, localId) <- getUniqueId-    playerName <- if systemId == 0 then return Nothing else do-        string <- getText-        return (Just string)-    -- No idea what these two flags are. Might be for bots?-    a <- getBool-    b <- getBool-    return (PReservation number systemId remoteId localId playerName a b)--getRigidBodyStateProperty :: Bits.BitGet PropValue-getRigidBodyStateProperty = do-    flag <- getBool-    position <- getVector-    rotation <- getFloatVector-    x <- if Type.unpackBoolean flag-        then return Nothing-        else fmap Just getVector-    y <- if Type.unpackBoolean flag-        then return Nothing-        else fmap Just getVector-    return (PRigidBodyState flag position rotation x y)--getStringProperty :: Bits.BitGet PropValue-getStringProperty = do-    string <- getText-    return (PString string)--getTeamPaintProperty :: Bits.BitGet PropValue-getTeamPaintProperty = do-    team <- getInt8-    primaryColor <- getInt8-    accentColor <- getInt8-    primaryFinish <- getInt32-    accentFinish <- getInt32-    return (PTeamPaint team primaryColor accentColor primaryFinish accentFinish)--getUniqueIdProperty :: Bits.BitGet PropValue-getUniqueIdProperty = do-    (systemId, remoteId, localId) <- getUniqueId-    return (PUniqueId systemId remoteId localId)---- | Even though this is just a unique ID property, it must be handled--- specially because it sometimes doesn't have the remote or local IDs.-getPartyLeaderProperty :: Bits.BitGet PropValue-getPartyLeaderProperty = do-    systemId <- getSystemId-    (remoteId, localId) <- if systemId == 0-        then return (SplitscreenId Nothing, Nothing)-        else do-            remoteId <- getRemoteId systemId-            localId <- getLocalId-            return (remoteId, localId)-    return (PUniqueId systemId remoteId localId)------getFloat32 :: Bits.BitGet Type.Float32-getFloat32 = BinaryBit.getBits unimportant--getText :: Bits.BitGet Type.Text-getText = BinaryBit.getBits unimportant--getUniqueId :: Bits.BitGet (SystemId, RemoteId, LocalId)-getUniqueId = do-    systemId <- getSystemId-    remoteId <- getRemoteId systemId-    localId <- getLocalId-    return (systemId, remoteId, localId)--getSystemId :: Bits.BitGet SystemId-getSystemId = getWord8--getRemoteId :: SystemId -> Bits.BitGet RemoteId-getRemoteId systemId = case systemId of-    0 -> do-        remoteId <- Bits.getByteString 3-        if BS.all (\ byte -> byte == 0) remoteId-            then 0 & Just & SplitscreenId & return-            else fail ("unexpected splitscreen id " ++ show remoteId)-    1 -> do-        bytes <- Bits.getByteString 8-        let remoteId = Binary.runGet-                Binary.getWord64le-                (bytes & BS.map Utility.reverseBits & BSL.fromStrict)-        remoteId & SteamId & return-    2 -> do-        bytes <- Bits.getByteString 32-        let remoteId = bytes-                & BS.map Utility.reverseBits-                & BS.unpack-                & concatMap (\ b -> Printf.printf "%02x" b)-                & Text.pack-        remoteId & PlayStationId & return-    4 -> do-        bytes <- Bits.getByteString 8-        let remoteId = Binary.runGet-                Binary.getWord64le-                (bytes & BS.map Utility.reverseBits & BSL.fromStrict)-        remoteId & XboxId & return-    _ -> fail ("unknown system id " ++ show systemId)--getLocalId :: Bits.BitGet LocalId-getLocalId = fmap Just getWord8--type SystemId = Type.Word8---- This is the number associated with a splitscreen player. So the first player--- is 0, the second is 1, and so on.--- - 0 "Someone"--- - 1 "Someone (1)"-type LocalId = Maybe Type.Word8--data RemoteId-    = SteamId !Word.Word64-    | PlayStationId !Text.Text-    | SplitscreenId !(Maybe Int)-    | XboxId !Word.Word64-    deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData RemoteId-instance Aeson.ToJSON RemoteId--data Prop = Prop-    { propName :: !Text.Text-    , propValue :: !PropValue-    } deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData Prop-instance Aeson.ToJSON Prop where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Prop")--data PropValue-    = PBoolean !Type.Boolean-    | PByte !Type.Word8-    | PCamSettings !Type.Float32 !Type.Float32 !Type.Float32 !Type.Float32 !Type.Float32 !Type.Float32-    | PDemolish !Type.Boolean !Type.Int32 !Type.Boolean !Type.Int32 !(Vector Int) !(Vector Int)-    | PEnum !Word.Word16 !Type.Boolean-    | PExplosion !Type.Boolean !(Maybe Type.Int32) !(Vector Int)-    | PFlaggedInt !Type.Boolean !Type.Int32-    | PFloat !Type.Float32-    | PGameMode !Word.Word8-    | PInt !Type.Int32-    | PLoadout !Type.Int8 !Type.Int32 !Type.Int32 !Type.Int32 !Type.Int32 !Type.Int32 !Type.Int32 !Type.Int32 !(Maybe Type.Int32)-    | PLoadoutOnline !Type.Int32 !Type.Int32 !Type.Int32 !(Maybe Type.Int8)-    | PLocation !(Vector Int)-    | PMusicStinger !Type.Boolean !Type.Int32 !Type.Int8-    | PPickup !Type.Boolean !(Maybe Type.Int32) !Type.Boolean-    | PPrivateMatchSettings !Type.Text !Type.Int32 !Type.Int32 !Type.Text !Type.Text !Type.Boolean-    | PQWord !Type.Int32 !Type.Int32-    | PRelativeRotation !(Vector Float)-    | PReservation !Int !SystemId !RemoteId !LocalId !(Maybe Type.Text) !Type.Boolean !Type.Boolean-    | PRigidBodyState !Type.Boolean !(Vector Int) !(Vector Float) !(Maybe (Vector Int)) !(Maybe (Vector Int))-    | PString !Type.Text-    | PTeamPaint !Type.Int8 !Type.Int8 !Type.Int8 !Type.Int32 !Type.Int32-    | PUniqueId !SystemId !RemoteId !LocalId-    deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData PropValue-instance Aeson.ToJSON PropValue where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "PropValue")---- | A frame in the net stream. Each frame has the time since the beginning of--- the match, the time since the last frame, and a list of replications.-data Frame = Frame-    { frameNumber :: !Int-    , frameIsKeyFrame :: !Bool-    , frameTime :: !Time-    , frameDelta :: !Delta-    , frameReplications :: ![Replication]-    } deriving (Eq,Generics.Generic,Show)--instance DeepSeq.NFData Frame-instance Aeson.ToJSON Frame where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Frame")--data ReplicationState-    = RSOpening-    | RSExisting-    | RSClosing-    deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData ReplicationState-instance Aeson.ToJSON ReplicationState where-    toJSON rs = Aeson.toJSON (case rs of-        RSOpening -> "opening"-        RSExisting -> "existing"-        RSClosing -> "closing")---- | Replication information about an actor in the net stream.-data Replication = Replication-    { replicationActorId :: !Int-    , replicationObjectName :: !Text.Text-    , replicationClassName :: !Text.Text-    , replicationState :: !ReplicationState-    , replicationInitialization :: !(Maybe ClassInit)-    , replicationProperties :: !(Map.Map Text.Text PropValue)-    } deriving (Eq,Generics.Generic,Show)--instance DeepSeq.NFData Replication-instance Aeson.ToJSON Replication where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Replication")--data Thing = Thing-    { thingFlag :: !Type.Boolean-    , thingObjectId :: !Type.Int32-    , thingObjectName :: !Text.Text-    , thingClassId :: !Int-    , thingClassName :: !Text.Text-    , thingClassInit :: !ClassInit-    } deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData Thing--type Time = Type.Float32--type Delta = Type.Float32--type ActorId = Int--data Vector a = Vector-    { vectorX :: !a-    , vectorY :: !a-    , vectorZ :: !a-    } deriving (Eq, Generics.Generic, Show)--instance (DeepSeq.NFData a) => DeepSeq.NFData (Vector a)-instance (Aeson.ToJSON a) => Aeson.ToJSON (Vector a) where-    toJSON vector = Aeson.toJSON-        [ vectorX vector-        , vectorY vector-        , vectorZ vector-        ]--data ClassInit = ClassInit-    { classInitLocation :: !(Maybe (Vector Int))-    , classInitRotation :: !(Maybe (Vector Type.Int8))-    } deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData ClassInit-instance Aeson.ToJSON ClassInit where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "ClassInit")---- { class stream id => { property stream id => name } }-type ClassPropertyMap = IntMap.IntMap (IntMap.IntMap Text.Text)---- { stream id => object name }-type ObjectMap = IntMap.IntMap Text.Text---- { class name => class id }-type ClassMap = Map.Map Text.Text Int--data Context = Context-    { contextObjectMap :: !ObjectMap-    , contextClassPropertyMap :: !ClassPropertyMap-    , contextThings :: !(IntMap.IntMap Thing)-    , contextClassMap :: !ClassMap-    , contextKeyFrames :: !(Set.Set Int)-    } deriving (Eq, Generics.Generic, Show)--instance DeepSeq.NFData Context--extractContext :: Type.Replay -> Context-extractContext replay =-    Context-    { contextObjectMap = CPM.getPropertyMap replay-    , contextClassPropertyMap = CPM.getClassPropertyMap replay-    , contextThings = IntMap.empty-    , contextClassMap = CPM.getActorMap replay-    , contextKeyFrames = replay-        & Type.replayKeyFrames-        & Type.unpackList-        & map Type.keyFrameFrame-        & map Type.unpackInt32-        & map fromIntegral-        & Set.fromList-    }--getVector :: Bits.BitGet (Vector Int)-getVector = do-    numBits <- getNumVectorBits-    let bias = Bits.shiftL 1 (numBits + 1)-    let maxBits = numBits + 2-    let maxValue = 2 ^ maxBits-    dx <- getInt maxValue-    dy <- getInt maxValue-    dz <- getInt maxValue-    return-        Vector-        { vectorX = dx - bias-        , vectorY = dy - bias-        , vectorZ = dz - bias-        }--getVectorBytewise-    :: Bits.BitGet (Vector Type.Int8)-getVectorBytewise = do-    hasX <- getBool-    x <- if Type.unpackBoolean hasX then getInt8 else return 0-    hasY <- getBool-    y <- if Type.unpackBoolean hasY then getInt8 else return 0-    hasZ <- getBool-    z <- if Type.unpackBoolean hasZ then getInt8 else return 0-    return-        Vector-        { vectorX = x-        , vectorY = y-        , vectorZ = z-        }--getFloatVector :: Bits.BitGet (Vector Float)-getFloatVector = do-    let maxValue = 1-    let numBits = 16-    x <- getFloat maxValue numBits-    y <- getFloat maxValue numBits-    z <- getFloat maxValue numBits-    return Vector { vectorX = x, vectorY = y, vectorZ = z }--getFloat :: Int -> Int -> Bits.BitGet Float-getFloat maxValue numBits = do-    let maxBitValue = (Bits.shiftL 1 (numBits - 1)) - 1-    let bias = Bits.shiftL 1 (numBits - 1)-    let serIntMax = Bits.shiftL 1 numBits-    delta <- getInt serIntMax-    let unscaledValue = delta - bias-    if maxValue > maxBitValue-    then do-        let invScale = fromIntegral maxValue / fromIntegral maxBitValue-        return (fromIntegral unscaledValue * invScale)-    else do-        let scale = fromIntegral maxBitValue / fromIntegral maxValue-        let invScale = 1.0 / scale-        return (fromIntegral unscaledValue * invScale)--getClassInit :: Text.Text -> Bits.BitGet ClassInit-getClassInit className = do-    location <--        if Set.member className Data.locationClasses-            then do-                vector <- getVector-                return (Just vector)-            else return Nothing-    rotation <--        if Set.member className Data.rotationClasses-            then do-                vector <- getVectorBytewise-                return (Just vector)-            else return Nothing-    return-        ClassInit-        { classInitLocation = location-        , classInitRotation = rotation-        }--bitSize-    :: (Integral a)-    => a -> a-bitSize x = x & fromIntegral & logBase (2 :: Double) & ceiling---- Reads an integer bitwise. The bits of the integer are backwards, so the--- least significant bit is first. The argument is the maximum value this--- integer can have. Bits will be read until the next bit would be greater than--- the maximum value, or the number of bits necessary to reach the maximum--- value has been reached, whichever comes first.------ For example, if the maximum value is 4 and "11" has been read already,--- nothing more will be read because another "1" would put the value over the--- maximum.-getInt-    :: Int -> Bits.BitGet Int-getInt maxValue = do-    let maxBits = bitSize maxValue-        go i value = do-            let x = Bits.shiftL 1 i-            if i < maxBits && value + x <= maxValue-                then do-                    bit <- getBool-                    let newValue =-                            if Type.unpackBoolean bit-                                then value + x-                                else value-                    go (i + 1) newValue-                else return value-    go 0 0--getInt32 :: Bits.BitGet Type.Int32-getInt32 = BinaryBit.getBits unimportant--getInt8 :: Bits.BitGet Type.Int8-getInt8 = BinaryBit.getBits unimportant--getWord8 :: Bits.BitGet Type.Word8-getWord8 = BinaryBit.getBits unimportant--getActorId :: Bits.BitGet Int-getActorId = getInt 1024--getNumVectorBits :: Bits.BitGet Int-getNumVectorBits = getInt 19--getInt7 :: Bits.BitGet Int-getInt7 = getInt 7--getBool :: Bits.BitGet Type.Boolean-getBool = BinaryBit.getBits unimportant---- | The 'getBits' function from "Data.Binary.Bits" requires a size parameter.--- None of Octane's instances use it.-unimportant :: Int-unimportant = 0
− library/Octane/Parser/ClassPropertyMap.hs
@@ -1,167 +0,0 @@--- | This module is responsible for building the class property map, which maps--- class IDs to a map of property IDs to property names. This map is the--- cornerstone of the replay stream parser.-module Octane.Parser.ClassPropertyMap where--import Data.Function ((&))--import qualified Data.Char as Char-import qualified Data.IntMap.Strict as IntMap-import qualified Data.List as List-import qualified Data.Map.Strict as Map-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Octane.Type as Type---- | The class property map is a map from class IDs in the stream to a map from--- property IDs in the stream to property names.-getClassPropertyMap :: Type.Replay -> IntMap.IntMap (IntMap.IntMap Text.Text)-getClassPropertyMap replay = let-    basicClassPropertyMap = getBasicClassPropertyMap replay-    classMap = getClassMap replay-    in replay-        & getClassIds-        & map (\ classId -> let-            ownProperties = case IntMap.lookup classId basicClassPropertyMap of-                Nothing -> IntMap.empty-                Just x -> x-            parentProperties = case IntMap.lookup classId classMap of-                Nothing -> IntMap.empty-                Just parentClassIds -> parentClassIds-                    & map (\ parentClassId ->-                        case IntMap.lookup parentClassId basicClassPropertyMap of-                            Nothing -> IntMap.empty-                            Just x -> x)-                    & IntMap.unions-            properties = IntMap.union ownProperties parentProperties-            in (classId, properties))-        & IntMap.fromList---- | The class cache is a list of 3-tuples where the first element is a class--- ID, the second is its cache ID, and the third is its parent's cache ID.-getClassCache :: Type.Replay -> [(Int, Int, Int)]-getClassCache replay = replay-    & Type.replayCacheItems-    & Type.unpackList-    & map (\ x ->-        ( x & Type.cacheItemClassId & Type.unpackInt32 & fromIntegral-        , x & Type.cacheItemCacheId & Type.unpackInt32 & fromIntegral-        , x & Type.cacheItemParentCacheId & Type.unpackInt32 & fromIntegral-        ))---- | The class IDs in a replay. Comes from the class cache.-getClassIds :: Type.Replay -> [Int]-getClassIds replay = replay-    & getClassCache-    & map (\ (x, _, _) -> x)---- | Gets the parent class ID for the given parent cache ID. This is necessary--- because there is not always a class with the given cache ID in the cache.--- When that happens, the parent cache ID is decremented and tried again.-getParentClassId :: Int -> [(Int, Int, Int)] -> Maybe Int-getParentClassId parentCacheId xs =-    case dropWhile (\ (_, cacheId, _) -> cacheId /= parentCacheId) xs of-        [] -> if parentCacheId <= 0-            then Nothing-            else getParentClassId (parentCacheId - 1) xs-        (parentClassId, _, _) : _ -> Just parentClassId---- | The basic class map is a naive mapping from class ID to its parent class--- ID. It's naive because it only maps the class ID to its immediate parent.--- It does not chase the inheritance all the way down.-getBasicClassMap :: Type.Replay -> IntMap.IntMap Int-getBasicClassMap replay = replay-    & getClassCache-    & reverse-    & List.tails-    & Maybe.mapMaybe (\ xs -> case xs of-        [] -> Nothing-        (classId, _, parentCacheId) : ys -> do-            parentClassId <- getParentClassId parentCacheId ys-            return (classId, parentClassId))-    & IntMap.fromList---- | Given a naive mapping from class ID to its parent class ID, return all of--- the parent IDs for a given class.-getParentClassIds :: Int -> IntMap.IntMap Int -> [Int]-getParentClassIds classId basicClassMap =-    case IntMap.lookup classId basicClassMap of-        Nothing -> []-        Just parentClassId -> parentClassId : getParentClassIds parentClassId basicClassMap---- | The class map is a mapping from a class ID to all of its parent class IDs.-getClassMap :: Type.Replay -> IntMap.IntMap [Int]-getClassMap replay = let-    basicClassMap = getBasicClassMap replay-    in replay-        & getClassIds-        & map (\ classId ->-            ( classId-            , getParentClassIds classId basicClassMap-            ))-        & IntMap.fromList---- | The property map is a mapping from property IDs to property names.-getPropertyMap :: Type.Replay -> IntMap.IntMap Text.Text-getPropertyMap replay = replay-    & Type.replayObjects-    & Type.unpackList-    & map Type.unpackText-    & zip [0 ..]-    & IntMap.fromList---- | The basic class property map is a naive mapping from class IDs to a--- mapping from property IDs to property names. It's naive because it does--- not include the properties from the class's parents.-getBasicClassPropertyMap :: Type.Replay -> IntMap.IntMap (IntMap.IntMap Text.Text)-getBasicClassPropertyMap replay = let-    propertyMap = getPropertyMap replay-    in replay-        & Type.replayCacheItems-        & Type.unpackList-        & map (\ x -> let-            classId = x & Type.cacheItemClassId & Type.unpackInt32 & fromIntegral-            properties = x-                & Type.cacheItemCacheProperties-                & Type.unpackList-                & Maybe.mapMaybe (\ y -> let-                    streamId = y & Type.cachePropertyStreamId & Type.unpackInt32 & fromIntegral-                    propertyId = y & Type.cachePropertyObjectId & Type.unpackInt32 & fromIntegral-                    in case IntMap.lookup propertyId propertyMap of-                        Nothing -> Nothing-                        Just name -> Just (streamId, name))-                & IntMap.fromList-            in (classId, properties))-        & IntMap.fromList---- | The actor map is a mapping from class names to their IDs.-getActorMap :: Type.Replay -> Map.Map Text.Text Int-getActorMap replay = replay-    & Type.replayActors-    & Type.unpackList-    & map (\ x -> let-        className = x & Type.actorName & Type.unpackText-        classId = x & Type.actorStreamId & Type.unpackInt32 & fromIntegral-        in (className, classId))-    & Map.fromList---- | Gets the class ID and name for a given property ID.-getClass-    :: IntMap.IntMap Text.Text -- ^ Property ID to property name-    -> Map.Map Text.Text Text.Text -- ^ Property name to class name-    -> Map.Map Text.Text Int -- ^ Class name to class ID-    -> Int -- ^ property ID-    -> Maybe (Int, Text.Text) -- ^ Maybe class ID and class name-getClass propertyIdsToNames propertyNamesToClassNames classNamesToIds propertyId =-    case IntMap.lookup propertyId propertyIdsToNames of-        Nothing -> Nothing-        Just rawPropertyName -> let-            -- There are a large number of properties that end in numbers that-            -- should all be treated the same. Instead of explicitly mapping-            -- each of them, we can remove the numbers and treat them the same.-            propertyName = rawPropertyName & Text.dropWhileEnd Char.isDigit-            in case Map.lookup propertyName propertyNamesToClassNames of-                Nothing -> Nothing-                Just className -> case Map.lookup className classNamesToIds of-                    Nothing -> Nothing-                    Just classId -> Just (classId, className)
− library/Octane/Parser/Garage.hs
@@ -1,43 +0,0 @@-module Octane.Parser.Garage where--import qualified Data.Bimap as Bimap-import qualified Data.Text as Text-import qualified Octane.Data as Data-import qualified Octane.Type as Type---type Body = Text.Text-type Decal = Text.Text-type Wheels = Text.Text-type RocketTrail = Text.Text-type Antenna = Text.Text-type Topper = Text.Text-type Finish = Text.Text---getBody :: Type.Int32 -> Maybe Body-getBody bodyId = Bimap.lookup (Type.fromInt32 bodyId) Data.bodies---getDecal :: Type.Int32 -> Maybe Decal-getDecal decalId = Bimap.lookup (Type.fromInt32 decalId) Data.decals---getWheels :: Type.Int32 -> Maybe Wheels-getWheels wheelsId = Bimap.lookup (Type.fromInt32 wheelsId) Data.wheels---getRocketTrail :: Type.Int32 -> Maybe RocketTrail-getRocketTrail rocketTrailId = Bimap.lookup (Type.fromInt32 rocketTrailId) Data.rocketTrails---getAntenna :: Type.Int32 -> Maybe Antenna-getAntenna antennaId = Bimap.lookup (Type.fromInt32 antennaId) Data.antennas---getTopper :: Type.Int32 -> Maybe Topper-getTopper topperId = Bimap.lookup (Type.fromInt32 topperId) Data.toppers---getFinish :: Type.Int32 -> Maybe Finish-getFinish finishId = Bimap.lookup (Type.fromInt32 finishId) Data.finishes
library/Octane/Type.hs view
@@ -1,1 +1,63 @@-{-# OPTIONS_GHC -F -pgmF autoexporter #-}+module Octane.Type+    ( module Octane.Type.Boolean+    , module Octane.Type.CacheItem+    , module Octane.Type.CacheProperty+    , module Octane.Type.ClassItem+    , module Octane.Type.Dictionary+    , module Octane.Type.Float32+    , module Octane.Type.Frame+    , module Octane.Type.Initialization+    , module Octane.Type.Int32+    , module Octane.Type.Int8+    , module Octane.Type.KeyFrame+    , module Octane.Type.List+    , module Octane.Type.Mark+    , module Octane.Type.Message+    , module Octane.Type.OptimizedReplay+    , module Octane.Type.Property+    , module Octane.Type.RawReplay+    , module Octane.Type.RemoteId+    , module Octane.Type.Replay+    , module Octane.Type.ReplayWithFrames+    , module Octane.Type.ReplayWithoutFrames+    , module Octane.Type.Replication+    , module Octane.Type.State+    , module Octane.Type.Stream+    , module Octane.Type.Text+    , module Octane.Type.Value+    , module Octane.Type.Word16+    , module Octane.Type.Word32+    , module Octane.Type.Word64+    , module Octane.Type.Word8+    ) where++import Octane.Type.Boolean+import Octane.Type.CacheItem+import Octane.Type.CacheProperty+import Octane.Type.ClassItem+import Octane.Type.Dictionary+import Octane.Type.Float32+import Octane.Type.Frame+import Octane.Type.Initialization+import Octane.Type.Int32+import Octane.Type.Int8+import Octane.Type.KeyFrame+import Octane.Type.List+import Octane.Type.Mark+import Octane.Type.Message+import Octane.Type.OptimizedReplay+import Octane.Type.Property+import Octane.Type.RawReplay+import Octane.Type.RemoteId+import Octane.Type.Replay+import Octane.Type.ReplayWithFrames+import Octane.Type.ReplayWithoutFrames+import Octane.Type.Replication+import Octane.Type.State+import Octane.Type.Stream+import Octane.Type.Text+import Octane.Type.Value+import Octane.Type.Word16+import Octane.Type.Word32+import Octane.Type.Word64+import Octane.Type.Word8
− library/Octane/Type/Actor.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Type.Actor (Actor(..)) where--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Binary as Binary-import Data.Function ((&))-import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Primitive.Text as Text-import qualified Octane.Type.Primitive.Int32 as Int32---- | A class (like @Core.Object@) and it's associated ID in the net stream--- (like 0).-data Actor = Actor-    { actorName :: !Text.Text-    , actorStreamId :: !Int32.Int32-    } deriving (Eq,Generics.Generic,Show)--instance Binary.Binary Actor where-    get = Actor <$> Binary.get <*> Binary.get-    put actor = do-        actor & actorName & Binary.put-        actor & actorStreamId & Binary.put--instance DeepSeq.NFData Actor--instance Aeson.ToJSON Actor where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Actor")
+ library/Octane/Type/Boolean.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}++module Octane.Type.Boolean (Boolean(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified GHC.Generics as Generics+++-- | A boolean value.+newtype Boolean = Boolean+    { unpack :: Bool+    } deriving (Eq, Generics.Generic, Show)++-- | Boolean values are stored in the last bit of a byte. Decoding will fail if+-- the byte is anything other than @0b00000000@ or @0b00000001@.+instance Binary.Binary Boolean where+    get = do+        value <- Binary.getWord8+        case value of+            0 -> pure (Boolean False)+            1 -> pure (Boolean True)+            _ -> fail ("Unexpected Boolean value " ++ show value)++    put boolean = boolean+        & unpack+        & fromEnum+        & fromIntegral+        & Binary.putWord8++instance BinaryBit.BinaryBit Boolean where+    getBits _ = do+        value <- BinaryBit.getBool+        value & Boolean & pure++    putBits _ boolean = boolean+        & unpack+        & BinaryBit.putBool++instance DeepSeq.NFData Boolean where++instance Aeson.ToJSON Boolean where+    toJSON boolean = boolean+        & unpack+        & Aeson.toJSON
library/Octane/Type/CacheItem.hs view
@@ -1,34 +1,33 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.CacheItem (CacheItem(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Json as Json import qualified Octane.Type.CacheProperty as CacheProperty-import qualified Octane.Type.Primitive.List as List-import qualified Octane.Type.Primitive.Int32 as Int32+import qualified Octane.Type.List as List+import qualified Octane.Type.Word32 as Word32 + -- | An item in the class net cache map. data CacheItem = CacheItem-    { cacheItemClassId :: !Int32.Int32-    , cacheItemParentCacheId :: !Int32.Int32-    , cacheItemCacheId :: !Int32.Int32-    , cacheItemCacheProperties :: !(List.List CacheProperty.CacheProperty)-    } deriving (Eq,Generics.Generic,Show)+    { classId :: Word32.Word32+    , parentCacheId :: Word32.Word32+    , cacheId :: Word32.Word32+    , properties :: List.List CacheProperty.CacheProperty+    } deriving (Eq, Generics.Generic, Show)  instance Binary.Binary CacheItem where     get = CacheItem <$> Binary.get <*> Binary.get <*> Binary.get <*> Binary.get     put cacheItem = do-        cacheItem & cacheItemClassId & Binary.put-        cacheItem & cacheItemParentCacheId & Binary.put-        cacheItem & cacheItemCacheId & Binary.put-        cacheItem & cacheItemCacheProperties & Binary.put--instance DeepSeq.NFData CacheItem+        cacheItem & classId & Binary.put+        cacheItem & parentCacheId & Binary.put+        cacheItem & cacheId & Binary.put+        cacheItem & properties & Binary.put -instance Aeson.ToJSON CacheItem where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "CacheItem")+instance DeepSeq.NFData CacheItem where
library/Octane/Type/CacheProperty.hs view
@@ -1,28 +1,27 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.CacheProperty (CacheProperty(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Primitive.Int32 as Int32+import qualified Octane.Type.Word32 as Word32 + -- | A property on an item in the class net cache map. data CacheProperty = CacheProperty-    { cachePropertyObjectId :: !Int32.Int32-    , cachePropertyStreamId :: !Int32.Int32-    } deriving (Eq,Generics.Generic,Show)+    { objectId :: Word32.Word32+    , streamId :: Word32.Word32+    } deriving (Eq, Generics.Generic, Show)  instance Binary.Binary CacheProperty where     get = CacheProperty <$> Binary.get <*> Binary.get     put cacheProperty = do-        cacheProperty & cachePropertyObjectId & Binary.put-        cacheProperty & cachePropertyStreamId & Binary.put--instance DeepSeq.NFData CacheProperty+        cacheProperty & objectId & Binary.put+        cacheProperty & streamId & Binary.put -instance Aeson.ToJSON CacheProperty where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "CacheProperty")+instance DeepSeq.NFData CacheProperty where
+ library/Octane/Type/ClassItem.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.ClassItem (ClassItem(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary as Binary+import qualified GHC.Generics as Generics+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32+++-- | A class (like @Core.Object@) and it's associated ID in the net stream+-- (like 0).+data ClassItem = ClassItem+    { name :: Text.Text+    , streamId :: Word32.Word32+    } deriving (Eq,Generics.Generic,Show)++instance Binary.Binary ClassItem where+    get = ClassItem <$> Binary.get <*> Binary.get+    put classItem = do+        classItem & name & Binary.put+        classItem & streamId & Binary.put++instance DeepSeq.NFData ClassItem where
+ library/Octane/Type/Dictionary.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Octane.Type.Dictionary (Dictionary(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Map.Strict as Map+import qualified GHC.Exts as Exts+import qualified GHC.Generics as Generics+import qualified Octane.Type.Text as Text+++-- | A dictionary that maps text to values.+newtype Dictionary a = Dictionary+    { unpack :: (Map.Map Text.Text a)+    } deriving (Eq, Generics.Generic)++-- | Reads elements are stored with the key first, then the value. The+-- dictionary ends when a key is @"None"@.+instance (Binary.Binary a) => Binary.Binary (Dictionary a) where+    get = do+        element <- getElement+        if Map.null element+        then element & Dictionary & pure+        else do+            Dictionary elements <- Binary.get+            elements & Map.union element & Dictionary & pure++    put dictionary = do+        dictionary & unpack & Map.assocs & mapM_ putElement+        noneKey & Binary.put++instance Exts.IsList (Dictionary a) where+    type Item (Dictionary a) = (Text.Text, a)++    fromList items = Dictionary (Map.fromList items)++    toList dictionary = Map.toList (unpack dictionary)++instance (DeepSeq.NFData a) => DeepSeq.NFData (Dictionary a) where++instance (Show a) => Show (Dictionary a) where+    show dictionary = show (unpack dictionary)++instance (Aeson.ToJSON a) => Aeson.ToJSON (Dictionary a) where+    toJSON dictionary = dictionary+        & unpack+        & Map.mapKeys Text.unpack+        & Aeson.toJSON+++getElement :: (Binary.Binary a) => Binary.Get (Map.Map Text.Text a)+getElement = do+    key <- Binary.get+    if key == noneKey+    then pure Map.empty+    else do+        value <- Binary.get+        value & Map.singleton key & pure+++putElement :: (Binary.Binary a) => (Text.Text, a) -> Binary.Put+putElement (key, value) = do+    Binary.put key+    Binary.put value+++noneKey :: Text.Text+noneKey = "None"
+ library/Octane/Type/Float32.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Float32 (Float32(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.IEEE754 as IEEE754+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+++-- | A 32-bit float.+newtype Float32 = Float32+    { unpack :: Float+    } deriving (Eq, Fractional, Generics.Generic, Num, Ord)++-- | Stored little-endian.+instance Binary.Binary Float32 where+    get = do+        value <- IEEE754.getFloat32le+        value & Float32 & pure++    put float32 = float32+        & unpack+        & IEEE754.putFloat32le++instance BinaryBit.BinaryBit Float32 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 4+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ float32 = float32+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Float32++instance Show Float32 where+    show float32 = show (unpack float32)++instance Aeson.ToJSON Float32 where+    toJSON float32 = float32+        & unpack+        & Aeson.toJSON
+ library/Octane/Type/Frame.hs view
@@ -0,0 +1,289 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.Frame (Frame(..)) where++import Data.Aeson ((.=))+import Data.Function ((&))+import Data.Monoid ((<>))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Bimap as Bimap+import qualified Data.Map.Strict as Map+import qualified Data.Text as StrictText+import qualified GHC.Generics as Generics+import qualified Octane.Data as Data+import qualified Octane.Type.Float32 as Float32+import qualified Octane.Type.Initialization as Initialization+import qualified Octane.Type.RemoteId as RemoteId+import qualified Octane.Type.Replication as Replication+import qualified Octane.Type.State as State+import qualified Octane.Type.Value as Value+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Type.Word8 as Word8+++data Frame = Frame+    { number :: Word+    , isKeyFrame :: Bool+    , time :: Float32.Float32+    , delta :: Float32.Float32+    , replications :: [Replication.Replication]+    } deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Frame where++instance Aeson.ToJSON Frame where+    toJSON frame = Aeson.object+        [ "Number" .= number frame+        , "IsKeyFrame" .= isKeyFrame frame+        , "Time" .= time frame+        , "Delta" .= delta frame+        , "Spawned" .= (frame & replications & getSpawned)+        , "Updated" .= (frame & replications & getUpdated)+        , "Destroyed" .= (frame & replications & getDestroyed)+        ]+++newtype Spawned = Spawned [Replication.Replication]++instance Aeson.ToJSON Spawned where+    toJSON (Spawned xs) = xs+        & map (\ x -> do+            let k = x & Replication.actorId & show & StrictText.pack+            let v = Aeson.object+                    [ "Name" .= Replication.objectName x+                    , "Class" .= Replication.className x+                    , "Position" .= (x & Replication.initialization & fmap Initialization.location)+                    , "Rotation" .= (x & Replication.initialization & fmap Initialization.rotation)+                    ]+            (k, v))+        & Map.fromList+        & Aeson.toJSON++getSpawned :: [Replication.Replication] -> Spawned+getSpawned xs = xs+    & filter (\ x -> x+        & Replication.state+        & (== State.SOpening))+    & Spawned+++newtype Updated = Updated [Replication.Replication]++instance Aeson.ToJSON Updated where+    toJSON (Updated xs) = xs+        & map (\ x -> do+            let k = x+                    & Replication.actorId+                    & show+                    & StrictText.pack+            let v = x+                    & Replication.properties+                    & Map.map (\ value -> Aeson.object+                        [ "Type" .= getType value+                        , "Value" .= getValue value+                        ])+            (k, v))+        & Map.fromList+        & Aeson.toJSON+++getUpdated :: [Replication.Replication] -> Updated+getUpdated xs = xs+    & filter (\ x -> x+        & Replication.state+        & (== State.SExisting))+    & filter (\ x -> x+        & Replication.properties+        & null+        & not)+    & Updated+++newtype Destroyed = Destroyed [Replication.Replication]++instance Aeson.ToJSON Destroyed where+    toJSON (Destroyed xs) = xs+        & map Replication.actorId+        & Aeson.toJSON++getDestroyed :: [Replication.Replication] -> Destroyed+getDestroyed xs = xs+    & filter (\ x -> x+        & Replication.state+        & (== State.SClosing))+    & Destroyed+++getType :: Value.Value -> StrictText.Text+getType value = case value of+    Value.VBoolean _ -> "Boolean"+    Value.VByte _ -> "Byte"+    Value.VCamSettings _ _ _ _ _ _ -> "CameraSettings"+    Value.VDemolish _ _ _ _ _ _ -> "Demolition"+    Value.VEnum _ _ -> "Enum"+    Value.VExplosion _ _ _ -> "Explosion"+    Value.VFlaggedInt _ _ -> "FlaggedInt"+    Value.VFloat _ -> "Float"+    Value.VGameMode _ -> "GameMode"+    Value.VInt _ -> "Int"+    Value.VLoadout _ _ _ _ _ _ _ _ _ -> "Loadout"+    Value.VLoadoutOnline _ _ _ _ -> "OnlineLoadout"+    Value.VLocation _ -> "Position"+    Value.VMusicStinger _ _ _ -> "MusicStinger"+    Value.VPickup _ _ _ -> "Pickup"+    Value.VPrivateMatchSettings _ _ _ _ _ _ -> "PrivateMatchSettings"+    Value.VQWord _ -> "QWord"+    Value.VRelativeRotation _ -> "RelativeRotation"+    Value.VReservation _ _ _ _ _ _ _ -> "Reservation"+    Value.VRigidBodyState _ _ _ _ _ -> "RigidBodyState"+    Value.VString _ -> "String"+    Value.VTeamPaint _ _ _ _ _ -> "Paint"+    Value.VUniqueId _ _ _ -> "UniqueId"+++getValue :: Value.Value -> Aeson.Value+getValue value = case value of+    Value.VBoolean x -> Aeson.toJSON x+    Value.VByte x -> Aeson.toJSON x+    Value.VCamSettings fov height angle distance stiffness swivelSpeed -> Aeson.object+        [ ("FOV", Aeson.toJSON fov)+        , ("Height", Aeson.toJSON height)+        , ("Angle", Aeson.toJSON angle)+        , ("Distance", Aeson.toJSON distance)+        , ("Stiffness", Aeson.toJSON stiffness)+        , ("SwivelSpeed", Aeson.toJSON swivelSpeed)+        ]+    Value.VDemolish a b c d e f -> Aeson.toJSON (a, b, c, d, e, f)+    Value.VEnum x y -> Aeson.toJSON (x, y)+    Value.VExplosion a b c -> Aeson.toJSON (a, b, c)+    Value.VFlaggedInt x y -> Aeson.toJSON (x, y)+    Value.VFloat x -> Aeson.toJSON x+    Value.VGameMode gameMode -> Aeson.object+        [ ("Id", Aeson.toJSON gameMode)+        , ("Name", gameMode & getGameMode & Aeson.toJSON)+        ]+    Value.VInt x -> Aeson.toJSON x+    Value.VLoadout version body decal wheels rocketTrail antenna topper x y -> Aeson.object+        [ ("Version", Aeson.toJSON version)+        , ("Body", Aeson.object+            [ ("Id", Aeson.toJSON body)+            , ("Name", body & getBody & Aeson.toJSON)+            ])+        , ("Decal", Aeson.object+            [ ("Id", Aeson.toJSON decal)+            , ("Name", decal & getDecal & Aeson.toJSON)+            ])+        , ("Wheels", Aeson.object+            [ ("Id", Aeson.toJSON wheels)+            , ("Name", wheels & getWheels & Aeson.toJSON)+            ])+        , ("RocketTrail", Aeson.object+            [ ("Id", Aeson.toJSON rocketTrail)+            , ("Name", rocketTrail & getRocketTrail & Aeson.toJSON)+            ])+        , ("Antenna", Aeson.object+            [ ("Id", Aeson.toJSON antenna)+            , ("Name", antenna & getAntenna & Aeson.toJSON)+            ])+        , ("Topper", Aeson.object+            [ ("Id", Aeson.toJSON topper)+            , ("Name", topper & getTopper & Aeson.toJSON)+            ])+        , ("Unknown1", Aeson.toJSON x)+        , ("Unknown2", Aeson.toJSON y)+        ]+    Value.VLoadoutOnline a b c d -> Aeson.toJSON (a, b, c, d)+    Value.VLocation x -> Aeson.toJSON x+    Value.VMusicStinger a b c -> Aeson.toJSON (a, b, c)+    Value.VPickup a b c -> Aeson.toJSON (a, b, c)+    Value.VPrivateMatchSettings mutators joinableBy maxPlayers name password x -> Aeson.object+        [ ("Mutators", Aeson.toJSON mutators)+        , ("JoinableBy", Aeson.toJSON joinableBy)+        , ("MaxPlayers", Aeson.toJSON maxPlayers)+        , ("Name", Aeson.toJSON name)+        , ("Password", Aeson.toJSON password)+        , ("Unknown", Aeson.toJSON x)+        ]+    Value.VQWord x -> Aeson.toJSON x+    Value.VRelativeRotation x -> Aeson.toJSON x+    Value.VReservation num systemId remoteId localId name x y -> Aeson.object+        [ ("Number", Aeson.toJSON num)+        , ("SystemId", Aeson.toJSON systemId)+        , ("RemoteId", Aeson.toJSON remoteId)+        , ("LocalId", Aeson.toJSON localId)+        , ("Name", Aeson.toJSON name)+        , ("Unknown1", Aeson.toJSON x)+        , ("Unknown2", Aeson.toJSON y)+        ]+    Value.VRigidBodyState sleeping position rotation linear angular -> Aeson.object+        [ ("Sleeping", Aeson.toJSON sleeping)+        , ("Position", Aeson.toJSON position)+        , ("Rotation", Aeson.toJSON rotation)+        , ("LinearVelocity", Aeson.toJSON linear)+        , ("AngularVelocity", Aeson.toJSON angular)+        ]+    Value.VString x -> Aeson.toJSON x+    Value.VTeamPaint team color1 color2 finish1 finish2 -> Aeson.object+        [ ("Team", Aeson.toJSON team)+        , ("PrimaryColor", Aeson.toJSON color1)+        , ("AccentColor", Aeson.toJSON color2)+        , ("PrimaryFinish", Aeson.object+            [ ("Id", Aeson.toJSON finish1)+            , ("Name", finish1 & getFinish & Aeson.toJSON)+            ])+        , ("AccentFinish", Aeson.object+            [ ("Id", Aeson.toJSON finish2)+            , ("Name", finish2 & getFinish & Aeson.toJSON)+            ])+        ]+    Value.VUniqueId systemId remoteId localId -> Aeson.object+        [ ("System", case systemId of+            0 -> "Local"+            1 -> "Steam"+            2 -> "PlayStation"+            4 -> "Xbox"+            _ -> Aeson.String ("Unknown system " <> StrictText.pack (show systemId)))+        , ("Remote", case remoteId of+            RemoteId.SplitscreenId x -> Aeson.toJSON x+            RemoteId.SteamId x -> Aeson.toJSON x+            RemoteId.PlayStationId x -> Aeson.toJSON x+            RemoteId.XboxId x -> Aeson.toJSON x)+        , ("Local", Aeson.toJSON localId)+        ]+++getGameMode :: Word8.Word8 -> Maybe StrictText.Text+getGameMode x = Bimap.lookup (Word8.fromWord8 x) Data.gameModes+++getBody :: Word32.Word32 -> Maybe StrictText.Text+getBody x = Bimap.lookup (Word32.fromWord32 x) Data.bodies+++getDecal :: Word32.Word32 -> Maybe StrictText.Text+getDecal x = Bimap.lookup (Word32.fromWord32 x) Data.decals+++getWheels :: Word32.Word32 -> Maybe StrictText.Text+getWheels x = Bimap.lookup (Word32.fromWord32 x) Data.wheels+++getRocketTrail :: Word32.Word32 -> Maybe StrictText.Text+getRocketTrail x = Bimap.lookup (Word32.fromWord32 x) Data.rocketTrails+++getAntenna :: Word32.Word32 -> Maybe StrictText.Text+getAntenna x = Bimap.lookup (Word32.fromWord32 x) Data.antennas+++getTopper :: Word32.Word32 -> Maybe StrictText.Text+getTopper x = Bimap.lookup (Word32.fromWord32 x) Data.toppers+++getFinish :: Word32.Word32 -> Maybe StrictText.Text+getFinish x = Bimap.lookup (Word32.fromWord32 x) Data.finishes
+ library/Octane/Type/Initialization.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.Initialization (Initialization(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified GHC.Generics as Generics+import qualified Octane.Type.Int8 as Int8+import qualified Octane.Type.Vector as Vector+++data Initialization = Initialization+    { location :: Maybe (Vector.Vector Int)+    , rotation :: Maybe (Vector.Vector Int8.Int8)+    } deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Initialization where
+ library/Octane/Type/Int32.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Int32 (Int32(..), fromInt32, toInt32) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Int as Int+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+++-- | A 32-bit signed integer.+newtype Int32 = Int32+    { unpack :: Int.Int32+    } deriving (Eq, Generics.Generic, Num, Ord)++-- | Stored in little-endian byte order.+instance Binary.Binary Int32 where+    get = do+        value <- Binary.getInt32le+        pure (Int32 value)++    put int32 = do+        let value = unpack int32+        Binary.putInt32le value++instance BinaryBit.BinaryBit Int32 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 4+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ int32 = int32+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Int32 where++-- | Shown as @1234@.+instance Show Int32 where+    show int32 = show (unpack int32)++instance Aeson.ToJSON Int32 where+    toJSON int32 = int32+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Int32' into any 'Integral' value.+--+-- >>> fromInt32 1 :: Int.Int32+-- 1+fromInt32 :: (Integral a) => Int32 -> a+fromInt32 int32 = fromIntegral (unpack int32)+++-- | Converts any 'Integral' value into a 'Int32'.+--+-- >>> toInt32 (1 :: Int.Int32)+-- 1+toInt32 :: (Integral a) => a -> Int32+toInt32 value = Int32 (fromIntegral value)
+ library/Octane/Type/Int8.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Int8 (Int8(..), fromInt8, toInt8) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Int as Int+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+++-- | A 8-bit signed integer.+newtype Int8 = Int8+    { unpack :: Int.Int8+    } deriving (Eq, Generics.Generic, Num, Ord)++instance Binary.Binary Int8 where+    get = do+        value <- Binary.getInt8+        pure (Int8 value)++    put int8 = do+        let value = unpack int8+        Binary.putInt8 value++instance BinaryBit.BinaryBit Int8 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 1+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ int8 = int8+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Int8 where++-- | Shown as @1234@.+instance Show Int8 where+    show int8 = show (unpack int8)++instance Aeson.ToJSON Int8 where+    toJSON int8 = int8+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Int8' into any 'Integral' value.+--+-- >>> fromInt8 1 :: Int.Int8+-- 1+fromInt8 :: (Integral a) => Int8 -> a+fromInt8 int8 = fromIntegral (unpack int8)+++-- | Converts any 'Integral' value into a 'Int8'.+--+-- >>> toInt8 (1 :: Int.Int8)+-- 1+toInt8 :: (Integral a) => a -> Int8+toInt8 value = Int8 (fromIntegral value)
library/Octane/Type/KeyFrame.hs view
@@ -1,33 +1,29 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.KeyFrame (KeyFrame(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Primitive.Float32 as Float32-import qualified Octane.Type.Primitive.Int32 as Int32+import qualified Octane.Type.Float32 as Float32+import qualified Octane.Type.Word32 as Word32 --- | A key frame. Each key frame has the time since the beginning of the match,--- the frame it corresponds to, and that frame's bit position in the network--- stream.+ data KeyFrame = KeyFrame-    { keyFrameTime :: !Float32.Float32-    , keyFrameFrame :: !Int32.Int32-    , keyFramePosition :: !Int32.Int32-    } deriving (Eq,Generics.Generic,Show)+    { time :: Float32.Float32+    , frame :: Word32.Word32+    , position :: Word32.Word32+    } deriving (Eq, Generics.Generic, Show)  instance Binary.Binary KeyFrame where     get = KeyFrame <$> Binary.get <*> Binary.get <*> Binary.get     put keyFrame = do-        keyFrame & keyFrameTime & Binary.put-        keyFrame & keyFrameFrame & Binary.put-        keyFrame & keyFramePosition & Binary.put--instance DeepSeq.NFData KeyFrame+        keyFrame & time & Binary.put+        keyFrame & frame & Binary.put+        keyFrame & position & Binary.put -instance Aeson.ToJSON KeyFrame where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "KeyFrame")+instance DeepSeq.NFData KeyFrame where
+ library/Octane/Type/List.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}++module Octane.Type.List (List(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Control.Monad as Monad+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified GHC.Generics as Generics+import qualified Octane.Type.Word32 as Word32+++-- | A list of valeus.+newtype List a = List+    { unpack :: [a]+    } deriving (Eq, Generics.Generic, Ord, Show)++-- | Bytewise lists are length-prefixed.+instance (Binary.Binary a) => Binary.Binary (List a) where+    get = do+        size <- Binary.get+        elements <- Monad.replicateM (Word32.fromWord32 size) Binary.get+        elements & List & return++    put list = do+        list & unpack & length & fromIntegral & Word32.Word32 & Binary.put+        list & unpack & mapM_ Binary.put++instance (DeepSeq.NFData a) => DeepSeq.NFData (List a) where++instance (Aeson.ToJSON a) => Aeson.ToJSON (List a) where+    toJSON list = list+        & unpack+        & Aeson.toJSON
library/Octane/Type/Mark.hs view
@@ -1,29 +1,27 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.Mark (Mark(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Primitive.Text as Text-import qualified Octane.Type.Primitive.Int32 as Int32+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32  -- | A tick mark on the replay. Both goals and saves make tick marks. data Mark = Mark-    { markLabel :: !Text.Text-    , markFrame :: !Int32.Int32+    { label :: Text.Text+    , frame :: Word32.Word32     } deriving (Eq,Generics.Generic,Show)  instance Binary.Binary Mark where     get = Mark <$> Binary.get <*> Binary.get     put mark = do-        mark & markLabel & Binary.put-        mark & markFrame & Binary.put+        mark & label & Binary.put+        mark & frame & Binary.put  instance DeepSeq.NFData Mark--instance Aeson.ToJSON Mark where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Mark")
library/Octane/Type/Message.hs view
@@ -1,31 +1,29 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.Message (Message(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Primitive.Text as Text-import qualified Octane.Type.Primitive.Int32 as Int32+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32  -- | A debugging message. Replays do not have any of these anymore. data Message = Message-    { messageFrame :: !Int32.Int32-    , messageName :: !Text.Text-    , messageContent :: !Text.Text-    } deriving (Eq,Generics.Generic,Show)+    { frame :: Word32.Word32+    , name :: Text.Text+    , content :: Text.Text+    } deriving (Eq, Generics.Generic, Show)  instance Binary.Binary Message where     get = Message <$> Binary.get <*> Binary.get <*> Binary.get     put message = do-        message & messageFrame & Binary.put-        message & messageName & Binary.put-        message & messageContent & Binary.put--instance DeepSeq.NFData Message+        message & frame & Binary.put+        message & name & Binary.put+        message & content & Binary.put -instance Aeson.ToJSON Message where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Message")+instance DeepSeq.NFData Message where
+ library/Octane/Type/OptimizedReplay.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.OptimizedReplay (OptimizedReplay(..), fromReplayWithFrames, toReplayWithFrames) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary as Binary+import qualified GHC.Generics as Generics+import qualified Octane.Type.CacheItem as CacheItem+import qualified Octane.Type.ClassItem as ClassItem+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.KeyFrame as KeyFrame+import qualified Octane.Type.List as List+import qualified Octane.Type.Mark as Mark+import qualified Octane.Type.Message as Message+import qualified Octane.Type.Property as Property+import qualified Octane.Type.ReplayWithFrames as ReplayWithFrames+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Utility.Optimizer as Optimizer+++data OptimizedReplay = OptimizedReplay+    { version1 :: Word32.Word32+    , version2 :: Word32.Word32+    , label :: Text.Text+    , properties :: Dictionary.Dictionary Property.Property+    , levels :: List.List Text.Text+    , keyFrames :: List.List KeyFrame.KeyFrame+    , frames :: [Frame.Frame]+    , messages :: List.List Message.Message+    , marks :: List.List Mark.Mark+    , packages :: List.List Text.Text+    , objects :: List.List Text.Text+    , names :: List.List Text.Text+    , classes :: List.List ClassItem.ClassItem+    , cache :: List.List CacheItem.CacheItem+    } deriving (Eq, Generics.Generic, Show)++instance Binary.Binary OptimizedReplay where+    get = do+        replayWithFrames <- Binary.get+        fromReplayWithFrames replayWithFrames++    put replay = do+        replayWithFrames <- toReplayWithFrames replay+        Binary.put replayWithFrames++instance DeepSeq.NFData OptimizedReplay where+++fromReplayWithFrames :: (Monad m) => ReplayWithFrames.ReplayWithFrames -> m OptimizedReplay+fromReplayWithFrames replayWithFrames = do+    pure OptimizedReplay+        { version1 = replayWithFrames & ReplayWithFrames.version1+        , version2 = replayWithFrames & ReplayWithFrames.version2+        , label = replayWithFrames & ReplayWithFrames.label+        , properties = replayWithFrames & ReplayWithFrames.properties+        , levels = replayWithFrames & ReplayWithFrames.levels+        , keyFrames = replayWithFrames & ReplayWithFrames.keyFrames+        , frames = replayWithFrames & ReplayWithFrames.frames & Optimizer.optimizeFrames+        , messages = replayWithFrames & ReplayWithFrames.messages+        , marks = replayWithFrames & ReplayWithFrames.marks+        , packages = replayWithFrames & ReplayWithFrames.packages+        , objects = replayWithFrames & ReplayWithFrames.objects+        , names = replayWithFrames & ReplayWithFrames.names+        , classes = replayWithFrames & ReplayWithFrames.classes+        , cache = replayWithFrames & ReplayWithFrames.cache+        }+++toReplayWithFrames :: (Monad m) => OptimizedReplay -> m ReplayWithFrames.ReplayWithFrames+toReplayWithFrames optimizedReplay = do+    pure ReplayWithFrames.ReplayWithFrames+        { ReplayWithFrames.version1 = optimizedReplay & version1+        , ReplayWithFrames.version2 = optimizedReplay & version2+        , ReplayWithFrames.label = optimizedReplay & label+        , ReplayWithFrames.properties = optimizedReplay & properties+        , ReplayWithFrames.levels = optimizedReplay & levels+        , ReplayWithFrames.keyFrames = optimizedReplay & keyFrames+        , ReplayWithFrames.frames = optimizedReplay & frames+        , ReplayWithFrames.messages = optimizedReplay & messages+        , ReplayWithFrames.marks = optimizedReplay & marks+        , ReplayWithFrames.packages = optimizedReplay & packages+        , ReplayWithFrames.objects = optimizedReplay & objects+        , ReplayWithFrames.names = optimizedReplay & names+        , ReplayWithFrames.classes = optimizedReplay & classes+        , ReplayWithFrames.cache = optimizedReplay & cache+        }
− library/Octane/Type/Primitive.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF autoexporter #-}
− library/Octane/Type/Primitive/Boolean.hs
@@ -1,58 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Type.Primitive.Boolean (Boolean(..)) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified GHC.Generics as Generics----- | A boolean value.-newtype Boolean = Boolean-    { unpackBoolean :: Bool-    } deriving (Eq, Generics.Generic, Show)---- | Boolean values are stored in the last bit of a byte. Decoding will fail if--- the byte is anything other than @0b00000000@ or @0b00000001@.-instance Binary.Binary Boolean where-    get = do-        value <- Binary.getWord8-        case value of-            0 -> pure (Boolean False)-            1 -> pure (Boolean True)-            _ -> fail ("Unexpected Boolean value " ++ show value)--    put boolean = boolean-        & unpackBoolean-        & fromEnum-        & fromIntegral-        & Binary.putWord8---- | Boolean values are stored as a single bit.-instance BinaryBit.BinaryBit Boolean where-    getBits _ = do-        value <- BinaryBit.getBool-        pure (Boolean value)--    putBits _ boolean = boolean-        & unpackBoolean-        & BinaryBit.putBool--instance Aeson.FromJSON Boolean where-    parseJSON json = case json of-        Aeson.Bool value -> pure (Boolean value)-        _ -> Aeson.typeMismatch "Boolean" json--instance DeepSeq.NFData Boolean where--instance Aeson.ToJSON Boolean where-    toJSON boolean = boolean-        & unpackBoolean-        & Aeson.toJSON
− library/Octane/Type/Primitive/Dictionary.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Dictionary (Dictionary(..)) where--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Binary as Binary-import Data.Function ((&))-import qualified Data.Map.Strict as Map-import qualified GHC.Generics as Generics-import qualified Octane.Type.Primitive.Text as Text---- | A dictionary that maps strings to values. The dictionary is terminated by--- the key "None".-newtype Dictionary a = Dictionary-    { unpackDictionary :: (Map.Map Text.Text a)-    } deriving (Eq,Generics.Generic,Show)--instance (Binary.Binary a) => Binary.Binary (Dictionary a) where-    get = do-        element <- getElement-        if Map.null element-            then do-                element & Dictionary & return-            else do-                Dictionary elements <- Binary.get-                elements & Map.union element & Dictionary & return-    put dictionary = do-        dictionary & unpackDictionary & Map.assocs & mapM_ putElement-        noneKey & Binary.put--instance (DeepSeq.NFData a) => DeepSeq.NFData (Dictionary a)--instance (Aeson.ToJSON a) => Aeson.ToJSON (Dictionary a) where-    toJSON dictionary =-        dictionary & unpackDictionary & Map.mapKeys Text.unpackText & Aeson.toJSON--getElement-    :: (Binary.Binary a)-    => Binary.Get (Map.Map Text.Text a)-getElement = do-    key <- Binary.get-    if key == noneKey-        then do-            return Map.empty-        else do-            value <- Binary.get-            value & Map.singleton key & return--putElement-    :: (Binary.Binary a)-    => (Text.Text, a) -> Binary.Put-putElement (key,value) = do-    Binary.put key-    Binary.put value--noneKey :: Text.Text-noneKey = "None"
− library/Octane/Type/Primitive/Float32.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Octane.Type.Primitive.Float32 (Float32(..)) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.IEEE754 as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as LazyBytes-import qualified Data.Scientific as Scientific-import qualified GHC.Generics as Generics-import qualified Octane.Utility as Utility----- | A 32-bit little-endian float.-newtype Float32 = Float32-    { unpackFloat32 :: Float-    } deriving (Eq, Fractional, Generics.Generic, Num, Ord, Show)--instance Binary.Binary Float32 where-    get = do-        value <- Binary.getFloat32le-        value & Float32 & pure--    put float32 = float32-        & unpackFloat32-        & Binary.putFloat32le--instance BinaryBit.BinaryBit Float32 where-    getBits _ = do-        bytes <- BinaryBit.getByteString 4-        let value = Binary.runGet-                Binary.getFloat32le-                (bytes & LazyBytes.fromStrict & Utility.reverseBitsInBytes)-        value & Float32 & pure--    putBits _ float32 = float32-        & unpackFloat32-        & Binary.putFloat32le-        & Binary.runPut-        & LazyBytes.toStrict-        & BinaryBit.putByteString--instance Aeson.FromJSON Float32 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedRealFloat number of-            Right float -> pure (Float32 float)-            _ -> Aeson.typeMismatch "Float32" json-        _ -> Aeson.typeMismatch "Float32" json--instance DeepSeq.NFData Float32--instance Aeson.ToJSON Float32 where-    toJSON float32 = float32-        & unpackFloat32-        & Aeson.toJSON
− library/Octane/Type/Primitive/Int32.hs
@@ -1,71 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Octane.Type.Primitive.Int32 (Int32(..), fromInt32) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as LazyBytes-import qualified Data.Int as Int-import qualified Data.Scientific as Scientific-import qualified GHC.Generics as Generics-import qualified Octane.Utility as Utility----- | A 32-bit little-endian integer.-newtype Int32 = Int32-    { unpackInt32 :: Int.Int32-    } deriving (Eq, Generics.Generic, Num, Ord, Show)--instance Binary.Binary Int32 where-    get = do-        value <- Binary.getWord32le-        value & fromIntegral & Int32 & pure--    put int32 = int32-        & unpackInt32-        & fromIntegral-        & Binary.putWord32le--instance BinaryBit.BinaryBit Int32 where-    getBits _ = do-        bytes <- BinaryBit.getByteString 4-        let value = Binary.runGet-                Binary.getWord32le-                (bytes & LazyBytes.fromStrict & Utility.reverseBitsInBytes)-        value & fromIntegral & Int32 & pure--    putBits _ int32 = int32-        & unpackInt32-        & fromIntegral-        & Binary.putWord32le-        & Binary.runPut-        & LazyBytes.toStrict-        & BinaryBit.putByteString--instance Aeson.FromJSON Int32 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedInteger number of-            Just integer -> pure (Int32 integer)-            _ -> Aeson.typeMismatch "Int32" json-        _ -> Aeson.typeMismatch "Int32" json--instance DeepSeq.NFData Int32 where--instance Aeson.ToJSON Int32 where-    toJSON int32 = int32-        & unpackInt32-        & Aeson.toJSON---fromInt32 :: (Integral a) => Int32 -> a-fromInt32 int32 = int32 & unpackInt32 & fromIntegral
− library/Octane/Type/Primitive/Int64.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Type.Primitive.Int64 (Int64(..)) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.Int as Int-import qualified Data.Scientific as Scientific-import qualified GHC.Generics as Generics----- | A 64-bit little-endian integer.-newtype Int64 = Int64-    { unpackInt64 :: Int.Int64-    } deriving (Eq, Generics.Generic, Show)--instance Binary.Binary Int64 where-    get = do-        value <- Binary.getWord64le-        value & fromIntegral & Int64 & return--    put word64 = word64-        & unpackInt64-        & fromIntegral-        & Binary.putWord64le--instance Aeson.FromJSON Int64 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedInteger number of-            Just integer -> pure (Int64 integer)-            _ -> Aeson.typeMismatch "Int64" json-        _ -> Aeson.typeMismatch "Int64" json--instance DeepSeq.NFData Int64--instance Aeson.ToJSON Int64 where-    toJSON int64 = int64-        & unpackInt64-        & Aeson.toJSON
− library/Octane/Type/Primitive/Int8.hs
@@ -1,61 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Octane.Type.Primitive.Int8 (Int8(..), fromInt8) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as LazyBytes-import qualified Data.Int as Int-import qualified Data.Scientific as Scientific-import qualified GHC.Generics as Generics-import qualified Octane.Utility as Utility----- | An 8-bit little-endian integer.-newtype Int8 = Int8-    { unpackInt8 :: Int.Int8-    } deriving (Eq, Generics.Generic, Num, Ord, Show)--instance BinaryBit.BinaryBit Int8 where-    getBits _ = do-        bytes <- BinaryBit.getByteString 1-        let value = Binary.runGet-                Binary.getWord8-                (bytes & LazyBytes.fromStrict & Utility.reverseBitsInBytes)-        value & fromIntegral & Int8 & pure--    putBits _ int8 = int8-        & unpackInt8-        & fromIntegral-        & Binary.putWord8-        & Binary.runPut-        & LazyBytes.toStrict-        & BinaryBit.putByteString--instance Aeson.FromJSON Int8 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedInteger number of-            Just integer -> pure (Int8 integer)-            _ -> Aeson.typeMismatch "Int8" json-        _ -> Aeson.typeMismatch "Int8" json--instance DeepSeq.NFData Int8 where--instance Aeson.ToJSON Int8 where-    toJSON int8 = int8-        & unpackInt8-        & Aeson.toJSON---fromInt8 :: (Integral a) => Int8 -> a-fromInt8 int8 = int8 & unpackInt8 & fromIntegral
− library/Octane/Type/Primitive/List.hs
@@ -1,63 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Type.Primitive.List (List(..)) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Control.Monad as Monad-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Vector as Vector-import qualified GHC.Generics as Generics-import qualified Octane.Type.Primitive.Int32 as Int32-import qualified Octane.Type.Primitive.Boolean as Boolean----- | A list of valeus.-newtype List a = List-    { unpackList :: [a]-    } deriving (Eq, Generics.Generic, Show)---- | Bytewise lists are length-prefixed.-instance (Binary.Binary a) => Binary.Binary (List a) where-    get = do-        (Int32.Int32 size) <- Binary.get-        elements <- Monad.replicateM (fromIntegral size) Binary.get-        elements & List & return--    put list = do-        list & unpackList & length & fromIntegral & Int32.Int32 & Binary.put-        list & unpackList & mapM_ Binary.put---- | Bitwise lists use a bit to signify if there is another element.-instance (BinaryBit.BinaryBit a) => BinaryBit.BinaryBit (List a) where-    getBits _ = do-        (Boolean.Boolean hasMore) <- BinaryBit.getBits 0-        if hasMore-            then do-                x <- BinaryBit.getBits 0-                (List xs) <- BinaryBit.getBits 0-                pure (List (x : xs))-            else pure (List [])--    putBits _ list = case unpackList list of-        [] -> BinaryBit.putBits 0 (Boolean.Boolean False)-        x : xs -> do-            BinaryBit.putBits 0 (Boolean.Boolean True)-            BinaryBit.putBits 0 x-            BinaryBit.putBits 0 (List xs)--instance (Aeson.FromJSON a) => Aeson.FromJSON (List a) where-    parseJSON json = case json of-        Aeson.Array array -> do-            values <- Vector.mapM Aeson.parseJSON array-            values & Vector.toList & List & pure-        _ -> Aeson.typeMismatch "List" json--instance (DeepSeq.NFData a) => DeepSeq.NFData (List a)--instance (Aeson.ToJSON a) => Aeson.ToJSON (List a) where-    toJSON list = list & unpackList & Aeson.toJSON
− library/Octane/Type/Primitive/Stream.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Octane.Type.Primitive.Stream (Stream(..)) where--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString as BS-import Data.Function ((&))-import qualified GHC.Generics as Generics-import qualified Octane.Type.Primitive.Int32 as Int32-import qualified Octane.Utility as Utility---- | A length-prefixed stream of bits. The length is given in bytes. Each byte--- is reversed such that 0b01234567 is actually 0b76543210.-newtype Stream = Stream-    { unpackStream :: BS.ByteString-    } deriving (Eq,Generics.Generic,Show)--instance Binary.Binary Stream where-    get = do-        Int32.Int32 size <- Binary.get-        content <- size & fromIntegral & Binary.getByteString-        content & BS.map Utility.reverseBits & Stream & return-    put stream = do-        let content = unpackStream stream-        content & BS.length & fromIntegral & Int32.Int32 & Binary.put-        content & BS.map Utility.reverseBits & Binary.putByteString--instance DeepSeq.NFData Stream--instance Aeson.ToJSON Stream where-    toJSON stream =-        let size = stream & unpackStream & BS.length-            bytes =-                if size == 1-                    then "byte"-                    else "bytes"-        in Aeson.toJSON (unwords ["Stream:", show size, bytes, "..."])
− library/Octane/Type/Primitive/Text.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Text (Text(..)) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Char8 as Bytes-import qualified Data.Char as Char-import qualified Data.String as String-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Encoding-import qualified GHC.Generics as Generics-import qualified Octane.Type.Primitive.Int32 as Int32-import qualified Octane.Utility as Utility----- | A length-prefixed null-terminated string.-newtype Text = Text-    { unpackText :: Text.Text-    } deriving (Eq, Generics.Generic, Ord, Show)--instance Binary.Binary Text where-    get = getText Binary.get Binary.getByteString--    put text = putText-        Binary.put-        Binary.putByteString-        id-        text--instance BinaryBit.BinaryBit Text where-    getBits _ = getText (BinaryBit.getBits 32) (BinaryBit.getByteString)--    putBits _ text = putText-        (BinaryBit.putBits 32)-        BinaryBit.putByteString-        Utility.reverseBitsInBytes'-        text--instance String.IsString Text where-    fromString string = string-        & Text.pack-        & Text--instance Aeson.FromJSON Text where-    parseJSON json = case json of-        Aeson.String text -> text & Text & pure-        _ -> Aeson.typeMismatch "Text" json--instance DeepSeq.NFData Text--instance Aeson.ToJSON Text where-    toJSON text = text-        & unpackText-        & Aeson.toJSON---getText :: (Monad m) => (m Int32.Int32) -> (Int -> m Bytes.ByteString) -> m Text-getText getInt getBytes = do-    (Int32.Int32 rawSize) <- getInt-    (size, decode) <--        -- In some tiny percentage of replays, this nonsensical string size-        -- shows up. As far as I can tell the next 3 bytes are always null. And-        -- the actual string is "None", which is 5 bytes including the null-        -- terminator.-        ---        -- These annoying replays come from around 2015-10-25 to 2015-11-01.-        if rawSize == 0x05000000-        then do-            bytes <- getBytes 3-            if Bytes.all (== '\0') bytes-                then pure (5, Encoding.decodeLatin1)-                else fail ("Unexpected Text bytes " ++ show bytes ++ " after size " ++ show rawSize)-        else if rawSize < 0-        then pure (-2 * fromIntegral rawSize, Encoding.decodeUtf16LE)-        else pure (fromIntegral rawSize, Encoding.decodeLatin1)-    bytes <- getBytes size-    let rawText = decode bytes-    case Text.splitAt (Text.length rawText - 1) rawText of-        (text, "") -> text & Text & pure-        (text, "\0") -> text & Text & pure-        _ -> fail ("Unexpected Text value " ++ show rawText)---putText :: (Monad m) => (Int32.Int32 -> m ()) -> (Bytes.ByteString -> m ()) -> (Bytes.ByteString -> Bytes.ByteString) -> Text -> m ()-putText putInt putBytes convertBytes text = do-    let fullText = text & unpackText & flip Text.snoc '\NUL'-    let size = fullText & Text.length & fromIntegral-    if Text.all Char.isLatin1 fullText-        then do-            size & Int32.Int32 & putInt-            fullText & encodeLatin1 & convertBytes & putBytes-        else do-            size & negate & Int32.Int32 & putInt-            fullText & Encoding.encodeUtf16LE & convertBytes & putBytes---encodeLatin1 :: Text.Text -> Bytes.ByteString-encodeLatin1 text = text-    & Text.unpack-    & Bytes.pack
− library/Octane/Type/Primitive/Word32.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Octane.Type.Primitive.Word32 (Word32(..), fromWord32) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.Scientific as Scientific-import qualified Data.Word as Word-import qualified GHC.Generics as Generics----- | A 32-bit little-endian word.-newtype Word32 = Word32-    { unpackWord32 :: Word.Word32-    } deriving (Eq, Generics.Generic, Num, Ord, Show)--instance Binary.Binary Word32 where-    get = do-        value <- Binary.getWord32le-        value & Word32 & pure--    put word32 = word32-        & unpackWord32-        & Binary.putWord32le--instance Aeson.FromJSON Word32 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedInteger number of-            Just integer -> pure (Word32 integer)-            _ -> Aeson.typeMismatch "Word32" json-        _ -> Aeson.typeMismatch "Word32" json--instance DeepSeq.NFData Word32 where--instance Aeson.ToJSON Word32 where-    toJSON word32 = word32-        & unpackWord32-        & Aeson.toJSON---fromWord32 :: (Integral a) => Word32 -> a-fromWord32 word32 = word32 & unpackWord32 & fromIntegral
− library/Octane/Type/Primitive/Word8.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Octane.Type.Primitive.Word8 (Word8(..), fromWord8) where--import Data.Function ((&))--import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Binary as Binary-import qualified Data.Binary.Bits as BinaryBit-import qualified Data.Binary.Bits.Get as BinaryBit-import qualified Data.Binary.Bits.Put as BinaryBit-import qualified Data.Binary.Get as Binary-import qualified Data.Binary.Put as Binary-import qualified Data.ByteString.Lazy as LazyBytes-import qualified Data.Word as Word-import qualified Data.Scientific as Scientific-import qualified GHC.Generics as Generics-import qualified Octane.Utility as Utility----- | An 8-bit little-endian word.-newtype Word8 = Word8-    { unpackWord8 :: Word.Word8-    } deriving (Eq, Generics.Generic, Num, Ord, Show)--instance BinaryBit.BinaryBit Word8 where-    getBits _ = do-        bytes <- BinaryBit.getByteString 1-        let value = Binary.runGet-                Binary.getWord8-                (bytes & LazyBytes.fromStrict & Utility.reverseBitsInBytes)-        value & Word8 & pure--    putBits _ word8 = word8-        & unpackWord8-        & Binary.putWord8-        & Binary.runPut-        & LazyBytes.toStrict-        & BinaryBit.putByteString--instance Aeson.FromJSON Word8 where-    parseJSON json = case json of-        Aeson.Number number -> case Scientific.toBoundedInteger number of-            Just integer -> pure (Word8 integer)-            _ -> Aeson.typeMismatch "Word8" json-        _ -> Aeson.typeMismatch "Word8" json--instance DeepSeq.NFData Word8 where--instance Aeson.ToJSON Word8 where-    toJSON word8 = word8-        & unpackWord8-        & Aeson.toJSON---fromWord8 :: (Integral a) => Word8 -> a-fromWord8 word8 = word8 & unpackWord8 & fromIntegral
library/Octane/Type/Property.hs view
@@ -1,42 +1,46 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-}  module Octane.Type.Property (Property(..)) where +import Data.Function ((&))+ import qualified Control.DeepSeq as DeepSeq-import qualified Data.Aeson.Types as Aeson+import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import Data.Function ((&)) import qualified GHC.Generics as Generics-import qualified Octane.Type.Primitive.Boolean as Boolean-import qualified Octane.Type.Primitive.Dictionary as Dictionary-import qualified Octane.Type.Primitive.Float32 as Float32-import qualified Octane.Type.Primitive.List as List-import qualified Octane.Type.Primitive.Text as Text-import qualified Octane.Type.Primitive.Int32 as Int32-import qualified Octane.Type.Primitive.Int64 as Int64+import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.Float32 as Float32+import qualified Octane.Type.Int32 as Int32+import qualified Octane.Type.List as List+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word64 as Word64 + -- | A metadata property. All properties have a size, but only some actually -- use it. The value stored in the property can be an array, a boolean, and -- so on. data Property-    = ArrayProperty !Int64.Int64-                    !(List.List (Dictionary.Dictionary Property))-    | BoolProperty !Int64.Int64-                   !Boolean.Boolean-    | ByteProperty !Int64.Int64-                   !(Text.Text, Text.Text)-    | FloatProperty !Int64.Int64-                    !Float32.Float32-    | IntProperty !Int64.Int64-                  !Int32.Int32-    | NameProperty !Int64.Int64-                   !Text.Text-    | QWordProperty !Int64.Int64-                    !Int64.Int64-    | StrProperty !Int64.Int64-                  !Text.Text-    deriving (Eq,Generics.Generic,Show)+    = ArrayProperty Word64.Word64+                    (List.List (Dictionary.Dictionary Property))+    | BoolProperty Word64.Word64+                   Boolean.Boolean+    | ByteProperty Word64.Word64+                   (Text.Text, Text.Text)+    | FloatProperty Word64.Word64+                    Float32.Float32+    | IntProperty Word64.Word64+                  Int32.Int32+    | NameProperty Word64.Word64+                   Text.Text+    | QWordProperty Word64.Word64+                    Word64.Word64+    | StrProperty Word64.Word64+                  Text.Text+    deriving (Eq, Generics.Generic, Show)  instance Binary.Binary Property where     get = do@@ -60,17 +64,17 @@                         (key, value) & ByteProperty size & return             _ | kind == floatProperty -> do                 size <- Binary.get-                value <--                    case Int64.unpackInt64 size of-                        4 -> Binary.get-                        x -> fail ("unknown FloatProperty size " ++ show x)+                value <- case size of+                    4 -> Binary.get+                    (Word64.Word64 x) ->+                        fail ("unknown FloatProperty size " ++ show x)                 value & FloatProperty size & return             _ | kind == intProperty -> do                 size <- Binary.get-                value <--                    case Int64.unpackInt64 size of-                        4 -> Binary.get-                        x -> fail ("unknown IntProperty size " ++ show x)+                value <- case size of+                    4 -> Binary.get+                    (Word64.Word64 x) ->+                        fail ("unknown IntProperty size " ++ show x)                 value & IntProperty size & return             _ | kind == nameProperty -> do                 size <- Binary.get@@ -78,16 +82,16 @@                 value & NameProperty size & return             _ | kind == qWordProperty -> do                 size <- Binary.get-                value <--                    case Int64.unpackInt64 size of-                        8 -> Binary.get-                        x -> fail ("unknown QWordProperty size " ++ show x)+                value <- case size of+                    8 -> Binary.get+                    (Word64.Word64 x) ->+                        fail ("unknown QWordProperty size " ++ show x)                 value & QWordProperty size & return             _ | kind == strProperty -> do                 size <- Binary.get                 value <- Binary.get                 value & StrProperty size & return-            _ -> fail ("unknown property type " ++ show (Text.unpackText kind))+            _ -> fail ("unknown property type " ++ show (Text.unpack kind))     put property =         case property of             ArrayProperty size value -> do@@ -124,19 +128,20 @@                 Binary.put size                 Binary.put value -instance DeepSeq.NFData Property+instance DeepSeq.NFData Property where +-- TODO: This encoding is lossy. instance Aeson.ToJSON Property where-    toJSON property =-        case property of-            ArrayProperty _ x -> Aeson.toJSON x-            BoolProperty _ x -> Aeson.toJSON x-            ByteProperty _ (_,x) -> Aeson.toJSON x-            FloatProperty _ x -> Aeson.toJSON x-            IntProperty _ x -> Aeson.toJSON x-            NameProperty _ x -> Aeson.toJSON x-            QWordProperty _ x -> Aeson.toJSON x-            StrProperty _ x -> Aeson.toJSON x+    toJSON property = case property of+        ArrayProperty _size x -> Aeson.toJSON x+        BoolProperty _size x -> Aeson.toJSON x+        ByteProperty _size (_key, value) -> Aeson.toJSON value+        FloatProperty _size x -> Aeson.toJSON x+        IntProperty _size x -> Aeson.toJSON x+        NameProperty _size x -> Aeson.toJSON x+        QWordProperty _size x -> Aeson.toJSON x+        StrProperty _size x -> Aeson.toJSON x+  arrayProperty :: Text.Text arrayProperty = "ArrayProperty"
+ library/Octane/Type/RawReplay.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.RawReplay (RawReplay(..), newRawReplay) where++import qualified Control.DeepSeq as DeepSeq+import qualified Control.Monad as Monad+import qualified Data.Binary as Binary+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified GHC.Generics as Generics+import qualified Octane.Utility.CRC as CRC+import qualified Octane.Type.Word32 as Word32+import qualified Text.Printf as Printf+++data RawReplay = RawReplay+    { headerSize :: Word32.Word32+    -- ^ The byte size of the first section.+    , headerCRC :: Word32.Word32+    -- ^ The CRC of the first section.+    , header :: LazyBytes.ByteString+    -- ^ The first section.+    , contentSize :: Word32.Word32+    -- ^ The byte size of the second section.+    , contentCRC :: Word32.Word32+    -- ^ The CRC of the second section.+    , content :: LazyBytes.ByteString+    -- ^ The second section.+    , footer :: LazyBytes.ByteString+    -- ^ Arbitrary data after the second section. In replays generated by+    -- Rocket League, this is always empty. However it is not technically+    -- invalid to put something here.+    } deriving (Eq, Generics.Generic, Show)++-- | Decoding will fail if the CRCs don't match, but it is possible to encode+-- invalid replays. That means @decode (encode rawReplay)@ can fail.+instance Binary.Binary RawReplay where+    get = do+        headerSize <- Binary.get+        headerCRC <- Binary.get+        header <- Binary.getLazyByteString (Word32.fromWord32 headerSize)+        checkCRC headerCRC header++        contentSize <- Binary.get+        contentCRC <- Binary.get+        content <- Binary.getLazyByteString (Word32.fromWord32 contentSize)+        checkCRC contentCRC content++        footer <- Binary.getRemainingLazyByteString++        pure RawReplay { .. }++    put replay = do+        Binary.put (headerSize replay)+        Binary.put (headerCRC replay)+        Binary.putLazyByteString (header replay)++        Binary.put (contentSize replay)+        Binary.put (contentCRC replay)+        Binary.putLazyByteString (content replay)++        Binary.putLazyByteString (footer replay)++instance DeepSeq.NFData RawReplay where+++newRawReplay+    :: LazyBytes.ByteString -- ^ The 'header'.+    -> LazyBytes.ByteString -- ^ The 'content'.+    -> LazyBytes.ByteString -- ^ The 'footer'.+    -> RawReplay+newRawReplay header content footer = do+    let headerSize = Word32.toWord32 (LazyBytes.length header)+    let headerCRC = Word32.Word32 (CRC.crc32 header)++    let contentSize = Word32.toWord32 (LazyBytes.length content)+    let contentCRC = Word32.Word32 (CRC.crc32 content)++    RawReplay { .. }+++checkCRC :: (Monad m) => Word32.Word32 -> LazyBytes.ByteString -> m ()+checkCRC (Word32.Word32 expected) bytes = do+    let actual = CRC.crc32 bytes+    Monad.when (actual /= expected) (do+        let message = Printf.printf+                "CRC 0x%08x does not equal expected value 0x%08x"+                actual+                expected+        fail message)
+ library/Octane/Type/RemoteId.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.RemoteId (RemoteId(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified GHC.Generics as Generics+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word64 as Word64+++data RemoteId+    = SteamId Word64.Word64+    | PlayStationId Text.Text+    | SplitscreenId (Maybe Int)+    | XboxId Word64.Word64+    deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData RemoteId where++instance Aeson.ToJSON RemoteId where
library/Octane/Type/Replay.hs view
@@ -1,163 +1,144 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-} -module Octane.Type.Replay (Replay(..)) where+module Octane.Type.Replay (Replay(..), fromOptimizedReplay, toOptimizedReplay) where +import Data.Aeson ((.=)) import Data.Function ((&))  import qualified Control.DeepSeq as DeepSeq-import qualified Control.Monad as Monad import qualified Data.Aeson as Aeson import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Map as Map+import qualified Data.Text as StrictText+import qualified Data.Version as Version import qualified GHC.Generics as Generics-import qualified Octane.Json as Json-import qualified Octane.Type.Actor as Actor-import qualified Octane.Type.CacheItem as CacheItem-import qualified Octane.Type.KeyFrame as KeyFrame+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.List as List import qualified Octane.Type.Mark as Mark import qualified Octane.Type.Message as Message-import qualified Octane.Type.Primitive.Dictionary as Dictionary-import qualified Octane.Type.Primitive.Int32 as Int32-import qualified Octane.Type.Primitive.List as List-import qualified Octane.Type.Primitive.Stream as Stream-import qualified Octane.Type.Primitive.Text as Text-import qualified Octane.Type.Primitive.Word32 as Word32+import qualified Octane.Type.OptimizedReplay as OptimizedReplay import qualified Octane.Type.Property as Property-import qualified Octane.Utility as Utility-import qualified Text.Printf as Printf+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32 --- | An entire replay. All of the metadata has been parsed, but the actual net--- stream has not.+ data Replay = Replay-    -- Number of bytes in the first section.-    { replaySize1 :: !Word32.Word32-    -- CRC to check the first section.-    , replayCRC1 :: !Word32.Word32-    -- Major replay version number.-    , replayVersion1 :: !Int32.Int32-    -- Minor replay version number.-    , replayVersion2 :: !Int32.Int32-    -- Label, which is always "TAGame.Replay_Soccar_TA".-    , replayLabel :: !Text.Text-    -- High-level metadata about the replay.-    , replayProperties :: !(Dictionary.Dictionary Property.Property)-    -- Number of bytes in the last section.-    , replaySize2 :: !Word32.Word32-    -- CRC to check the last section.-    , replayCRC2 :: !Word32.Word32-    -- Array of strings for all of the levels that need to be loaded (array-    -- length followed by each string)-    , replayLevels :: !(List.List Text.Text)-    -- Array of Keyframe information used for timeline scrubbing (array-    -- length followed by each keyframe struct) (Time, Frame, File Position)-    , replayKeyFrames :: !(List.List KeyFrame.KeyFrame)-    -- Array of bytes that is the bulk of the data. This is the raw network-    -- stream. (array length followed by a bunch of bytes)-    , replayStream :: !Stream.Stream-    -- Array of debugging logs (strings). This reminds me that I should-    -- probably turn these off to make the replays smaller. (array length-    -- followed by each string)-    , replayMessages :: !(List.List Message.Message)-    -- Array of information used to display the Tick marks in the replay (goal-    -- scores). (array length followed by each tick struct) (Type, Frame)-    , replayMarks :: !(List.List Mark.Mark)-    -- Array of strings of replicated Packages-    , replayPackages :: !(List.List Text.Text)-    -- Array of strings for the Object table. Whenever a persistent object gets-    -- referenced in the network stream its path gets added to this array. Then-    -- its index in this array is used in the network stream.-    , replayObjects :: !(List.List Text.Text)-    -- Array of strings for the Name table. "Names" are commonly used strings-    -- that get assigned an integer for use in the network stream.-    , replayNames :: !(List.List Text.Text)-    -- Map of string, integer pairs for the Class Index Map. Whenever a class-    -- is used in the network stream it is given an integer id by this map.-    , replayActors :: !(List.List Actor.Actor)-    -- "Class Net Cache Map" maps each replicated property in a class to an-    -- integer id used in the network stream.-    , replayCacheItems :: !(List.List CacheItem.CacheItem)-    } deriving (Eq,Generics.Generic,Show)+    { version :: Version.Version+    , metadata :: Map.Map StrictText.Text Property.Property+    , levels :: [StrictText.Text]+    , messages :: Map.Map StrictText.Text StrictText.Text+    , tickMarks :: Map.Map StrictText.Text StrictText.Text+    , frames :: [Frame.Frame]+    } deriving (Eq, Generics.Generic, Show)  instance Binary.Binary Replay where     get = do-        size1 <- Binary.get-        crc1 <- Binary.get-        data1 <- Binary.getLazyByteString (Word32.fromWord32 size1)-        let actualCRC1 = Utility.crc32 data1-        let expectedCRC1 = Word32.fromWord32 crc1-        Monad.when (actualCRC1 /= expectedCRC1) (fail (Printf.printf-            "First CRC 0x%08x does not match expected value 0x%08x"-            actualCRC1 expectedCRC1))+        optimizedReplay <- Binary.get+        fromOptimizedReplay optimizedReplay -        size2 <- Binary.get-        crc2 <- Binary.get-        data2 <- Binary.getLazyByteString (Word32.fromWord32 size2)-        let actualCRC2 = Utility.crc32 data2-        let expectedCRC2 = Word32.fromWord32 crc2-        Monad.when (actualCRC2 /= expectedCRC2) (fail (Printf.printf-            "Second CRC 0x%08x does not match expected value 0x%08x"-            actualCRC2 expectedCRC2))+    put replay = do+        optimizedReplay <- toOptimizedReplay replay+        Binary.put optimizedReplay -        pure (flip Binary.runGet (LazyBytes.append data1 data2) (do-            version1 <- Binary.get-            version2 <- Binary.get-            label <- Binary.get-            properties <- Binary.get+instance DeepSeq.NFData Replay where -            levels <- Binary.get-            keyFrames <- Binary.get-            stream <- Binary.get-            messages <- Binary.get-            marks <- Binary.get-            packages <- Binary.get-            objects <- Binary.get-            names <- Binary.get-            actors <- Binary.get-            cacheItems <- Binary.get+instance Aeson.ToJSON Replay where+    toJSON replay = Aeson.object+        [ "Version" .= version replay+        , "Metadata" .= metadata replay+        , "Levels" .= levels replay+        , "Messages" .= messages replay+        , "TickMarks" .= tickMarks replay+        , "Frames" .= frames replay+        ] -            pure Replay-                { replaySize1 = size1-                , replayCRC1 = crc1-                , replayVersion1 = version1-                , replayVersion2 = version2-                , replayLabel = label-                , replayProperties = properties-                , replaySize2 = size2-                , replayCRC2 = crc2-                , replayLevels = levels-                , replayKeyFrames = keyFrames-                , replayStream = stream-                , replayMessages = messages-                , replayMarks = marks-                , replayPackages = packages-                , replayObjects = objects-                , replayNames = names-                , replayActors = actors-                , replayCacheItems = cacheItems-                })) -    put replay = do-        replay & replaySize1 & Binary.put-        replay & replayCRC1 & Binary.put-        replay & replayVersion1 & Binary.put-        replay & replayVersion2 & Binary.put-        replay & replayLabel & Binary.put-        replay & replayProperties & Binary.put-        replay & replaySize2 & Binary.put-        replay & replayCRC2 & Binary.put-        replay & replayLevels & Binary.put-        replay & replayKeyFrames & Binary.put-        replay & replayStream & Binary.put-        replay & replayMessages & Binary.put-        replay & replayMarks & Binary.put-        replay & replayPackages & Binary.put-        replay & replayObjects & Binary.put-        replay & replayNames & Binary.put-        replay & replayActors & Binary.put-        replay & replayCacheItems & Binary.put+fromOptimizedReplay :: (Monad m) => OptimizedReplay.OptimizedReplay -> m Replay+fromOptimizedReplay optimizedReplay = do+    pure Replay+        { version =+            [ OptimizedReplay.version1 optimizedReplay+            , OptimizedReplay.version2 optimizedReplay+            ] & map Word32.fromWord32 & Version.makeVersion+        , metadata = optimizedReplay+            & OptimizedReplay.properties+            & Dictionary.unpack+            & Map.mapKeys Text.unpack+        , levels = optimizedReplay+            & OptimizedReplay.levels+            & List.unpack+            & map Text.unpack+        , messages = optimizedReplay+            & OptimizedReplay.messages+            & List.unpack+            & map (\ message -> do+                let key = message+                        & Message.frame+                        & Word32.unpack+                        & show+                        & StrictText.pack+                let value = message+                        & Message.content+                        & Text.unpack+                (key, value))+            & Map.fromList+        , tickMarks = optimizedReplay+            & OptimizedReplay.marks+            & List.unpack+            & map (\ mark -> do+                let key = mark+                        & Mark.frame+                        & Word32.unpack+                        & show+                        & StrictText.pack+                let value = mark+                        & Mark.label+                        & Text.unpack+                (key, value))+            & Map.fromList+        , frames = optimizedReplay+            & OptimizedReplay.frames+        } -instance DeepSeq.NFData Replay -instance Aeson.ToJSON Replay where-    toJSON = Aeson.genericToJSON (Json.toJsonOptions "Replay")+toOptimizedReplay :: (Monad m) => Replay -> m OptimizedReplay.OptimizedReplay+toOptimizedReplay replay = do+    let [version1, version2] = replay+            & version+            & Version.versionBranch+            & map Word32.toWord32++    pure OptimizedReplay.OptimizedReplay+        { OptimizedReplay.version1 = version1+        , OptimizedReplay.version2 = version2+        , OptimizedReplay.label = "TAGame.Replay_Soccar_TA"+        , OptimizedReplay.properties = replay & metadata & Map.mapKeys Text.Text & Dictionary.Dictionary+        , OptimizedReplay.levels = replay & levels & map Text.Text & List.List+        , OptimizedReplay.keyFrames = List.List [] -- TODO+        , OptimizedReplay.frames = replay & frames+        , OptimizedReplay.messages = replay+            & messages+            & Map.toList+            & map (\ (key, value) -> do+                let frame = key & StrictText.unpack & read & Word32.Word32+                let content = value & Text.Text+                Message.Message frame "" content)+            & List.List+        , OptimizedReplay.marks = replay+            & tickMarks+            & Map.toList+            & map (\ (key, value) -> do+                let label = value & Text.Text+                let frame = key & StrictText.unpack & read & Word32.Word32+                Mark.Mark label frame)+            & List.List+        , OptimizedReplay.packages = List.List [] -- TODO+        , OptimizedReplay.objects = List.List [] -- TODO+        , OptimizedReplay.names = List.List [] -- TODO+        , OptimizedReplay.classes = List.List [] -- TODO+        , OptimizedReplay.cache = List.List [] -- TODO+        }
+ library/Octane/Type/ReplayWithFrames.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.ReplayWithFrames (ReplayWithFrames(..), fromReplayWithoutFrames, toReplayWithoutFrames) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified GHC.Generics as Generics+import qualified Octane.Type.CacheItem as CacheItem+import qualified Octane.Type.ClassItem as ClassItem+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.KeyFrame as KeyFrame+import qualified Octane.Type.List as List+import qualified Octane.Type.Mark as Mark+import qualified Octane.Type.Message as Message+import qualified Octane.Type.Property as Property+import qualified Octane.Type.ReplayWithoutFrames as ReplayWithoutFrames+import qualified Octane.Type.Stream as Stream+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Utility.Parser as Parser+++data ReplayWithFrames = ReplayWithFrames+    { version1 :: Word32.Word32+    , version2 :: Word32.Word32+    , label :: Text.Text+    , properties :: Dictionary.Dictionary Property.Property+    , levels :: List.List Text.Text+    , keyFrames :: List.List KeyFrame.KeyFrame+    , frames :: [Frame.Frame]+    , messages :: List.List Message.Message+    , marks :: List.List Mark.Mark+    , packages :: List.List Text.Text+    , objects :: List.List Text.Text+    , names :: List.List Text.Text+    , classes :: List.List ClassItem.ClassItem+    , cache :: List.List CacheItem.CacheItem+    } deriving (Eq, Generics.Generic, Show)++instance Binary.Binary ReplayWithFrames where+    get = do+        replayWithoutFrames <- Binary.get+        fromReplayWithoutFrames replayWithoutFrames++    put replay = do+        replayWithoutFrames <- toReplayWithoutFrames replay+        Binary.put replayWithoutFrames++instance DeepSeq.NFData ReplayWithFrames where+++fromReplayWithoutFrames :: (Monad m) => ReplayWithoutFrames.ReplayWithoutFrames -> m ReplayWithFrames+fromReplayWithoutFrames replayWithoutFrames = do+    pure ReplayWithFrames+        { version1 = replayWithoutFrames & ReplayWithoutFrames.version1+        , version2 = replayWithoutFrames & ReplayWithoutFrames.version2+        , label = replayWithoutFrames & ReplayWithoutFrames.label+        , properties = replayWithoutFrames & ReplayWithoutFrames.properties+        , levels = replayWithoutFrames & ReplayWithoutFrames.levels+        , keyFrames = replayWithoutFrames & ReplayWithoutFrames.keyFrames+        , frames = replayWithoutFrames & Parser.parseFrames+        , messages = replayWithoutFrames & ReplayWithoutFrames.messages+        , marks = replayWithoutFrames & ReplayWithoutFrames.marks+        , packages = replayWithoutFrames & ReplayWithoutFrames.packages+        , objects = replayWithoutFrames & ReplayWithoutFrames.objects+        , names = replayWithoutFrames & ReplayWithoutFrames.names+        , classes = replayWithoutFrames & ReplayWithoutFrames.classes+        , cache = replayWithoutFrames & ReplayWithoutFrames.cache+        }+++toReplayWithoutFrames :: (Monad m) => ReplayWithFrames -> m ReplayWithoutFrames.ReplayWithoutFrames+toReplayWithoutFrames replayWithFrames = do+    pure ReplayWithoutFrames.ReplayWithoutFrames+        { ReplayWithoutFrames.version1 = replayWithFrames & version1+        , ReplayWithoutFrames.version2 = replayWithFrames & version2+        , ReplayWithoutFrames.label = replayWithFrames & label+        , ReplayWithoutFrames.properties = replayWithFrames & properties+        , ReplayWithoutFrames.levels = replayWithFrames & levels+        , ReplayWithoutFrames.keyFrames = replayWithFrames & keyFrames+        , ReplayWithoutFrames.stream = Stream.Stream LazyBytes.empty -- TODO+        , ReplayWithoutFrames.messages = replayWithFrames & messages+        , ReplayWithoutFrames.marks = replayWithFrames & marks+        , ReplayWithoutFrames.packages = replayWithFrames & packages+        , ReplayWithoutFrames.objects = replayWithFrames & objects+        , ReplayWithoutFrames.names = replayWithFrames & names+        , ReplayWithoutFrames.classes = replayWithFrames & classes+        , ReplayWithoutFrames.cache = replayWithFrames & cache+        }
+ library/Octane/Type/ReplayWithoutFrames.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.ReplayWithoutFrames (ReplayWithoutFrames(..), fromRawReplay, toRawReplay) where++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary as Binary+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified GHC.Generics as Generics+import qualified Octane.Type.CacheItem as CacheItem+import qualified Octane.Type.ClassItem as ClassItem+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.KeyFrame as KeyFrame+import qualified Octane.Type.List as List+import qualified Octane.Type.Mark as Mark+import qualified Octane.Type.Message as Message+import qualified Octane.Type.Property as Property+import qualified Octane.Type.RawReplay as RawReplay+import qualified Octane.Type.Stream as Stream+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32+++data ReplayWithoutFrames = ReplayWithoutFrames+    { version1 :: Word32.Word32+    , version2 :: Word32.Word32+    , label :: Text.Text+    , properties :: Dictionary.Dictionary Property.Property+    , levels :: List.List Text.Text+    , keyFrames :: List.List KeyFrame.KeyFrame+    , stream :: Stream.Stream+    , messages :: List.List Message.Message+    , marks :: List.List Mark.Mark+    , packages :: List.List Text.Text+    , objects :: List.List Text.Text+    , names :: List.List Text.Text+    , classes :: List.List ClassItem.ClassItem+    , cache :: List.List CacheItem.CacheItem+    } deriving (Eq, Generics.Generic, Show)++instance Binary.Binary ReplayWithoutFrames where+    get = do+        rawReplay <- Binary.get+        fromRawReplay rawReplay++    put replayWithoutFrames = do+        rawReplay <- toRawReplay replayWithoutFrames+        Binary.put rawReplay++instance DeepSeq.NFData ReplayWithoutFrames where+++fromRawReplay :: (Monad m) => RawReplay.RawReplay -> m ReplayWithoutFrames+fromRawReplay rawReplay = do+    let header = RawReplay.header rawReplay+    let content = RawReplay.content rawReplay++    let get = do+            version1 <- Binary.get+            version2 <- Binary.get+            label <- Binary.get+            properties <- Binary.get+            levels <- Binary.get+            keyFrames <- Binary.get+            stream <- Binary.get+            messages <- Binary.get+            marks <- Binary.get+            packages <- Binary.get+            objects <- Binary.get+            names <- Binary.get+            classes <- Binary.get+            cache <- Binary.get++            pure ReplayWithoutFrames { .. }+    let bytes = LazyBytes.append header content++    pure (Binary.runGet get bytes)+++toRawReplay :: (Monad m) => ReplayWithoutFrames -> m RawReplay.RawReplay+toRawReplay replay = do+    let header = Binary.runPut (do+            Binary.put (version1 replay)+            Binary.put (version2 replay)+            Binary.put (label replay)+            Binary.put (properties replay))++    let content = Binary.runPut (do+            Binary.put (levels replay)+            Binary.put (keyFrames replay)+            Binary.put (stream replay)+            Binary.put (messages replay)+            Binary.put (marks replay)+            Binary.put (packages replay)+            Binary.put (objects replay)+            Binary.put (names replay)+            Binary.put (classes replay)+            Binary.put (cache replay))++    let footer = LazyBytes.empty++    pure (RawReplay.newRawReplay header content footer)
+ library/Octane/Type/Replication.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.Replication (Replication(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Map.Strict as Map+import qualified Data.Text as StrictText+import qualified GHC.Generics as Generics+import qualified Octane.Type.Initialization as Initialization+import qualified Octane.Type.State as State+import qualified Octane.Type.Value as Value+++data Replication = Replication+    { actorId :: Word+    , objectName :: StrictText.Text+    , className :: StrictText.Text+    , state :: State.State+    , initialization :: Maybe Initialization.Initialization+    , properties :: Map.Map StrictText.Text Value.Value+    } deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Replication where
+ library/Octane/Type/State.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveGeneric #-}++module Octane.Type.State (State(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified GHC.Generics as Generics+++data State+    = SOpening+    | SExisting+    | SClosing+    deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData State where
+ library/Octane/Type/Stream.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}++module Octane.Type.Stream (Stream(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary as Binary+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified GHC.Generics as Generics+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Utility.Endian as Endian+++-- | A length-prefixed stream of bits. The length is given in bytes. Each byte+-- is reversed such that @0b01234567@ is actually @0b76543210@.+newtype Stream = Stream+    { unpack :: LazyBytes.ByteString+    } deriving (Eq, Generics.Generic, Show)++instance Binary.Binary Stream where+    get = do+        size <- Binary.get+        content <- size & Word32.fromWord32 & Binary.getLazyByteString+        content & Endian.reverseBitsInBytes & Stream & return+    put stream = do+        let content = unpack stream+        content & LazyBytes.length & Word32.toWord32 & Binary.put+        content & Endian.reverseBitsInBytes & Binary.putLazyByteString++instance DeepSeq.NFData Stream where
+ library/Octane/Type/Text.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module Octane.Type.Text (Text(..)) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Char8 as StrictBytes+import qualified Data.Char as Char+import qualified Data.String as String+import qualified Data.Text as StrictText+import qualified Data.Text.Encoding as Encoding+import qualified GHC.Generics as Generics+import qualified Octane.Type.Int32 as Int32+import qualified Octane.Utility.Endian as Endian+++-- | A thin wrapper around 'StrictText.Text'.+newtype Text = Text+    { unpack :: StrictText.Text+    } deriving (Eq, Generics.Generic, Ord)++-- | Text is both length-prefixed and null-terminated.+instance Binary.Binary Text where+    get = getText+        Binary.get+        Binary.getByteString+        id++    put text = putText+        Binary.put+        Binary.putByteString+        id+        text++instance BinaryBit.BinaryBit Text where+    getBits _ = getText+        (BinaryBit.getBits 32)+        BinaryBit.getByteString+        Endian.reverseBitsInBytes'++    putBits _ text = putText+        (BinaryBit.putBits 32)+        BinaryBit.putByteString+        Endian.reverseBitsInBytes'+        text++instance String.IsString Text where+    fromString string = Text (StrictText.pack string)++instance DeepSeq.NFData Text where++instance Show Text where+    show text = StrictText.unpack (unpack text)++instance Aeson.ToJSON Text where+    toJSON text = text+        & unpack+        & Aeson.toJSON+++getText :: (Monad m) => (m Int32.Int32) -> (Int -> m StrictBytes.ByteString) -> (StrictBytes.ByteString -> StrictBytes.ByteString) -> m Text+getText getInt getBytes convertBytes = do+    (Int32.Int32 rawSize) <- getInt+    (size, decode) <-+        -- In some tiny percentage of replays, this nonsensical string size+        -- shows up. As far as I can tell the next 3 bytes are always null. And+        -- the actual string is "None", which is 5 bytes including the null+        -- terminator.+        --+        -- These annoying replays come from around 2015-10-25 to 2015-11-01.+        if rawSize == 0x05000000+        then do+            bytes <- getBytes 3+            if StrictBytes.all (== '\0') bytes+                then pure (5, Encoding.decodeLatin1)+                else fail ("Unexpected Text bytes " ++ show bytes ++ " after size " ++ show rawSize)+        else if rawSize < 0+        then pure (-2 * fromIntegral rawSize, Encoding.decodeUtf16LE)+        else pure (fromIntegral rawSize, Encoding.decodeLatin1)+    bytes <- getBytes size+    let rawText = bytes & convertBytes & decode+    case StrictText.splitAt (StrictText.length rawText - 1) rawText of+        (text, "") -> text & Text & pure+        (text, "\0") -> text & Text & pure+        _ -> fail ("Unexpected Text value " ++ show rawText)+++putText :: (Monad m) => (Int32.Int32 -> m ()) -> (StrictBytes.ByteString -> m ()) -> (StrictBytes.ByteString -> StrictBytes.ByteString) -> Text -> m ()+putText putInt putBytes convertBytes text = do+    let fullText = text & unpack & flip StrictText.snoc '\NUL'+    let size = fullText & StrictText.length & fromIntegral+    if StrictText.all Char.isLatin1 fullText+    then do+        size & Int32.Int32 & putInt+        fullText & encodeLatin1 & convertBytes & putBytes+    else do+        size & negate & Int32.Int32 & putInt+        fullText & Encoding.encodeUtf16LE & convertBytes & putBytes+++encodeLatin1 :: StrictText.Text -> StrictBytes.ByteString+encodeLatin1 text = text+    & StrictText.unpack+    & StrictBytes.pack
+ library/Octane/Type/Value.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.Value (Value(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified GHC.Generics as Generics+import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.Float32 as Float32+import qualified Octane.Type.Int32 as Int32+import qualified Octane.Type.RemoteId as RemoteId+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Vector as Vector+import qualified Octane.Type.Word16 as Word16+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Type.Word64 as Word64+import qualified Octane.Type.Word8 as Word8+++data Value+    = VBoolean+        Boolean.Boolean+    | VByte+        Word8.Word8+    | VCamSettings+        Float32.Float32+        Float32.Float32+        Float32.Float32+        Float32.Float32+        Float32.Float32+        Float32.Float32+    | VDemolish+        Boolean.Boolean+        Word32.Word32+        Boolean.Boolean+        Word32.Word32+        (Vector.Vector Int)+        (Vector.Vector Int)+    | VEnum+        Word16.Word16+        Boolean.Boolean+    | VExplosion+        Boolean.Boolean+        (Maybe Int32.Int32)+        (Vector.Vector Int)+    | VFlaggedInt+        Boolean.Boolean+        Int32.Int32+    | VFloat+        Float32.Float32+    | VGameMode+        Word8.Word8+    | VInt+        Int32.Int32+    | VLoadout+        Word8.Word8+        Word32.Word32+        Word32.Word32+        Word32.Word32+        Word32.Word32+        Word32.Word32+        Word32.Word32+        Word32.Word32+        (Maybe Word32.Word32)+    | VLoadoutOnline+        Word32.Word32+        Word32.Word32+        Word32.Word32+        (Maybe Word8.Word8)+    | VLocation+        (Vector.Vector Int)+    | VMusicStinger+        Boolean.Boolean+        Word32.Word32+        Word8.Word8+    | VPickup+        Boolean.Boolean+        (Maybe Word32.Word32)+        Boolean.Boolean+    | VPrivateMatchSettings+        Text.Text+        Word32.Word32+        Word32.Word32+        Text.Text+        Text.Text+        Boolean.Boolean+    | VQWord+        Word64.Word64+    | VRelativeRotation+        (Vector.Vector Float)+    | VReservation+        Int+        Word8.Word8+        RemoteId.RemoteId+        (Maybe Word8.Word8)+        (Maybe Text.Text)+        Boolean.Boolean+        Boolean.Boolean+    | VRigidBodyState+        Boolean.Boolean+        (Vector.Vector Int)+        (Vector.Vector Float)+        (Maybe (Vector.Vector Int))+        (Maybe (Vector.Vector Int))+    | VString+        Text.Text+    | VTeamPaint+        Word8.Word8+        Word8.Word8+        Word8.Word8+        Word32.Word32+        Word32.Word32+    | VUniqueId+        Word8.Word8+        RemoteId.RemoteId+        (Maybe Word8.Word8)+    deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Value where
+ library/Octane/Type/Vector.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE StrictData #-}++module Octane.Type.Vector (Vector(..)) where++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified GHC.Generics as Generics+++data Vector a = Vector+    { x :: a+    , y :: a+    , z :: a+    } deriving (Eq, Generics.Generic, Show)++instance (DeepSeq.NFData a) => DeepSeq.NFData (Vector a) where++instance (Aeson.ToJSON a) => Aeson.ToJSON (Vector a) where+    toJSON vector = Aeson.toJSON+        [ x vector+        , y vector+        , z vector+        ]
+ library/Octane/Type/Word16.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Word16 (Word16(..), fromWord16, toWord16) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.Word as Word+import qualified GHC.Generics as Generics+import qualified Text.Printf as Printf+++-- | A 16-bit unsigned integer.+newtype Word16 = Word16+    { unpack :: Word.Word16+    } deriving (Eq, Generics.Generic, Num, Ord)++-- | Stored in little-endian byte order.+instance Binary.Binary Word16 where+    get = do+        value <- Binary.getWord16le+        pure (Word16 value)++    put word16 = do+        let value = unpack word16+        Binary.putWord16le value++instance DeepSeq.NFData Word16 where++-- | Shown as @0x0102@.+instance Show Word16 where+    show word16 = Printf.printf "0x%04x" (unpack word16)++instance Aeson.ToJSON Word16 where+    toJSON word16 = word16+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Word16' into any 'Integral' value.+--+-- >>> fromWord16 0x0001 :: Word.Word16+-- 1+--+-- >>> fromWord16 0xffff :: Data.Int.Int16+-- -1+fromWord16 :: (Integral a) => Word16 -> a+fromWord16 word16 = fromIntegral (unpack word16)+++-- | Converts any 'Integral' value into a 'Word16'.+--+-- >>> toWord16 (1 :: Word.Word16)+-- 0x0001+--+-- >>> toWord16 (-1 :: Data.Int.Int16)+-- 0xffff+toWord16 :: (Integral a) => a -> Word16+toWord16 value = Word16 (fromIntegral value)
+ library/Octane/Type/Word32.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Word32 (Word32(..), fromWord32, toWord32) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Word as Word+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+import qualified Text.Printf as Printf+++-- | A 32-bit unsigned integer.+newtype Word32 = Word32+    { unpack :: Word.Word32+    } deriving (Eq, Generics.Generic, Num, Ord)++-- | Stored in little-endian byte order.+instance Binary.Binary Word32 where+    get = do+        value <- Binary.getWord32le+        pure (Word32 value)++    put word32 = do+        let value = unpack word32+        Binary.putWord32le value++instance BinaryBit.BinaryBit Word32 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 4+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ word32 = word32+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Word32 where++-- | Shown as @0x01020304@.+instance Show Word32 where+    show word32 = Printf.printf "0x%08x" (unpack word32)++instance Aeson.ToJSON Word32 where+    toJSON word32 = word32+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Word32' into any 'Integral' value.+--+-- >>> fromWord32 0x00000001 :: Word.Word32+-- 1+--+-- >>> fromWord32 0xffffffff :: Data.Int.Int32+-- -1+fromWord32 :: (Integral a) => Word32 -> a+fromWord32 word32 = fromIntegral (unpack word32)+++-- | Converts any 'Integral' value into a 'Word32'.+--+-- >>> toWord32 (1 :: Word.Word32)+-- 0x00000001+--+-- >>> toWord32 (-1 :: Data.Int.Int32)+-- 0xffffffff+toWord32 :: (Integral a) => a -> Word32+toWord32 value = Word32 (fromIntegral value)
+ library/Octane/Type/Word64.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Word64 (Word64(..), fromWord64, toWord64) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Word as Word+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+import qualified Text.Printf as Printf+++-- | A 64-bit unsigned integer.+newtype Word64 = Word64+    { unpack :: Word.Word64+    } deriving (Eq, Generics.Generic, Num, Ord)++-- | Stored in little-endian byte order.+instance Binary.Binary Word64 where+    get = do+        value <- Binary.getWord64le+        pure (Word64 value)++    put word64 = do+        let value = unpack word64+        Binary.putWord64le value++instance BinaryBit.BinaryBit Word64 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 8+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ word64 = word64+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Word64 where++-- | Shown as @0x01020304@.+instance Show Word64 where+    show word64 = Printf.printf "0x%016x" (unpack word64)++instance Aeson.ToJSON Word64 where+    toJSON word64 = word64+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Word64' into any 'Integral' value.+--+-- >>> fromWord64 0x0000000000000001 :: Word.Word64+-- 1+--+-- >>> fromWord64 0xffffffffffffffff :: Data.Int.Int64+-- -1+fromWord64 :: (Integral a) => Word64 -> a+fromWord64 word64 = fromIntegral (unpack word64)+++-- | Converts any 'Integral' value into a 'Word64'.+--+-- >>> toWord64 (1 :: Word.Word64)+-- 0x0000000000000001+--+-- >>> toWord64 (-1 :: Data.Int.Int64)+-- 0xffffffffffffffff+toWord64 :: (Integral a) => a -> Word64+toWord64 value = Word64 (fromIntegral value)
+ library/Octane/Type/Word8.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Octane.Type.Word8 (Word8(..), fromWord8, toWord8) where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Get as Binary+import qualified Data.Binary.Put as Binary+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Word as Word+import qualified GHC.Generics as Generics+import qualified Octane.Utility.Endian as Endian+import qualified Text.Printf as Printf+++-- | A 8-bit unsigned integer.+newtype Word8 = Word8+    { unpack :: Word.Word8+    } deriving (Eq, Generics.Generic, Num, Ord)++instance Binary.Binary Word8 where+    get = do+        value <- Binary.getWord8+        pure (Word8 value)++    put word8 = do+        let value = unpack word8+        Binary.putWord8 value++instance BinaryBit.BinaryBit Word8 where+    getBits _ = do+        bytes <- BinaryBit.getByteString 1+        bytes+            & LazyBytes.fromStrict+            & Endian.reverseBitsInBytes+            & Binary.runGet Binary.get+            & pure++    putBits _ word8 = word8+        & Binary.put+        & Binary.runPut+        & Endian.reverseBitsInBytes+        & LazyBytes.toStrict+        & BinaryBit.putByteString++instance DeepSeq.NFData Word8 where++-- | Shown as @0x01@.+instance Show Word8 where+    show word8 = Printf.printf "0x%02x" (unpack word8)++instance Aeson.ToJSON Word8 where+    toJSON word8 = word8+        & unpack+        & Aeson.toJSON+++-- | Converts a 'Word8' into any 'Integral' value.+--+-- >>> fromWord8 0x01 :: Word.Word8+-- 1+--+-- >>> fromWord8 0xff :: Data.Int.Int8+-- -1+fromWord8 :: (Integral a) => Word8 -> a+fromWord8 word8 = fromIntegral (unpack word8)+++-- | Converts any 'Integral' value into a 'Word8'.+--+-- >>> toWord8 (1 :: Word.Word8)+-- 0x01+--+-- >>> toWord8 (-1 :: Data.Int.Int8)+-- 0xff+toWord8 :: (Integral a) => a -> Word8+toWord8 value = Word8 (fromIntegral value)
library/Octane/Utility.hs view
@@ -1,116 +1,11 @@-{-# LANGUAGE BinaryLiterals #-}--module Octane.Utility where--import qualified Data.Bits as Bits-import qualified Data.ByteString as StrictBytes-import qualified Data.ByteString.Lazy as LazyBytes-import qualified Data.Vector.Unboxed as Vector-import qualified Data.Word as Word----- | Computes the CRC32 of some bytes. Note that this is a non-standard CRC32.--- It probably only works for Rocket League.-crc32 :: LazyBytes.ByteString -> Word.Word32-crc32 bytes = Bits.xor 0xffffffff (LazyBytes.foldl-    (\ crc byte -> Bits.xor-        (crc32Table Vector.! fromIntegral-            (Bits.xor byte (fromIntegral (Bits.shiftR crc 24))))-        (Bits.shiftL crc 8))-    0x10340dfe-    bytes)----- | The lookup table of values for 'crc32'. Computed from the polynomial--- @0x04c11db7@.-crc32Table :: Vector.Vector Word.Word32-crc32Table = Vector.fromList-    [-        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-    ]----- | Reverses all the bits in each lazy byte.-reverseBitsInBytes :: LazyBytes.ByteString -> LazyBytes.ByteString-reverseBitsInBytes bytes = LazyBytes.map reverseBits bytes----- | Reverses all the bits in each strict byte.-reverseBitsInBytes' :: StrictBytes.ByteString -> StrictBytes.ByteString-reverseBitsInBytes' bytes = StrictBytes.map reverseBits bytes-+module Octane.Utility+    ( module Octane.Utility.ClassPropertyMap+    , module Octane.Utility.CRC+    , module Octane.Utility.Endian+    , module Octane.Utility.Optimizer+    ) where --- | Reverses the bits in a byte.-reverseBits :: Word.Word8 -> Word.Word8-reverseBits byte-    = Bits.shiftR (byte Bits..&. 0b10000000) 7-    + Bits.shiftR (byte Bits..&. 0b01000000) 5-    + Bits.shiftR (byte Bits..&. 0b00100000) 3-    + Bits.shiftR (byte Bits..&. 0b00010000) 1-    + Bits.shiftL (byte Bits..&. 0b00001000) 1-    + Bits.shiftL (byte Bits..&. 0b00000100) 3-    + Bits.shiftL (byte Bits..&. 0b00000010) 5-    + Bits.shiftL (byte Bits..&. 0b00000001) 7+import Octane.Utility.ClassPropertyMap+import Octane.Utility.CRC+import Octane.Utility.Endian+import Octane.Utility.Optimizer
+ library/Octane/Utility/CRC.hs view
@@ -0,0 +1,106 @@+module Octane.Utility.CRC (crc32) where++import qualified Data.Bits as Bits+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.Vector.Unboxed as Vector+import qualified Data.Word as Word+++-- | Computes the CRC32 of some bytes. Note that this is a non-standard CRC32.+-- It probably only works for Rocket League.+--+-- >>> crc32 LazyBytes.empty -- ""+-- 4023120385+--+-- >>> crc32 (LazyBytes.pack [49 .. 57]) -- "123456789"+-- 3690624627+crc32 :: LazyBytes.ByteString -> Word.Word32+crc32 bytes = do+    let update = crc32Update crc32Table+    let initial = Bits.complement crc32Initial+    let crc = LazyBytes.foldl update initial bytes+    Bits.complement crc+++crc32Update :: Vector.Vector Word.Word32 -> Word.Word32 -> Word.Word8 -> Word.Word32+crc32Update table crc byte = do+    let toWord8 = fromIntegral :: (Integral a) => a -> Word.Word8+    let toInt = fromIntegral :: (Integral a) => a -> Int+    let index = toInt (Bits.xor byte (toWord8 (Bits.shiftR crc 24)))+    let left = Vector.unsafeIndex table index+    let right = Bits.shiftL crc 8+    Bits.xor left right+++crc32Initial :: Word.Word32+crc32Initial = 0xefcbf201+++crc32Table :: Vector.Vector Word.Word32+crc32Table = Vector.fromList+    [+        0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,+        0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,+        0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,+        0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,+        0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,+        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,+        0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,+        0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,+        0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,+        0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,+        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,+        0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,+        0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,+        0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,+        0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,+        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,+        0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,+        0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,+        0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,+        0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,+        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,+        0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,+        0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,+        0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,+        0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,+        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,+        0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,+        0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,+        0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,+        0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,+        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,+        0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,+        0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,+        0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,+        0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,+        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,+        0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,+        0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,+        0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,+        0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,+        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,+        0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,+        0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,+        0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,+        0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,+        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,+        0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,+        0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,+        0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,+        0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,+        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,+        0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,+        0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,+        0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,+        0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,+        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,+        0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,+        0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,+        0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,+        0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,+        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,+        0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,+        0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,+        0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4+    ]
+ library/Octane/Utility/ClassPropertyMap.hs view
@@ -0,0 +1,174 @@+-- | This module is responsible for building the class property map, which maps+-- class IDs to a map of property IDs to property names. This map is the+-- cornerstone of the replay stream parser.+module Octane.Utility.ClassPropertyMap where++import Data.Function ((&))++import qualified Data.Char as Char+import qualified Data.IntMap.Strict as IntMap+import qualified Data.List as List+import qualified Data.Map.Strict as Map+import qualified Data.Maybe as Maybe+import qualified Data.Text as StrictText+import qualified Octane.Type.CacheItem as CacheItem+import qualified Octane.Type.CacheProperty as CacheProperty+import qualified Octane.Type.ClassItem as ClassItem+import qualified Octane.Type.List as List+import qualified Octane.Type.ReplayWithoutFrames as ReplayWithoutFrames+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Word32 as Word32+++-- | The class property map is a map from class IDs in the stream to a map from+-- property IDs in the stream to property names.+getClassPropertyMap :: ReplayWithoutFrames.ReplayWithoutFrames -> IntMap.IntMap (IntMap.IntMap StrictText.Text)+getClassPropertyMap replay = let+    basicClassPropertyMap = getBasicClassPropertyMap replay+    classMap = getClassMap replay+    in replay+        & getClassIds+        & map (\ classId -> let+            ownProperties = case IntMap.lookup classId basicClassPropertyMap of+                Nothing -> IntMap.empty+                Just x -> x+            parentProperties = case IntMap.lookup classId classMap of+                Nothing -> IntMap.empty+                Just parentClassIds -> parentClassIds+                    & map (\ parentClassId ->+                        case IntMap.lookup parentClassId basicClassPropertyMap of+                            Nothing -> IntMap.empty+                            Just x -> x)+                    & IntMap.unions+            properties = IntMap.union ownProperties parentProperties+            in (classId, properties))+        & IntMap.fromList++-- | The class cache is a list of 3-tuples where the first element is a class+-- ID, the second is its cache ID, and the third is its parent's cache ID.+getClassCache :: ReplayWithoutFrames.ReplayWithoutFrames -> [(Int, Int, Int)]+getClassCache replay = replay+    & ReplayWithoutFrames.cache+    & List.unpack+    & map (\ x ->+        ( x & CacheItem.classId & Word32.fromWord32+        , x & CacheItem.cacheId & Word32.fromWord32+        , x & CacheItem.parentCacheId & Word32.fromWord32+        ))++-- | The class IDs in a replay. Comes from the class cache.+getClassIds :: ReplayWithoutFrames.ReplayWithoutFrames -> [Int]+getClassIds replay = replay+    & getClassCache+    & map (\ (x, _, _) -> x)++-- | Gets the parent class ID for the given parent cache ID. This is necessary+-- because there is not always a class with the given cache ID in the cache.+-- When that happens, the parent cache ID is decremented and tried again.+getParentClassId :: Int -> [(Int, Int, Int)] -> Maybe Int+getParentClassId parentCacheId xs =+    case dropWhile (\ (_, cacheId, _) -> cacheId /= parentCacheId) xs of+        [] -> if parentCacheId <= 0+            then Nothing+            else getParentClassId (parentCacheId - 1) xs+        (parentClassId, _, _) : _ -> Just parentClassId++-- | The basic class map is a naive mapping from class ID to its parent class+-- ID. It's naive because it only maps the class ID to its immediate parent.+-- It does not chase the inheritance all the way down.+getBasicClassMap :: ReplayWithoutFrames.ReplayWithoutFrames -> IntMap.IntMap Int+getBasicClassMap replay = replay+    & getClassCache+    & reverse+    & List.tails+    & Maybe.mapMaybe (\ xs -> case xs of+        [] -> Nothing+        (classId, _, parentCacheId) : ys -> do+            parentClassId <- getParentClassId parentCacheId ys+            return (classId, parentClassId))+    & IntMap.fromList++-- | Given a naive mapping from class ID to its parent class ID, return all of+-- the parent IDs for a given class.+getParentClassIds :: Int -> IntMap.IntMap Int -> [Int]+getParentClassIds classId basicClassMap =+    case IntMap.lookup classId basicClassMap of+        Nothing -> []+        Just parentClassId -> parentClassId : getParentClassIds parentClassId basicClassMap++-- | The class map is a mapping from a class ID to all of its parent class IDs.+getClassMap :: ReplayWithoutFrames.ReplayWithoutFrames -> IntMap.IntMap [Int]+getClassMap replay = let+    basicClassMap = getBasicClassMap replay+    in replay+        & getClassIds+        & map (\ classId ->+            ( classId+            , getParentClassIds classId basicClassMap+            ))+        & IntMap.fromList++-- | The property map is a mapping from property IDs to property names.+getPropertyMap :: ReplayWithoutFrames.ReplayWithoutFrames -> IntMap.IntMap StrictText.Text+getPropertyMap replay = replay+    & ReplayWithoutFrames.objects+    & List.unpack+    & map Text.unpack+    & zip [0 ..]+    & IntMap.fromList++-- | The basic class property map is a naive mapping from class IDs to a+-- mapping from property IDs to property names. It's naive because it does+-- not include the properties from the class's parents.+getBasicClassPropertyMap :: ReplayWithoutFrames.ReplayWithoutFrames -> IntMap.IntMap (IntMap.IntMap StrictText.Text)+getBasicClassPropertyMap replay = let+    propertyMap = getPropertyMap replay+    in replay+        & ReplayWithoutFrames.cache+        & List.unpack+        & map (\ x -> let+            classId = x & CacheItem.classId & Word32.fromWord32+            properties = x+                & CacheItem.properties+                & List.unpack+                & Maybe.mapMaybe (\ y -> let+                    streamId = y & CacheProperty.streamId & Word32.fromWord32+                    propertyId = y & CacheProperty.objectId & Word32.fromWord32+                    in case IntMap.lookup propertyId propertyMap of+                        Nothing -> Nothing+                        Just name -> Just (streamId, name))+                & IntMap.fromList+            in (classId, properties))+        & IntMap.fromList++-- | The actor map is a mapping from class names to their IDs.+getActorMap :: ReplayWithoutFrames.ReplayWithoutFrames -> Map.Map StrictText.Text Int+getActorMap replay = replay+    & ReplayWithoutFrames.classes+    & List.unpack+    & map (\ x -> let+        className = x & ClassItem.name & Text.unpack+        classId = x & ClassItem.streamId & Word32.fromWord32+        in (className, classId))+    & Map.fromList++-- | Gets the class ID and name for a given property ID.+getClass+    :: IntMap.IntMap StrictText.Text -- ^ Property ID to property name+    -> Map.Map StrictText.Text StrictText.Text -- ^ Property name to class name+    -> Map.Map StrictText.Text Int -- ^ Class name to class ID+    -> Int -- ^ property ID+    -> Maybe (Int, StrictText.Text) -- ^ Maybe class ID and class name+getClass propertyIdsToNames propertyNamesToClassNames classNamesToIds propertyId =+    case IntMap.lookup propertyId propertyIdsToNames of+        Nothing -> Nothing+        Just rawPropertyName -> let+            -- There are a large number of properties that end in numbers that+            -- should all be treated the same. Instead of explicitly mapping+            -- each of them, we can remove the numbers and treat them the same.+            propertyName = rawPropertyName & StrictText.dropWhileEnd Char.isDigit+            in case Map.lookup propertyName propertyNamesToClassNames of+                Nothing -> Nothing+                Just className -> case Map.lookup className classNamesToIds of+                    Nothing -> Nothing+                    Just classId -> Just (classId, className)
+ library/Octane/Utility/Endian.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE BinaryLiterals #-}++module Octane.Utility.Endian where++import qualified Data.Bits as Bits+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.ByteString as StrictBytes+import qualified Data.Word as Word+++-- | Reverses all the bits in each lazy byte.+reverseBitsInBytes :: LazyBytes.ByteString -> LazyBytes.ByteString+reverseBitsInBytes bytes = LazyBytes.map reverseBits bytes+++-- | Reverses all the bits in each strict byte.+reverseBitsInBytes' :: StrictBytes.ByteString -> StrictBytes.ByteString+reverseBitsInBytes' bytes = StrictBytes.map reverseBits bytes+++-- | Reverses the bits in a byte.+reverseBits :: Word.Word8 -> Word.Word8+reverseBits byte+    = Bits.shiftR (byte Bits..&. 0b10000000) 7+    + Bits.shiftR (byte Bits..&. 0b01000000) 5+    + Bits.shiftR (byte Bits..&. 0b00100000) 3+    + Bits.shiftR (byte Bits..&. 0b00010000) 1+    + Bits.shiftL (byte Bits..&. 0b00001000) 1+    + Bits.shiftL (byte Bits..&. 0b00000100) 3+    + Bits.shiftL (byte Bits..&. 0b00000010) 5+    + Bits.shiftL (byte Bits..&. 0b00000001) 7
+ library/Octane/Utility/Optimizer.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE OverloadedStrings #-}++module Octane.Utility.Optimizer (optimizeFrames) where++import Data.Function ((&))++import qualified Data.Foldable as Foldable+import qualified Data.IntMap.Strict as IntMap+import qualified Data.Map.Strict as Map+import qualified Data.Text as StrictText+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.Replication as Replication+import qualified Octane.Type.State as State+import qualified Octane.Type.Value as Value+++optimizeFrames :: [Frame.Frame] -> [Frame.Frame]+optimizeFrames frames = frames+    & Foldable.foldl'+        (\ (state, fs) f -> let+            newState = updateState f state+            minimalFrame = getDelta state f+            in (newState, minimalFrame : fs))+        (initialState, [])+    & snd+    & reverse+++-- { actor id => (alive?, { property name => property value } ) }+type State = IntMap.IntMap (Bool, Map.Map StrictText.Text Value.Value)+++initialState :: State+initialState = IntMap.empty+++updateState :: Frame.Frame -> State -> State+updateState frame state1 = let+    spawned = frame+        & Frame.replications+        & filter (\ replication -> replication+            & Replication.state+            & (== State.SOpening))+        & map Replication.actorId+        & map fromIntegral+    state2 = spawned+        & foldr+            (IntMap.alter (\ maybeValue -> Just (case maybeValue of+                Nothing -> (True, Map.empty)+                Just (_, properties) -> (True, properties))))+            state1++    destroyed = frame+        & Frame.replications+        & filter (\ replication -> replication+            & Replication.state+            & (== State.SClosing))+        & map Replication.actorId+        & map fromIntegral+    state3 = destroyed+        & foldr+            (IntMap.alter (\ maybeValue -> Just (case maybeValue of+                Nothing -> (False, Map.empty)+                Just (_, properties) -> (False, properties))))+            state2++    updated = frame+        & Frame.replications+        & filter (\ replication -> replication+            & Replication.state+            & (== State.SExisting))+    state4 = updated+        & foldr+            (\ replication -> IntMap.alter+                (\ maybeValue -> Just (case maybeValue of+                    Nothing ->+                        (True, Replication.properties replication)+                    Just (alive, properties) ->+                        ( alive+                        , Map.union+                            (Replication.properties replication)+                            properties+                        )))+                (replication & Replication.actorId & fromIntegral))+            state3++    in state4+++getDelta :: State -> Frame.Frame -> Frame.Frame+getDelta state frame = let+    newReplications = frame+        & Frame.replications+        -- Remove replications that aren't actually new.+        & reject (\ replication -> let+            isOpening = Replication.state replication == State.SOpening+            actorId = Replication.actorId replication+            currentState = IntMap.lookup (fromIntegral actorId) state+            isAlive = fmap fst currentState+            wasAlreadyAlive = isAlive == Just True+            in isOpening && wasAlreadyAlive)+        -- Remove properties that haven't changed.+        & map (\ replication ->+            if Replication.state replication == State.SExisting+            then let+                actorId = Replication.actorId replication+                currentState = IntMap.findWithDefault+                    (True, Map.empty) (fromIntegral actorId) state+                currentProperties = snd currentState+                newProperties = Replication.properties replication+                changes = newProperties+                    & Map.filterWithKey (\ name newValue -> let+                        oldValue = Map.lookup name currentProperties+                        in Just newValue /= oldValue)+                in replication { Replication.properties = changes }+            else replication)+    in frame { Frame.replications = newReplications }+++reject :: (a -> Bool) -> [a] -> [a]+reject p xs = filter (\ x -> not (p x)) xs
+ library/Octane/Utility/Parser.hs view
@@ -0,0 +1,721 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE StrictData #-}++module Octane.Utility.Parser where++import Data.Function ((&))++import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Get as Bits+import qualified Data.Binary.Get as Binary+import qualified Data.Bits as Bits+import qualified Data.ByteString as StrictBytes+import qualified Data.ByteString.Lazy as LazyBytes+import qualified Data.IntMap.Strict as IntMap+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.Text as StrictText+import qualified GHC.Generics as Generics+import qualified Octane.Data as Data+import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.Dictionary as Dictionary+import qualified Octane.Type.Float32 as Float32+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.Initialization as Initialization+import qualified Octane.Type.Int32 as Int32+import qualified Octane.Type.Int8 as Int8+import qualified Octane.Type.KeyFrame as KeyFrame+import qualified Octane.Type.List as List+import qualified Octane.Type.Property as Property+import qualified Octane.Type.RemoteId as RemoteId+import qualified Octane.Type.ReplayWithoutFrames as ReplayWithoutFrames+import qualified Octane.Type.Replication as Replication+import qualified Octane.Type.State as State+import qualified Octane.Type.Stream as Stream+import qualified Octane.Type.Text as Text+import qualified Octane.Type.Value as Value+import qualified Octane.Type.Vector as Vector+import qualified Octane.Type.Word16 as Word16+import qualified Octane.Type.Word32 as Word32+import qualified Octane.Type.Word64 as Word64+import qualified Octane.Type.Word8 as Word8+import qualified Octane.Utility.ClassPropertyMap as CPM+import qualified Octane.Utility.Endian as Endian+import qualified Text.Printf as Printf+++parseFrames :: ReplayWithoutFrames.ReplayWithoutFrames -> [Frame.Frame]+parseFrames replay = let+    numFrames = replay+        & ReplayWithoutFrames.properties+        & Dictionary.unpack+        & Map.lookup ("NumFrames" & StrictText.pack & Text.Text)+        & (\ property -> case property of+            Just (Property.IntProperty _ x) -> x & Int32.unpack & fromIntegral+            _ -> 0)+    get = replay & extractContext & getFrames 0 numFrames & Bits.runBitGet+    stream = replay & ReplayWithoutFrames.stream & Stream.unpack+    (_context, frames) = Binary.runGet get stream+    in frames++getFrames :: Word -> Int -> Context -> Bits.BitGet (Context, [Frame.Frame])+getFrames number numFrames context = do+    if fromIntegral number >= numFrames+    then return (context, [])+    else do+        isEmpty <- Bits.isEmpty+        if isEmpty+        then return (context, [])+        else do+            maybeFrame <- getMaybeFrame context number+            case maybeFrame of+                Nothing -> return (context, [])+                Just (newContext, frame) -> do+                    (newerContext, frames) <- getFrames (number + 1) numFrames newContext+                    return (newerContext, (frame : frames))++getMaybeFrame :: Context -> Word -> Bits.BitGet (Maybe (Context, Frame.Frame))+getMaybeFrame context number = do+    time <- getFloat32+    delta <- getFloat32+    if time == 0 && delta == 0+    then return Nothing+    else if time < 0.001 || delta < 0.001+    then fail ("parsing previous frame probably failed. time: " ++ show time ++ ", delta: " ++ show delta)+    else do+        (newContext, frame) <- getFrame context number time delta+        return (Just (newContext, frame))++getFrame :: Context -> Word -> Float32.Float32 -> Float32.Float32 -> Bits.BitGet (Context, Frame.Frame)+getFrame context number time delta = do+    (newContext, replications) <- getReplications context+    let frame =+            Frame.Frame+            { Frame.number = number+            , Frame.isKeyFrame = context & contextKeyFrames & Set.member number+            , Frame.time = time+            , Frame.delta = delta+            , Frame.replications = replications+            }+    (newContext, frame) & DeepSeq.force & return++getReplications :: Context -> Bits.BitGet (Context, [Replication.Replication])+getReplications context = do+    maybeReplication <- getMaybeReplication context+    case maybeReplication of+        Nothing -> return (context, [])+        Just (newContext, replication) -> do+            (newerContext, replications) <- getReplications newContext+            return (newerContext, replication : replications)++getMaybeReplication :: Context -> Bits.BitGet (Maybe (Context, Replication.Replication))+getMaybeReplication context = do+    hasReplication <- getBool+    if Boolean.unpack hasReplication+        then do+            (newContext,replication) <- getReplication context+            return (Just (newContext, replication))+        else return Nothing++getReplication :: Context -> Bits.BitGet (Context, Replication.Replication)+getReplication context = do+    actorId <- getActorId+    isOpen <- getBool+    let go =+            if Boolean.unpack isOpen+                then getOpenReplication+                else getClosedReplication+    go context actorId++getOpenReplication :: Context+                   -> Int+                   -> Bits.BitGet (Context, Replication.Replication)+getOpenReplication context actorId = do+    isNew <- getBool+    let go =+            if Boolean.unpack isNew+                then getNewReplication+                else getExistingReplication+    go context actorId++getNewReplication :: Context+                  -> Int+                  -> Bits.BitGet (Context, Replication.Replication)+getNewReplication context actorId = do+    unknownFlag <- getBool+    if Boolean.unpack unknownFlag+        then fail "the unknown flag in a new replication is true! what does it mean?"+        else pure ()+    objectId <- getInt32+    objectName <- case context & contextObjectMap & IntMap.lookup (Int32.fromInt32 objectId) of+        Nothing -> fail ("could not find object name for id " ++ show objectId)+        Just x -> pure x+    (classId, className) <- case CPM.getClass (contextObjectMap context) Data.objectToClass (contextClassMap context) (Int32.fromInt32 objectId) of+        Nothing -> fail ("could not find class for object id " ++ show objectId)+        Just x -> pure x+    classInit <- getInitialization className+    let thing = Thing+            { thingFlag = unknownFlag+            , thingObjectId = objectId+            , thingObjectName = objectName+            , thingClassId = classId+            , thingClassName = className+            , thingInitialization = classInit+            }+    let things = contextThings context+    let newThings = IntMap.insert actorId thing things+    let newContext = context { contextThings = newThings }+    return+        ( newContext+        , Replication.Replication+          { Replication.actorId = fromIntegral actorId+          , Replication.objectName = objectName+          , Replication.className = className+          , Replication.state = State.SOpening+          , Replication.initialization = Just classInit+          , Replication.properties = Map.empty+          })++getExistingReplication :: Context+                       -> Int+                       -> Bits.BitGet (Context, Replication.Replication)+getExistingReplication context actorId = do+    thing <- case context & contextThings & IntMap.lookup actorId of+        Nothing -> fail ("could not find thing for actor id " ++ show actorId)+        Just x -> pure x+    props <- getProps context thing+    return (context, Replication.Replication+        { Replication.actorId = fromIntegral actorId+        , Replication.objectName = thingObjectName thing+        , Replication.className = thingClassName thing+        , Replication.state = State.SExisting+        , Replication.initialization = Nothing+        , Replication.properties = props+        })++getClosedReplication :: Context+                     -> Int+                     -> Bits.BitGet (Context, Replication.Replication)+getClosedReplication context actorId = do+    thing <- case context & contextThings & IntMap.lookup actorId of+        Nothing -> fail ("could not find thing for actor id " ++ show actorId)+        Just x -> pure x+    let newThings = context & contextThings & IntMap.delete actorId+    let newContext = context { contextThings = newThings }+    return+        ( newContext+        , Replication.Replication+          { Replication.actorId = fromIntegral actorId+          , Replication.objectName = thingObjectName thing+          , Replication.className = thingClassName thing+          , Replication.state = State.SClosing+          , Replication.initialization = Nothing+          , Replication.properties = Map.empty+          })++getProps :: Context -> Thing -> Bits.BitGet (Map.Map StrictText.Text Value.Value)+getProps context thing = do+    maybeProp <- getMaybeProp context thing+    case maybeProp of+        Nothing -> return Map.empty+        Just (k, v) -> do+            let m = Map.singleton k v+            props <- getProps context thing+            return (Map.union m props)++getMaybeProp :: Context -> Thing -> Bits.BitGet (Maybe (StrictText.Text, Value.Value))+getMaybeProp context thing = do+    hasProp <- getBool+    if Boolean.unpack hasProp+    then do+        prop <- getProp context thing+        return (Just prop)+    else return Nothing++getProp :: Context -> Thing -> Bits.BitGet (StrictText.Text, Value.Value)+getProp context thing = do+    let classId = thing & thingClassId+    props <- case context & contextClassPropertyMap & IntMap.lookup classId of+        Nothing -> fail ("could not find property map for class id " ++ show classId)+        Just x -> pure x+    let maxId = props & IntMap.keys & (0 :) & maximum+    pid <- getInt maxId+    name <- case props & IntMap.lookup pid of+        Nothing -> fail ("could not find property name for property id " ++ show pid)+        Just x -> pure x+    value <- getPropValue name+    return (name, value)++--++getPropValue :: StrictText.Text -> Bits.BitGet Value.Value+getPropValue name = case Map.lookup name propertyNameToGet of+    Nothing -> fail ("don't know how to read property " ++ show name)+    Just get -> get++propertyNameToGet :: Map.Map StrictText.Text (Bits.BitGet Value.Value)+propertyNameToGet =+    [ (Data.booleanProperties, getBooleanProperty)+    , (Data.byteProperties, getByteProperty)+    , (Data.camSettingsProperties, getCamSettingsProperty)+    , (Data.demolishProperties, getDemolishProperty)+    , (Data.enumProperties, getEnumProperty)+    , (Data.explosionProperties, getExplosionProperty)+    , (Data.flaggedIntProperties, getFlaggedIntProperty)+    , (Data.floatProperties, getFloatProperty)+    , (Data.gameModeProperties, getGameModeProperty)+    , (Data.intProperties, getIntProperty)+    , (Data.loadoutOnlineProperties, getLoadoutOnlineProperty)+    , (Data.loadoutProperties, getLoadoutProperty)+    , (Data.locationProperties, getLocationProperty)+    , (Data.musicStingerProperties, getMusicStingerProperty)+    , (Data.pickupProperties, getPickupProperty)+    , (Data.privateMatchSettingsProperties, getPrivateMatchSettingsProperty)+    , (Data.qWordProperties, getQWordProperty)+    , (Data.relativeRotationProperties, getRelativeRotationProperty)+    , (Data.reservationProperties, getReservationProperty)+    , (Data.rigidBodyStateProperties, getRigidBodyStateProperty)+    , (Data.stringProperties, getStringProperty)+    , (Data.teamPaintProperties, getTeamPaintProperty)+    , (Data.uniqueIdProperties, getUniqueIdProperty)+    , (Set.fromList [StrictText.pack "TAGame.PRI_TA:PartyLeader"], getPartyLeaderProperty)+    ]+        & concatMap (\ (ks, v) -> ks & Set.toList & map (\ k -> (k, v)))+        & Map.fromList++getBooleanProperty :: Bits.BitGet Value.Value+getBooleanProperty = do+    bool <- getBool+    return (Value.VBoolean bool)++getByteProperty :: Bits.BitGet Value.Value+getByteProperty = do+    word <- getWord8+    return (Value.VByte word)++getCamSettingsProperty :: Bits.BitGet Value.Value+getCamSettingsProperty = do+    fov <- getFloat32+    height <- getFloat32+    angle <- getFloat32+    distance <- getFloat32+    stiffness <- getFloat32+    swivelSpeed <- getFloat32+    return (Value.VCamSettings fov height angle distance stiffness swivelSpeed)++getDemolishProperty :: Bits.BitGet Value.Value+getDemolishProperty = do+    atkFlag <- getBool+    atk <- getWord32+    vicFlag <- getBool+    vic <- getWord32+    vec1 <- getVector+    vec2 <- getVector+    return (Value.VDemolish atkFlag atk vicFlag vic vec1 vec2)++getEnumProperty :: Bits.BitGet Value.Value+getEnumProperty = do+    x <- Bits.getWord16be 10+    y <- if x == 1023+        then getBool+        else fail ("unexpected enum value " ++ show x)+    return (Value.VEnum (Word16.toWord16 x) y)++getExplosionProperty :: Bits.BitGet Value.Value+getExplosionProperty = do+    noGoal <- getBool+    a <- if Boolean.unpack noGoal+        then return Nothing+        else fmap Just getInt32+    b <- getVector+    return (Value.VExplosion noGoal a b)++getFlaggedIntProperty :: Bits.BitGet Value.Value+getFlaggedIntProperty = do+    flag <- getBool+    int <- getInt32+    return (Value.VFlaggedInt flag int)++getFloatProperty :: Bits.BitGet Value.Value+getFloatProperty = do+    float <- getFloat32+    return (Value.VFloat float)++getGameModeProperty :: Bits.BitGet Value.Value+getGameModeProperty = do+    x <- Bits.getWord8 2+    return (Value.VGameMode (Word8.toWord8 x))++getIntProperty :: Bits.BitGet Value.Value+getIntProperty = do+    int <- getInt32+    return (Value.VInt int)++getLoadoutOnlineProperty :: Bits.BitGet Value.Value+getLoadoutOnlineProperty = do+    version <- getWord32+    x <- getWord32+    y <- getWord32+    z <- if version >= 12+        then do+            value <- getWord8+            return (Just value)+        else return Nothing+    return (Value.VLoadoutOnline version x y z)++getLoadoutProperty :: Bits.BitGet Value.Value+getLoadoutProperty = do+    version <- getWord8+    body <- getWord32+    decal <- getWord32+    wheels <- getWord32+    rocketTrail <- getWord32+    antenna <- getWord32+    topper <- getWord32+    g <- getWord32+    h <- if version > 10+        then do+            value <- getWord32+            return (Just value)+        else return Nothing+    return (Value.VLoadout version body decal wheels rocketTrail antenna topper g h)++getLocationProperty :: Bits.BitGet Value.Value+getLocationProperty = do+    vector <- getVector+    return (Value.VLocation vector)++getMusicStingerProperty :: Bits.BitGet Value.Value+getMusicStingerProperty = do+    flag <- getBool+    cue <- getWord32+    trigger <- getWord8+    return (Value.VMusicStinger flag cue trigger)++getPickupProperty :: Bits.BitGet Value.Value+getPickupProperty = do+    instigator <- getBool+    instigatorId <- if Boolean.unpack instigator+        then fmap Just getWord32+        else return Nothing+    pickedUp <- getBool+    return (Value.VPickup instigator instigatorId pickedUp)++getPrivateMatchSettingsProperty :: Bits.BitGet Value.Value+getPrivateMatchSettingsProperty = do+    mutators <- getText+    joinableBy <- getWord32+    maxPlayers <- getWord32+    gameName <- getText+    password <- getText+    flag <- getBool+    return (Value.VPrivateMatchSettings mutators joinableBy maxPlayers gameName password flag)++getQWordProperty :: Bits.BitGet Value.Value+getQWordProperty = do+    qword <- getWord64+    return (Value.VQWord qword)++getRelativeRotationProperty :: Bits.BitGet Value.Value+getRelativeRotationProperty = do+    vector <- getFloatVector+    return (Value.VRelativeRotation vector)++getReservationProperty :: Bits.BitGet Value.Value+getReservationProperty = do+    -- I think this is the connection order. The first player to connect+    -- gets number 0, and it goes up from there. The maximum is 7, which+    -- would be a full 4x4 game.+    number <- getInt7+    (systemId, remoteId, localId) <- getUniqueId+    playerName <- if systemId == 0 then return Nothing else do+        string <- getText+        return (Just string)+    -- No idea what these two flags are. Might be for bots?+    a <- getBool+    b <- getBool+    return (Value.VReservation number systemId remoteId localId playerName a b)++getRigidBodyStateProperty :: Bits.BitGet Value.Value+getRigidBodyStateProperty = do+    flag <- getBool+    position <- getVector+    rotation <- getFloatVector+    x <- if Boolean.unpack flag+        then return Nothing+        else fmap Just getVector+    y <- if Boolean.unpack flag+        then return Nothing+        else fmap Just getVector+    return (Value.VRigidBodyState flag position rotation x y)++getStringProperty :: Bits.BitGet Value.Value+getStringProperty = do+    string <- getText+    return (Value.VString string)++getTeamPaintProperty :: Bits.BitGet Value.Value+getTeamPaintProperty = do+    team <- getWord8+    primaryColor <- getWord8+    accentColor <- getWord8+    primaryFinish <- getWord32+    accentFinish <- getWord32+    return (Value.VTeamPaint team primaryColor accentColor primaryFinish accentFinish)++getUniqueIdProperty :: Bits.BitGet Value.Value+getUniqueIdProperty = do+    (systemId, remoteId, localId) <- getUniqueId+    return (Value.VUniqueId systemId remoteId localId)++-- | Even though this is just a unique ID property, it must be handled+-- specially because it sometimes doesn't have the remote or local IDs.+getPartyLeaderProperty :: Bits.BitGet Value.Value+getPartyLeaderProperty = do+    systemId <- getSystemId+    (remoteId, localId) <- if systemId == 0+        then return (RemoteId.SplitscreenId Nothing, Nothing)+        else do+            remoteId <- getRemoteId systemId+            localId <- getLocalId+            return (remoteId, localId)+    return (Value.VUniqueId systemId remoteId localId)++--++getFloat32 :: Bits.BitGet Float32.Float32+getFloat32 = BinaryBit.getBits unimportant++getText :: Bits.BitGet Text.Text+getText = BinaryBit.getBits unimportant++getUniqueId :: Bits.BitGet (Word8.Word8, RemoteId.RemoteId, Maybe Word8.Word8)+getUniqueId = do+    systemId <- getSystemId+    remoteId <- getRemoteId systemId+    localId <- getLocalId+    return (systemId, remoteId, localId)++getSystemId :: Bits.BitGet Word8.Word8+getSystemId = getWord8++getRemoteId :: Word8.Word8 -> Bits.BitGet RemoteId.RemoteId+getRemoteId systemId = case systemId of+    0 -> do+        remoteId <- Bits.getByteString 3+        if StrictBytes.all (\ byte -> byte == 0) remoteId+            then 0 & Just & RemoteId.SplitscreenId & return+            else fail ("unexpected splitscreen id " ++ show remoteId)+    1 -> do+        bytes <- Bits.getByteString 8+        let remoteId = Binary.runGet+                Binary.getWord64le+                (bytes & LazyBytes.fromStrict & Endian.reverseBitsInBytes)+        remoteId & Word64.toWord64 & RemoteId.SteamId & return+    2 -> do+        bytes <- Bits.getByteString 32+        let remoteId = bytes+                & LazyBytes.fromStrict+                & Endian.reverseBitsInBytes+                & LazyBytes.unpack+                & concatMap (\ b -> Printf.printf "%02x" b)+                & StrictText.pack+                & Text.Text+        remoteId & RemoteId.PlayStationId & return+    4 -> do+        bytes <- Bits.getByteString 8+        let remoteId = Binary.runGet+                Binary.getWord64le+                (bytes & LazyBytes.fromStrict & Endian.reverseBitsInBytes)+        remoteId & Word64.toWord64 & RemoteId.XboxId & return+    _ -> fail ("unknown system id " ++ show systemId)++getLocalId :: Bits.BitGet (Maybe Word8.Word8)+getLocalId = fmap Just getWord8+++data Thing = Thing+    { thingFlag :: Boolean.Boolean+    , thingObjectId :: Int32.Int32+    , thingObjectName :: StrictText.Text+    , thingClassId :: Int+    , thingClassName :: StrictText.Text+    , thingInitialization :: Initialization.Initialization+    } deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Thing++-- { class stream id => { property stream id => name } }+type ClassPropertyMap = IntMap.IntMap (IntMap.IntMap StrictText.Text)++-- { stream id => object name }+type ObjectMap = IntMap.IntMap StrictText.Text++-- { class name => class id }+type ClassMap = Map.Map StrictText.Text Int++data Context = Context+    { contextObjectMap :: ObjectMap+    , contextClassPropertyMap :: ClassPropertyMap+    , contextThings :: (IntMap.IntMap Thing)+    , contextClassMap :: ClassMap+    , contextKeyFrames :: (Set.Set Word)+    } deriving (Eq, Generics.Generic, Show)++instance DeepSeq.NFData Context++extractContext :: ReplayWithoutFrames.ReplayWithoutFrames -> Context+extractContext replay =+    Context+    { contextObjectMap = CPM.getPropertyMap replay+    , contextClassPropertyMap = CPM.getClassPropertyMap replay+    , contextThings = IntMap.empty+    , contextClassMap = CPM.getActorMap replay+    , contextKeyFrames = replay+        & ReplayWithoutFrames.keyFrames+        & List.unpack+        & map KeyFrame.frame+        & map Word32.fromWord32+        & Set.fromList+    }++getVector :: Bits.BitGet (Vector.Vector Int)+getVector = do+    numBits <- getNumVectorBits+    let bias = Bits.shiftL 1 (numBits + 1)+    let maxBits = numBits + 2+    let maxValue = 2 ^ maxBits+    dx <- getInt maxValue+    dy <- getInt maxValue+    dz <- getInt maxValue+    return+        Vector.Vector+        { Vector.x = dx - bias+        , Vector.y = dy - bias+        , Vector.z = dz - bias+        }++getVectorBytewise+    :: Bits.BitGet (Vector.Vector Int8.Int8)+getVectorBytewise = do+    hasX <- getBool+    x <- if Boolean.unpack hasX then getInt8 else return 0+    hasY <- getBool+    y <- if Boolean.unpack hasY then getInt8 else return 0+    hasZ <- getBool+    z <- if Boolean.unpack hasZ then getInt8 else return 0+    return+        Vector.Vector+        { Vector.x = x+        , Vector.y = y+        , Vector.z = z+        }++getFloatVector :: Bits.BitGet (Vector.Vector Float)+getFloatVector = do+    let maxValue = 1+    let numBits = 16+    x <- getFloat maxValue numBits+    y <- getFloat maxValue numBits+    z <- getFloat maxValue numBits+    return Vector.Vector { Vector.x = x, Vector.y = y, Vector.z = z }++getFloat :: Int -> Int -> Bits.BitGet Float+getFloat maxValue numBits = do+    let maxBitValue = (Bits.shiftL 1 (numBits - 1)) - 1+    let bias = Bits.shiftL 1 (numBits - 1)+    let serIntMax = Bits.shiftL 1 numBits+    delta <- getInt serIntMax+    let unscaledValue = delta - bias+    if maxValue > maxBitValue+    then do+        let invScale = fromIntegral maxValue / fromIntegral maxBitValue+        return (fromIntegral unscaledValue * invScale)+    else do+        let scale = fromIntegral maxBitValue / fromIntegral maxValue+        let invScale = 1.0 / scale+        return (fromIntegral unscaledValue * invScale)++getInitialization :: StrictText.Text -> Bits.BitGet Initialization.Initialization+getInitialization className = do+    location <-+        if Set.member className Data.locationClasses+            then do+                vector <- getVector+                return (Just vector)+            else return Nothing+    rotation <-+        if Set.member className Data.rotationClasses+            then do+                vector <- getVectorBytewise+                return (Just vector)+            else return Nothing+    return+        Initialization.Initialization+        { Initialization.location = location+        , Initialization.rotation = rotation+        }++bitSize+    :: (Integral a)+    => a -> a+bitSize x = x & fromIntegral & logBase (2 :: Double) & ceiling++-- Reads an integer bitwise. The bits of the integer are backwards, so the+-- least significant bit is first. The argument is the maximum value this+-- integer can have. Bits will be read until the next bit would be greater than+-- the maximum value, or the number of bits necessary to reach the maximum+-- value has been reached, whichever comes first.+--+-- For example, if the maximum value is 4 and "11" has been read already,+-- nothing more will be read because another "1" would put the value over the+-- maximum.+getInt+    :: Int -> Bits.BitGet Int+getInt maxValue = do+    let maxBits = bitSize maxValue+        go i value = do+            let x = Bits.shiftL 1 i+            if i < maxBits && value + x <= maxValue+                then do+                    bit <- getBool+                    let newValue =+                            if Boolean.unpack bit+                                then value + x+                                else value+                    go (i + 1) newValue+                else return value+    go 0 0++getInt32 :: Bits.BitGet Int32.Int32+getInt32 = BinaryBit.getBits unimportant++getInt8 :: Bits.BitGet Int8.Int8+getInt8 = BinaryBit.getBits unimportant++getWord64 :: Bits.BitGet Word64.Word64+getWord64 = BinaryBit.getBits unimportant++getWord32 :: Bits.BitGet Word32.Word32+getWord32 = BinaryBit.getBits unimportant++getWord8 :: Bits.BitGet Word8.Word8+getWord8 = BinaryBit.getBits unimportant++getActorId :: Bits.BitGet Int+getActorId = getInt 1024++getNumVectorBits :: Bits.BitGet Int+getNumVectorBits = getInt 19++getInt7 :: Bits.BitGet Int+getInt7 = getInt 7++getBool :: Bits.BitGet Boolean.Boolean+getBool = BinaryBit.getBits unimportant++-- | The 'getBits' function from "Data.Binary.Bits" requires a size parameter.+-- None of Octane's instances use it.+unimportant :: Int+unimportant = 0
− library/Octane/Version.hs
@@ -1,8 +0,0 @@-module Octane.Version (version) where--import qualified Data.Version as Version-import qualified Paths_octane as This---- | The current version number.-version :: Version.Version-version = This.version
octane.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           octane-version:        0.10.0+version:        0.11.0 synopsis:       Parse Rocket League replays. description:    Octane parses Rocket League replays. category:       Game@@ -31,7 +31,6 @@   ghc-options: -Wall   build-depends:       aeson ==0.11.*-    , autoexporter ==0.2.*     , base ==4.9.*     , bimap ==0.3.*     , binary ==0.8.*@@ -40,48 +39,69 @@     , containers ==0.5.*     , data-binary-ieee754 ==0.4.*     , deepseq ==1.4.*-    , scientific ==0.3.*+    , http-client ==0.4.*+    , http-client-tls ==0.2.*     , text ==1.2.*+    , unordered-containers ==0.2.*     , vector ==0.11.*   exposed-modules:       Octane-      Octane.Analyzer       Octane.Data-      Octane.FullReplay-      Octane.Json+      Octane.Data.Antennas+      Octane.Data.Bodies+      Octane.Data.Bots+      Octane.Data.Classes+      Octane.Data.Decals+      Octane.Data.Finishes+      Octane.Data.GameModes+      Octane.Data.Logos+      Octane.Data.Properties+      Octane.Data.RocketTrails+      Octane.Data.Toppers+      Octane.Data.Wheels       Octane.Main-      Octane.Parser-      Octane.Parser.ClassPropertyMap-      Octane.Parser.Garage       Octane.Type-      Octane.Type.Actor+      Octane.Type.Boolean       Octane.Type.CacheItem       Octane.Type.CacheProperty+      Octane.Type.ClassItem+      Octane.Type.Dictionary+      Octane.Type.Float32+      Octane.Type.Frame+      Octane.Type.Initialization+      Octane.Type.Int32+      Octane.Type.Int8       Octane.Type.KeyFrame+      Octane.Type.List       Octane.Type.Mark       Octane.Type.Message-      Octane.Type.Primitive-      Octane.Type.Primitive.Boolean-      Octane.Type.Primitive.Dictionary-      Octane.Type.Primitive.Float32-      Octane.Type.Primitive.Int32-      Octane.Type.Primitive.Int64-      Octane.Type.Primitive.Int8-      Octane.Type.Primitive.List-      Octane.Type.Primitive.Stream-      Octane.Type.Primitive.Text-      Octane.Type.Primitive.Word32-      Octane.Type.Primitive.Word8+      Octane.Type.OptimizedReplay       Octane.Type.Property+      Octane.Type.RawReplay+      Octane.Type.RemoteId       Octane.Type.Replay+      Octane.Type.ReplayWithFrames+      Octane.Type.ReplayWithoutFrames+      Octane.Type.Replication+      Octane.Type.State+      Octane.Type.Stream+      Octane.Type.Text+      Octane.Type.Value+      Octane.Type.Vector+      Octane.Type.Word16+      Octane.Type.Word32+      Octane.Type.Word64+      Octane.Type.Word8       Octane.Utility-      Octane.Version-  other-modules:-      Paths_octane+      Octane.Utility.ClassPropertyMap+      Octane.Utility.CRC+      Octane.Utility.Endian+      Octane.Utility.Optimizer+      Octane.Utility.Parser   default-language: Haskell2010  executable octane-  main-is: Executable.hs+  main-is: Main.hs   hs-source-dirs:       executable   ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N@@ -90,76 +110,47 @@     , octane   default-language: Haskell2010 +test-suite octane-doctest+  type: exitcode-stdio-1.0+  main-is: DocTest.hs+  hs-source-dirs:+      test-suite+  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N+  build-depends:+      base+    , octane+    , doctest ==0.11.*+  other-modules:+      Main+      OctaneSpec+  default-language: Haskell2010+ test-suite octane-test-suite   type: exitcode-stdio-1.0-  main-is: TestSuite.hs+  main-is: Main.hs   hs-source-dirs:       test-suite   ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N   build-depends:       base-    , binary-    , bytestring-    , containers     , octane     , tasty ==0.11.*     , tasty-hspec ==1.1.*   other-modules:-      Octane.MainSpec-      Octane.Type.ActorSpec-      Octane.Type.CacheItemSpec-      Octane.Type.CachePropertySpec-      Octane.Type.KeyFrameSpec-      Octane.Type.MarkSpec-      Octane.Type.MessageSpec-      Octane.Type.Primitive.BooleanSpec-      Octane.Type.Primitive.DictionarySpec-      Octane.Type.Primitive.Float32Spec-      Octane.Type.Primitive.Int32Spec-      Octane.Type.Primitive.Int64Spec-      Octane.Type.Primitive.ListSpec-      Octane.Type.Primitive.StreamSpec-      Octane.Type.Primitive.TextSpec-      Octane.Type.PrimitiveSpec-      Octane.Type.PropertySpec-      Octane.Type.ReplaySpec-      Octane.TypeSpec-      Octane.VersionSpec+      DocTest       OctaneSpec   default-language: Haskell2010  benchmark octane-benchmark   type: exitcode-stdio-1.0-  main-is: Benchmark.hs+  main-is: Main.hs   hs-source-dirs:       benchmark   ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N   build-depends:       base-    , binary-    , bytestring-    , containers     , criterion ==1.1.*     , octane   other-modules:-      Octane.MainBench-      Octane.Type.ActorBench-      Octane.Type.CacheItemBench-      Octane.Type.CachePropertyBench-      Octane.Type.KeyFrameBench-      Octane.Type.MarkBench-      Octane.Type.MessageBench-      Octane.Type.Primitive.BooleanBench-      Octane.Type.Primitive.DictionaryBench-      Octane.Type.Primitive.Float32Bench-      Octane.Type.Primitive.Int32Bench-      Octane.Type.Primitive.Int64Bench-      Octane.Type.Primitive.ListBench-      Octane.Type.Primitive.StreamBench-      Octane.Type.Primitive.TextBench-      Octane.Type.PrimitiveBench-      Octane.Type.PropertyBench-      Octane.Type.ReplayBench-      Octane.TypeBench       OctaneBench   default-language: Haskell2010
package.yaml view
@@ -2,16 +2,13 @@   octane-benchmark:     dependencies:     - base-    - binary-    - bytestring-    - containers     - criterion ==1.1.*     - octane     ghc-options:     - -rtsopts     - -threaded     - -with-rtsopts=-N-    main: Benchmark.hs+    main: Main.hs     source-dirs: benchmark category: Game description: Octane parses Rocket League replays.@@ -24,7 +21,7 @@     - -rtsopts     - -threaded     - -with-rtsopts=-N-    main: Executable.hs+    main: Main.hs     source-dirs: executable extra-source-files: - CHANGELOG.markdown@@ -36,7 +33,6 @@ library:   dependencies:   - aeson ==0.11.*-  - autoexporter ==0.2.*   - base ==4.9.*   - bimap ==0.3.*   - binary ==0.8.*@@ -45,10 +41,11 @@   - containers ==0.5.*   - data-binary-ieee754 ==0.4.*   - deepseq ==1.4.*-  - scientific ==0.3.*+  - http-client ==0.4.*+  - http-client-tls ==0.2.*   - text ==1.2.*+  - unordered-containers ==0.2.*   - vector ==0.11.*-  other-modules: Paths_octane   source-dirs: library license: MIT license-file: LICENSE.markdown@@ -56,12 +53,20 @@ name: octane synopsis: Parse Rocket League replays. tests:+  octane-doctest:+    dependencies:+    - base+    - octane+    - doctest ==0.11.*+    ghc-options:+    - -rtsopts+    - -threaded+    - -with-rtsopts=-N+    main: DocTest.hs+    source-dirs: test-suite   octane-test-suite:     dependencies:     - base-    - binary-    - bytestring-    - containers     - octane     - tasty ==0.11.*     - tasty-hspec ==1.1.*@@ -69,6 +74,6 @@     - -rtsopts     - -threaded     - -with-rtsopts=-N-    main: TestSuite.hs+    main: Main.hs     source-dirs: test-suite-version: '0.10.0'+version: '0.11.0'
+ test-suite/DocTest.hs view
@@ -0,0 +1,7 @@+module Main (main) where++import qualified Test.DocTest as DocTest+++main :: IO ()+main = DocTest.doctest ["library"]
+ test-suite/Main.hs view
@@ -0,0 +1,14 @@+module Main (main) where++import qualified OctaneSpec+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.Hspec as Hspec++main :: IO ()+main = do+    test <- Hspec.testSpec "octane" spec+    Tasty.defaultMain test+++spec :: Hspec.Spec+spec = Hspec.parallel OctaneSpec.spec
− test-suite/Octane/MainSpec.hs
@@ -1,7 +0,0 @@-module Octane.MainSpec (spec) where--import Test.Tasty.Hspec--spec :: Spec-spec = describe "Main" $ do-    return ()
− test-suite/Octane/Type/ActorSpec.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.ActorSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Actor" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeActor "\-                \\1\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 9, Actor-                (Text "")-                (Int32 0)))-        shouldBe-            (decodeActor "\-                \\2\0\0\0a\0\-                \\2\0\0\0")-            (Right ("", 10, Actor-                (Text "a")-                (Int32 2)))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Actor-                (Text "")-                (Int32 0)))-            "\-                \\1\0\0\0\0\-                \\0\0\0\0"-        shouldBe-            (Binary.encode (Actor-                (Text "a")-                (Int32 2)))-            "\-                \\2\0\0\0a\0\-                \\2\0\0\0"--decodeActor :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Actor)-decodeActor = Binary.decodeOrFail
− test-suite/Octane/Type/CacheItemSpec.hs
@@ -1,64 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.CacheItemSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "CacheItem" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeCacheItem "\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 16, CacheItem-                (Int32 0)-                (Int32 0)-                (Int32 0)-                (List [])))-        shouldBe-            (decodeCacheItem "\-                \\1\0\0\0\-                \\2\0\0\0\-                \\3\0\0\0\-                \\1\0\0\0\-                \\4\0\0\0\5\0\0\0")-            (Right ("", 24, CacheItem-                (Int32 1)-                (Int32 2)-                (Int32 3)-                (List [CacheProperty (Int32 4) (Int32 5)])))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (CacheItem-                (Int32 0)-                (Int32 0)-                (Int32 0)-                (List [])))-            "\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0"-        shouldBe-            (Binary.encode (CacheItem-                (Int32 1)-                (Int32 2)-                (Int32 3)-                (List [CacheProperty (Int32 4) (Int32 5)])))-            "\-                \\1\0\0\0\-                \\2\0\0\0\-                \\3\0\0\0\-                \\1\0\0\0\-                \\4\0\0\0\5\0\0\0"--decodeCacheItem :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, CacheItem)-decodeCacheItem = Binary.decodeOrFail
− test-suite/Octane/Type/CachePropertySpec.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.CachePropertySpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "CacheProperty" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeCacheProperty "\-                \\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 8, CacheProperty-                (Int32 0)-                (Int32 0)))-        shouldBe-            (decodeCacheProperty "\-                \\1\0\0\0\-                \\2\0\0\0")-            (Right ("", 8, CacheProperty-                (Int32 1)-                (Int32 2)))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (CacheProperty-                (Int32 0)-                (Int32 0)))-            "\-                \\0\0\0\0\-                \\0\0\0\0"-        shouldBe-            (Binary.encode (CacheProperty-                (Int32 1)-                (Int32 2)))-            "\-                \\1\0\0\0\-                \\2\0\0\0"--decodeCacheProperty :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, CacheProperty)-decodeCacheProperty = Binary.decodeOrFail
− test-suite/Octane/Type/KeyFrameSpec.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.KeyFrameSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "KeyFrame" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeKeyFrame "\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 12, KeyFrame-                (Float32 0.0)-                (Int32 0)-                (Int32 0)))-        shouldBe-            (decodeKeyFrame "\-                \\0\0\128\63\-                \\2\0\0\0\-                \\3\0\0\0")-            (Right ("", 12, KeyFrame-                (Float32 1.0)-                (Int32 2)-                (Int32 3)))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (KeyFrame-                (Float32 0.0)-                (Int32 0)-                (Int32 0)))-            "\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0"-        shouldBe-            (Binary.encode (KeyFrame-                (Float32 1.0)-                (Int32 2)-                (Int32 3)))-            "\-                \\0\0\128\63\-                \\2\0\0\0\-                \\3\0\0\0"--decodeKeyFrame :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, KeyFrame)-decodeKeyFrame = Binary.decodeOrFail
− test-suite/Octane/Type/MarkSpec.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.MarkSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Mark" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeMark "\-                \\1\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 9, Mark-                (Text "")-                (Int32 0)))-        shouldBe-            (decodeMark "\-                \\2\0\0\0a\0\-                \\1\0\0\0")-            (Right ("", 10, Mark-                (Text "a")-                (Int32 1)))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Mark-                (Text "")-                (Int32 0)))-            "\-                \\1\0\0\0\0\-                \\0\0\0\0"-        shouldBe-            (Binary.encode (Mark-                (Text "a")-                (Int32 1)))-            "\-                \\2\0\0\0a\0\-                \\1\0\0\0"--decodeMark :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Mark)-decodeMark = Binary.decodeOrFail
− test-suite/Octane/Type/MessageSpec.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.MessageSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Message" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeMessage "\-                \\0\0\0\0\-                \\1\0\0\0\0\-                \\1\0\0\0\0")-            (Right ("", 14, Message-                (Int32 0)-                (Text "")-                (Text "")))-        shouldBe-            (decodeMessage "\-                \\1\0\0\0\-                \\2\0\0\0a\0\-                \\2\0\0\0b\0")-            (Right ("", 16, Message-                (Int32 1)-                (Text "a")-                (Text "b")))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Message (Int32 0) (Text "") (Text "")))-            "\-                \\0\0\0\0\-                \\1\0\0\0\0\-                \\1\0\0\0\0"-        shouldBe-            (Binary.encode (Message (Int32 1) (Text "a") (Text "b")))-            "\-                \\1\0\0\0\-                \\2\0\0\0a\0\-                \\2\0\0\0b\0"--decodeMessage :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Message)-decodeMessage = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/BooleanSpec.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.BooleanSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Boolean" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeBoolean "\0")-            (Right ("", 1, Boolean False))-        shouldBe-            (decodeBoolean "\1")-            (Right ("", 1, Boolean True))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Boolean False))-            "\0"-        shouldBe-            (Binary.encode (Boolean True))-            "\1"-    it "does not raise a runtime error when decoding garbage" $ do-        shouldBe-            (decodeBoolean "garbage")-            (Left ("arbage", 1, "Unexpected Boolean value 103"))--decodeBoolean :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Boolean)-decodeBoolean = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/DictionarySpec.hs
@@ -1,37 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.DictionarySpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Map as Map--spec :: Spec-spec = describe "Dictionary" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeDictionary "\5\0\0\0None\0")-            (Right ("", 9, Dictionary Map.empty :: Dictionary Boolean))-        shouldBe-            (decodeDictionary "\6\0\0\0Hello\0\6\0\0\0World\0\5\0\0\0None\0")-            (Right ("", 29, Dictionary (Map.singleton (Text "Hello") (Text "World"))))-        shouldBe-            (decodeDictionary "\7\0\0\0falsey\0\0\7\0\0\0truthy\0\1\5\0\0\0None\0")-            (Right ("", 33, Dictionary (Map.fromList [(Text "truthy", Boolean True), (Text "falsey", Boolean False)])))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Dictionary Map.empty :: Dictionary Boolean))-            "\5\0\0\0None\0"-        shouldBe-            (Binary.encode (Dictionary (Map.singleton (Text "Hello") (Text "World"))))-            "\6\0\0\0Hello\0\6\0\0\0World\0\5\0\0\0None\0"-        shouldBe-            (Binary.encode (Dictionary (Map.fromList [(Text "truthy", Boolean True), (Text "falsey", Boolean False)])))-            "\7\0\0\0falsey\0\0\7\0\0\0truthy\0\1\5\0\0\0None\0"--decodeDictionary :: (Binary.Binary a) => BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Dictionary a)-decodeDictionary = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/Float32Spec.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Float32Spec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Float32" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeFloat32 "\0\0\0\0")-            (Right ("", 4, Float32 0.0))-        shouldBe-            (decodeFloat32 "\0\0\128\63")-            (Right ("", 4, Float32 1.0))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Float32 0.0))-            "\0\0\0\0"-        shouldBe-            (Binary.encode (Float32 1.0))-            "\0\0\128\63"--decodeFloat32 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Float32)-decodeFloat32 = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/Int32Spec.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Int32Spec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Int32" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeInt32 "\0\0\0\0")-            (Right ("", 4, Int32 0))-        shouldBe-            (decodeInt32 "\1\0\0\0")-            (Right ("", 4, Int32 1))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Int32 0))-            "\0\0\0\0"-        shouldBe-            (Binary.encode (Int32 1))-            "\1\0\0\0"--decodeInt32 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int32)-decodeInt32 = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/Int64Spec.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.Int64Spec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Int64" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeInt64 "\0\0\0\0\0\0\0\0")-            (Right ("", 8, Int64 0))-        shouldBe-            (decodeInt64 "\1\0\0\0\0\0\0\0")-            (Right ("", 8, Int64 1))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Int64 0))-            "\0\0\0\0\0\0\0\0"-        shouldBe-            (Binary.encode (Int64 1))-            "\1\0\0\0\0\0\0\0"--decodeInt64 :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int64)-decodeInt64 = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/ListSpec.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.ListSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "List" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeList "\0\0\0\0")-            (Right ("", 4, List [] :: List Boolean))-        shouldBe-            (decodeList "\1\0\0\0\0\0\0\0")-            (Right ("", 8, List [Int32 0]))-        shouldBe-            (decodeList "\2\0\0\0\0\1")-            (Right ("", 6, List [Boolean False, Boolean True]))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (List [] :: List Boolean))-            "\0\0\0\0"-        shouldBe-            (Binary.encode (List [Int32 0]))-            "\1\0\0\0\0\0\0\0"-        shouldBe-            (Binary.encode (List [Boolean False, Boolean True]))-            "\2\0\0\0\0\1"--decodeList :: (Binary.Binary a) => BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, List a)-decodeList = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/StreamSpec.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.StreamSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Stream" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeStream "\0\0\0\0")-            (Right ("", 4, Stream ""))-    it "reverses the bits within each byte when decoding" $ do-        shouldBe-            (decodeStream "\2\0\0\0\128\64")-            (Right ("", 6, Stream "\1\2"))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Stream ""))-            "\0\0\0\0"-    it "reverses the bits within each byte when encoding" $ do-        shouldBe-            (Binary.encode (Stream "\1\2"))-            "\2\0\0\0\128\64"--decodeStream :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Stream)-decodeStream = Binary.decodeOrFail
− test-suite/Octane/Type/Primitive/TextSpec.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.Primitive.TextSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL--spec :: Spec-spec = describe "Text" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeText "\1\0\0\0\0")-            (Right ("", 5, Text ""))-        shouldBe-            (decodeText "\6\0\0\0ascii\0")-            (Right ("", 10, Text "ascii"))-        shouldBe-            (decodeText "\254\255\255\255\24\35\0\0")-            (Right ("", 8, Text "\8984"))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Text ""))-            "\1\0\0\0\0"-        shouldBe-            (Binary.encode (Text "ascii"))-            "\6\0\0\0ascii\0"-        shouldBe-            (Binary.encode (Text "\8984"))-            "\254\255\255\255\24\35\0\0"-    it "decodes strings of length 0" $ do-        shouldBe-            (decodeText "\0\0\0\0")-            (Right ("", 4, ""))--decodeText :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Text)-decodeText = Binary.decodeOrFail
− test-suite/Octane/Type/PrimitiveSpec.hs
@@ -1,23 +0,0 @@-module Octane.Type.PrimitiveSpec (spec) where--import Test.Tasty.Hspec--import qualified Octane.Type.Primitive.BooleanSpec-import qualified Octane.Type.Primitive.DictionarySpec-import qualified Octane.Type.Primitive.Float32Spec-import qualified Octane.Type.Primitive.ListSpec-import qualified Octane.Type.Primitive.TextSpec-import qualified Octane.Type.Primitive.StreamSpec-import qualified Octane.Type.Primitive.Int32Spec-import qualified Octane.Type.Primitive.Int64Spec--spec :: Spec-spec = describe "Primitive" $ do-    Octane.Type.Primitive.BooleanSpec.spec-    Octane.Type.Primitive.DictionarySpec.spec-    Octane.Type.Primitive.Float32Spec.spec-    Octane.Type.Primitive.ListSpec.spec-    Octane.Type.Primitive.TextSpec.spec-    Octane.Type.Primitive.StreamSpec.spec-    Octane.Type.Primitive.Int32Spec.spec-    Octane.Type.Primitive.Int64Spec.spec
− test-suite/Octane/Type/PropertySpec.hs
@@ -1,316 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.PropertySpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Map as Map--spec :: Spec-spec = describe "Property" $ do-    describe "Array" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0ArrayProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\0\0\0\0")-                (Right ("", 30, ArrayProperty-                    (Int64 0)-                    (List [])))-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0ArrayProperty\0\-                    \\1\0\0\0\0\0\0\0\1\0\0\0\2\0\0\0a\0\13\0\0\0BoolProperty\0\2\0\0\0\0\0\0\0\1\5\0\0\0None\0")-                (Right ("", 71, ArrayProperty-                    (Int64 1)-                    (List [Dictionary (Map.singleton (Text "a") (BoolProperty (Int64 2) (Boolean True)))])))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (ArrayProperty-                    (Int64 0)-                    (List [])))-                "\-                    \\14\0\0\0ArrayProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\0\0\0\0"-            shouldBe-                (Binary.encode (ArrayProperty-                    (Int64 1)-                    (List [Dictionary (Map.singleton (Text "a") (BoolProperty (Int64 2) (Boolean True)))])))-                "\-                    \\14\0\0\0ArrayProperty\0\-                    \\1\0\0\0\0\0\0\0\1\0\0\0\2\0\0\0a\0\13\0\0\0BoolProperty\0\2\0\0\0\0\0\0\0\1\5\0\0\0None\0"-    describe "Bool" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0BoolProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\0")-                (Right ("", 26, BoolProperty-                    (Int64 0)-                    (Boolean False)))-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0BoolProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\1")-                (Right ("", 26, BoolProperty-                    (Int64 1)-                    (Boolean True)))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (BoolProperty-                    (Int64 0)-                    (Boolean False)))-                "\-                    \\13\0\0\0BoolProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\0"-            shouldBe-                (Binary.encode (BoolProperty-                    (Int64 1)-                    (Boolean True)))-                "\-                    \\13\0\0\0BoolProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\1"-    describe "Byte" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0ByteProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0\1\0\0\0\0")-                (Right ("", 35, ByteProperty-                    (Int64 0)-                    (Text "", Text "")))-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0ByteProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0\2\0\0\0b\0")-                (Right ("", 37, ByteProperty-                    (Int64 1)-                    (Text "a", Text "b")))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (ByteProperty-                    (Int64 0)-                    (Text "", Text "")))-                "\-                    \\13\0\0\0ByteProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0\1\0\0\0\0"-            shouldBe-                (Binary.encode (ByteProperty-                    (Int64 1)-                    (Text "a", Text "b")))-                "\-                    \\13\0\0\0ByteProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0\2\0\0\0b\0"-    describe "Float" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0FloatProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\0\0")-                (Right ("", 30, FloatProperty-                    (Int64 4)-                    (Float32 0.0)))-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0FloatProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\128\63")-                (Right ("", 30, FloatProperty-                    (Int64 4)-                    (Float32 1.0)))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (FloatProperty-                    (Int64 4)-                    (Float32 0.0)))-                "\-                    \\14\0\0\0FloatProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\0\0"-            shouldBe-                (Binary.encode (FloatProperty-                    (Int64 4)-                    (Float32 1.0)))-                "\-                    \\14\0\0\0FloatProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\128\63"-        it "does not raise a runtime error when decoding garbage" $ do-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0FloatProperty\0\-                    \\0\0\0\0\0\0\0\0")-                (Left ("", 26, "unknown FloatProperty size 0"))-    describe "Int" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\12\0\0\0IntProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\0\0")-                (Right ("", 28, IntProperty-                    (Int64 4)-                    (Int32 0)))-            shouldBe-                (decodeProperty "\-                    \\12\0\0\0IntProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\1\0\0\0")-                (Right ("", 28, IntProperty-                    (Int64 4)-                    (Int32 1)))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (IntProperty-                    (Int64 4)-                    (Int32 0)))-                "\-                    \\12\0\0\0IntProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\0\0\0\0"-            shouldBe-                (Binary.encode (IntProperty-                    (Int64 4)-                    (Int32 1)))-                "\-                    \\12\0\0\0IntProperty\0\-                    \\4\0\0\0\0\0\0\0\-                    \\1\0\0\0"-        it "does not raise a runtime error when decoding garbage" $ do-            shouldBe-                (decodeProperty "\-                    \\12\0\0\0IntProperty\0\-                    \\0\0\0\0\0\0\0\0")-                (Left ("", 24, "unknown IntProperty size 0"))-    describe "Name" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0NameProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0")-                (Right ("", 30, NameProperty-                    (Int64 0)-                    (Text "")))-            shouldBe-                (decodeProperty "\-                    \\13\0\0\0NameProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0")-                (Right ("", 31, NameProperty-                    (Int64 1)-                    (Text "a")))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (NameProperty-                    (Int64 0)-                    (Text "")))-                "\-                    \\13\0\0\0NameProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0"-            shouldBe-                (Binary.encode (NameProperty-                    (Int64 1)-                    (Text "a")))-                "\-                    \\13\0\0\0NameProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0"-    describe "QWord" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0QWordProperty\0\-                    \\8\0\0\0\0\0\0\0\-                    \\0\0\0\0\0\0\0\0")-                (Right ("", 34, QWordProperty-                    (Int64 8)-                    (Int64 0)))-            shouldBe-                (decodeProperty "\-                    \\14\0\0\0QWordProperty\0\-                    \\8\0\0\0\0\0\0\0\-                    \\2\0\0\0\0\0\0\0")-                (Right ("", 34, QWordProperty-                    (Int64 8)-                    (Int64 2)))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (QWordProperty-                    (Int64 8)-                    (Int64 0)))-                "\-                    \\14\0\0\0QWordProperty\0\-                    \\8\0\0\0\0\0\0\0\-                    \\0\0\0\0\0\0\0\0"-            shouldBe-                (Binary.encode (QWordProperty-                    (Int64 8)-                    (Int64 2)))-                "\-                    \\14\0\0\0QWordProperty\0\-                    \\8\0\0\0\0\0\0\0\-                    \\2\0\0\0\0\0\0\0"-        it "does not raise a runtime error when decoding garbage" $ do-            shouldBe-                (decodeProperty "\14\0\0\0QWordProperty\0\0\0\0\0\0\0\0\0")-                (Left ("", 26, "unknown QWordProperty size 0"))-    describe "Str" $ do-        it "can be decoded" $ do-            shouldBe-                (decodeProperty "\-                    \\12\0\0\0StrProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0")-                (Right ("", 29, StrProperty-                    (Int64 0)-                    (Text "")))-            shouldBe-                (decodeProperty "\-                    \\12\0\0\0StrProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0")-                (Right ("", 30, StrProperty-                    (Int64 1)-                    (Text "a")))-        it "can be encoded" $ do-            shouldBe-                (Binary.encode (StrProperty-                    (Int64 0)-                    (Text "")))-                "\-                    \\12\0\0\0StrProperty\0\-                    \\0\0\0\0\0\0\0\0\-                    \\1\0\0\0\0"-            shouldBe-                (Binary.encode (StrProperty-                    (Int64 1)-                    (Text "a")))-                "\-                    \\12\0\0\0StrProperty\0\-                    \\1\0\0\0\0\0\0\0\-                    \\2\0\0\0a\0"-    it "does not raise a runtime error when decoding garbage" $ do-        shouldBe-            (decodeProperty "\-                \\14\0\0\0OtherProperty\0")-            (Left ("", 18, "unknown property type \"OtherProperty\""))--decodeProperty :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Property)-decodeProperty = Binary.decodeOrFail
− test-suite/Octane/Type/ReplaySpec.hs
@@ -1,97 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Octane.Type.ReplaySpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Binary as Binary-import qualified Data.Binary.Get as Binary-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Map as Map--spec :: Spec-spec = describe "Replay" $ do-    it "can be decoded" $ do-        shouldBe-            (decodeReplay "\-                \\x16\x00\x00\x00\-                \\xc5\x30\xa7\xe2\-                \\0\0\0\0\-                \\0\0\0\0\-                \\1\0\0\0\0\-                \\5\0\0\0None\0\-                \\x28\x00\x00\x00\-                \\xf5\x9a\x89\x13\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0")-            (Right ("", 78, Replay-                (Word32 22)-                (Word32 3802607813)-                (Int32 0)-                (Int32 0)-                (Text "")-                (Dictionary Map.empty)-                (Word32 40)-                (Word32 327785205)-                (List [])-                (List [])-                (Stream "")-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])))-    it "can be encoded" $ do-        shouldBe-            (Binary.encode (Replay-                (Word32 0)-                (Word32 0)-                (Int32 0)-                (Int32 0)-                (Text "")-                (Dictionary Map.empty)-                (Word32 0)-                (Word32 0)-                (List [])-                (List [])-                (Stream "")-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])-                (List [])))-            "\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\1\0\0\0\0\-                \\5\0\0\0None\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0\-                \\0\0\0\0"--decodeReplay :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Replay)-decodeReplay = Binary.decodeOrFail
− test-suite/Octane/TypeSpec.hs
@@ -1,25 +0,0 @@-module Octane.TypeSpec (spec) where--import Test.Tasty.Hspec--import qualified Octane.Type.ActorSpec-import qualified Octane.Type.CacheItemSpec-import qualified Octane.Type.CachePropertySpec-import qualified Octane.Type.KeyFrameSpec-import qualified Octane.Type.MarkSpec-import qualified Octane.Type.MessageSpec-import qualified Octane.Type.PrimitiveSpec-import qualified Octane.Type.PropertySpec-import qualified Octane.Type.ReplaySpec--spec :: Spec-spec = describe "Type" $ do-    Octane.Type.ActorSpec.spec-    Octane.Type.CacheItemSpec.spec-    Octane.Type.CachePropertySpec.spec-    Octane.Type.KeyFrameSpec.spec-    Octane.Type.MarkSpec.spec-    Octane.Type.MessageSpec.spec-    Octane.Type.PrimitiveSpec.spec-    Octane.Type.PropertySpec.spec-    Octane.Type.ReplaySpec.spec
− test-suite/Octane/VersionSpec.hs
@@ -1,11 +0,0 @@-module Octane.VersionSpec (spec) where--import Octane-import Test.Tasty.Hspec--import qualified Data.Version as Version--spec :: Spec-spec = describe "Version" $ do-    it "has some parts" $ do-        shouldBe (null (Version.versionBranch version)) False
test-suite/OctaneSpec.hs view
@@ -1,13 +1,8 @@ module OctaneSpec (spec) where -import Test.Tasty.Hspec+import qualified Test.Tasty.Hspec as Hspec -import qualified Octane.MainSpec-import qualified Octane.TypeSpec-import qualified Octane.VersionSpec -spec :: Spec-spec = describe "Octane" $ do-    Octane.MainSpec.spec-    Octane.TypeSpec.spec-    Octane.VersionSpec.spec+spec :: Hspec.Spec+spec = Hspec.describe "Octane" (do+    pure ())
− test-suite/TestSuite.hs
@@ -1,12 +0,0 @@-import Test.Tasty.Hspec--import qualified OctaneSpec-import qualified Test.Tasty--main :: IO ()-main = do-    test <- testSpec "octane" spec-    Test.Tasty.defaultMain test--spec :: Spec-spec = parallel OctaneSpec.spec