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 +1/−0
- benchmark/Benchmark.hs +0/−5
- benchmark/Main.hs +14/−0
- benchmark/Octane/MainBench.hs +0/−8
- benchmark/Octane/Type/ActorBench.hs +0/−23
- benchmark/Octane/Type/CacheItemBench.hs +0/−27
- benchmark/Octane/Type/CachePropertyBench.hs +0/−23
- benchmark/Octane/Type/KeyFrameBench.hs +0/−25
- benchmark/Octane/Type/MarkBench.hs +0/−23
- benchmark/Octane/Type/MessageBench.hs +0/−25
- benchmark/Octane/Type/Primitive/BooleanBench.hs +0/−19
- benchmark/Octane/Type/Primitive/DictionaryBench.hs +0/−23
- benchmark/Octane/Type/Primitive/Float32Bench.hs +0/−19
- benchmark/Octane/Type/Primitive/Int32Bench.hs +0/−19
- benchmark/Octane/Type/Primitive/Int64Bench.hs +0/−19
- benchmark/Octane/Type/Primitive/ListBench.hs +0/−22
- benchmark/Octane/Type/Primitive/StreamBench.hs +0/−19
- benchmark/Octane/Type/Primitive/TextBench.hs +0/−19
- benchmark/Octane/Type/PrimitiveBench.hs +0/−24
- benchmark/Octane/Type/PropertyBench.hs +0/−90
- benchmark/Octane/Type/ReplayBench.hs +0/−56
- benchmark/Octane/TypeBench.hs +0/−26
- benchmark/OctaneBench.hs +5/−8
- executable/Executable.hs +0/−3
- executable/Main.hs +3/−0
- library/Octane.hs +2/−6
- library/Octane/Analyzer.hs +0/−233
- library/Octane/Data.hs +27/−1294
- library/Octane/Data/Antennas.hs +435/−0
- library/Octane/Data/Bodies.hs +33/−0
- library/Octane/Data/Bots.hs +66/−0
- library/Octane/Data/Classes.hs +174/−0
- library/Octane/Data/Decals.hs +220/−0
- library/Octane/Data/Finishes.hs +28/−0
- library/Octane/Data/GameModes.hs +15/−0
- library/Octane/Data/Logos.hs +33/−0
- library/Octane/Data/Properties.hs +294/−0
- library/Octane/Data/RocketTrails.hs +63/−0
- library/Octane/Data/Toppers.hs +78/−0
- library/Octane/Data/Wheels.hs +51/−0
- library/Octane/FullReplay.hs +0/−483
- library/Octane/Json.hs +0/−9
- library/Octane/Main.hs +70/−9
- library/Octane/Parser.hs +0/−823
- library/Octane/Parser/ClassPropertyMap.hs +0/−167
- library/Octane/Parser/Garage.hs +0/−43
- library/Octane/Type.hs +63/−1
- library/Octane/Type/Actor.hs +0/−30
- library/Octane/Type/Boolean.hs +52/−0
- library/Octane/Type/CacheItem.hs +17/−18
- library/Octane/Type/CacheProperty.hs +12/−13
- library/Octane/Type/ClassItem.hs +29/−0
- library/Octane/Type/Dictionary.hs +75/−0
- library/Octane/Type/Float32.hs +62/−0
- library/Octane/Type/Frame.hs +289/−0
- library/Octane/Type/Initialization.hs +18/−0
- library/Octane/Type/Int32.hs +79/−0
- library/Octane/Type/Int8.hs +78/−0
- library/Octane/Type/KeyFrame.hs +15/−19
- library/Octane/Type/List.hs +37/−0
- library/Octane/Type/Mark.hs +10/−12
- library/Octane/Type/Message.hs +14/−16
- library/Octane/Type/OptimizedReplay.hs +94/−0
- library/Octane/Type/Primitive.hs +0/−1
- library/Octane/Type/Primitive/Boolean.hs +0/−58
- library/Octane/Type/Primitive/Dictionary.hs +0/−59
- library/Octane/Type/Primitive/Float32.hs +0/−65
- library/Octane/Type/Primitive/Int32.hs +0/−71
- library/Octane/Type/Primitive/Int64.hs +0/−45
- library/Octane/Type/Primitive/Int8.hs +0/−61
- library/Octane/Type/Primitive/List.hs +0/−63
- library/Octane/Type/Primitive/Stream.hs +0/−41
- library/Octane/Type/Primitive/Text.hs +0/−111
- library/Octane/Type/Primitive/Word32.hs +0/−49
- library/Octane/Type/Primitive/Word8.hs +0/−60
- library/Octane/Type/Property.hs +55/−50
- library/Octane/Type/RawReplay.hs +94/−0
- library/Octane/Type/RemoteId.hs +23/−0
- library/Octane/Type/Replay.hs +120/−139
- library/Octane/Type/ReplayWithFrames.hs +95/−0
- library/Octane/Type/ReplayWithoutFrames.hs +106/−0
- library/Octane/Type/Replication.hs +25/−0
- library/Octane/Type/State.hs +15/−0
- library/Octane/Type/Stream.hs +34/−0
- library/Octane/Type/Text.hs +114/−0
- library/Octane/Type/Value.hs +120/−0
- library/Octane/Type/Vector.hs +25/−0
- library/Octane/Type/Word16.hs +65/−0
- library/Octane/Type/Word32.hs +86/−0
- library/Octane/Type/Word64.hs +86/−0
- library/Octane/Type/Word8.hs +85/−0
- library/Octane/Utility.hs +10/−115
- library/Octane/Utility/CRC.hs +106/−0
- library/Octane/Utility/ClassPropertyMap.hs +174/−0
- library/Octane/Utility/Endian.hs +31/−0
- library/Octane/Utility/Optimizer.hs +121/−0
- library/Octane/Utility/Parser.hs +721/−0
- library/Octane/Version.hs +0/−8
- octane.cabal +64/−73
- package.yaml +18/−13
- test-suite/DocTest.hs +7/−0
- test-suite/Main.hs +14/−0
- test-suite/Octane/MainSpec.hs +0/−7
- test-suite/Octane/Type/ActorSpec.hs +0/−46
- test-suite/Octane/Type/CacheItemSpec.hs +0/−64
- test-suite/Octane/Type/CachePropertySpec.hs +0/−46
- test-suite/Octane/Type/KeyFrameSpec.hs +0/−54
- test-suite/Octane/Type/MarkSpec.hs +0/−46
- test-suite/Octane/Type/MessageSpec.hs +0/−48
- test-suite/Octane/Type/Primitive/BooleanSpec.hs +0/−34
- test-suite/Octane/Type/Primitive/DictionarySpec.hs +0/−37
- test-suite/Octane/Type/Primitive/Float32Spec.hs +0/−30
- test-suite/Octane/Type/Primitive/Int32Spec.hs +0/−30
- test-suite/Octane/Type/Primitive/Int64Spec.hs +0/−30
- test-suite/Octane/Type/Primitive/ListSpec.hs +0/−36
- test-suite/Octane/Type/Primitive/StreamSpec.hs +0/−32
- test-suite/Octane/Type/Primitive/TextSpec.hs +0/−40
- test-suite/Octane/Type/PrimitiveSpec.hs +0/−23
- test-suite/Octane/Type/PropertySpec.hs +0/−316
- test-suite/Octane/Type/ReplaySpec.hs +0/−97
- test-suite/Octane/TypeSpec.hs +0/−25
- test-suite/Octane/VersionSpec.hs +0/−11
- test-suite/OctaneSpec.hs +4/−9
- test-suite/TestSuite.hs +0/−12
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