octane (empty) → 0.1.0
raw patch · 49 files changed
+1131/−0 lines, 49 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, containers, data-binary-ieee754, octane, tasty, tasty-hspec, text
Files
- CHANGELOG.md +6/−0
- LICENSE.md +21/−0
- README.md +19/−0
- Setup.hs +4/−0
- executable/Executable.hs +4/−0
- library/Octane.hs +4/−0
- library/Octane/Core.hs +17/−0
- library/Octane/Main.hs +163/−0
- library/Octane/Types.hs +19/−0
- library/Octane/Types/Actor.hs +23/−0
- library/Octane/Types/ActorMap.hs +32/−0
- library/Octane/Types/Boolean.hs +17/−0
- library/Octane/Types/CacheItem.hs +32/−0
- library/Octane/Types/CacheProperty.hs +22/−0
- library/Octane/Types/Float32LE.hs +15/−0
- library/Octane/Types/Int32LE.hs +15/−0
- library/Octane/Types/Int64LE.hs +15/−0
- library/Octane/Types/KeyFrame.hs +27/−0
- library/Octane/Types/List.hs +18/−0
- library/Octane/Types/Mark.hs +23/−0
- library/Octane/Types/Message.hs +27/−0
- library/Octane/Types/ObjectMap.hs +18/−0
- library/Octane/Types/PCString.hs +37/−0
- library/Octane/Types/Property.hs +96/−0
- library/Octane/Types/Replay.hs +108/−0
- library/Octane/Types/Table.hs +41/−0
- octane.cabal +99/−0
- stack.yaml +4/−0
- test-suite/Octane/MainSpec.hs +7/−0
- test-suite/Octane/Types/ActorMapSpec.hs +7/−0
- test-suite/Octane/Types/ActorSpec.hs +7/−0
- test-suite/Octane/Types/BooleanSpec.hs +25/−0
- test-suite/Octane/Types/CacheItemSpec.hs +7/−0
- test-suite/Octane/Types/CachePropertySpec.hs +7/−0
- test-suite/Octane/Types/Float32LESpec.hs +7/−0
- test-suite/Octane/Types/Int32LESpec.hs +7/−0
- test-suite/Octane/Types/Int64LESpec.hs +7/−0
- test-suite/Octane/Types/KeyFrameSpec.hs +7/−0
- test-suite/Octane/Types/ListSpec.hs +7/−0
- test-suite/Octane/Types/MarkSpec.hs +7/−0
- test-suite/Octane/Types/MessageSpec.hs +7/−0
- test-suite/Octane/Types/ObjectMapSpec.hs +7/−0
- test-suite/Octane/Types/PCStringSpec.hs +7/−0
- test-suite/Octane/Types/PropertySpec.hs +7/−0
- test-suite/Octane/Types/ReplaySpec.hs +7/−0
- test-suite/Octane/Types/TableSpec.hs +7/−0
- test-suite/Octane/TypesSpec.hs +40/−0
- test-suite/OctaneSpec.hs +10/−0
- test-suite/TestSuite.hs +11/−0
+ CHANGELOG.md view
@@ -0,0 +1,6 @@+# Change log++Octane uses [Semantic Versioning][]. The change log is available [on GitHub][].++[semantic versioning]: http://semver.org/spec/v2.0.0.html+[on github]: https://github.com/tfausak/octane/releases
+ LICENSE.md view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2016 Taylor Fausak <taylor@fausak.me>++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is furnished to do+so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,19 @@+# [Octane][]++[![Build badge]][build status]++Octane parses [Rocket League][] replays.++## Usage++1. Install [Stack][].++2. Install Octane with `stack build`.++3. Parse a replay with `stack exec octane path/to/the.replay`.++[octane]: https://github.com/tfausak/octane+[build badge]: https://img.shields.io/travis/tfausak/octane/main.svg+[build status]: https://travis-ci.org/tfausak/octane+[rocket league]: http://rocketleague.psyonix.com+[stack]: http://haskellstack.org
+ Setup.hs view
@@ -0,0 +1,4 @@+import qualified Distribution.Simple++main :: IO ()+main = Distribution.Simple.defaultMain
+ executable/Executable.hs view
@@ -0,0 +1,4 @@+import qualified Octane++main :: IO ()+main = Octane.main
+ library/Octane.hs view
@@ -0,0 +1,4 @@+module Octane (module Octane) where++import Octane.Main as Octane+import Octane.Types as Octane
+ library/Octane/Core.hs view
@@ -0,0 +1,17 @@+module Octane.Core (module Octane.Core) where++import Control.Monad as Octane.Core (replicateM, when)+import Data.Binary as Octane.Core (Binary, Get, Put, decodeFileOrFail, encode, get, getWord8, put, putWord8)+import Data.Binary.Get as Octane.Core (ByteOffset, getByteString, getWord32le, getWord64le)+import Data.Binary.IEEE754 as Octane.Core (getFloat32le, putFloat32le)+import Data.Binary.Put as Octane.Core (putByteString, putWord32le, putWord64le)+import Data.ByteString as Octane.Core (ByteString)+import Data.Char as Octane.Core (isLatin1)+import Data.Function as Octane.Core ((&))+import Data.Int as Octane.Core (Int32, Int64)+import Data.IntMap as Octane.Core (IntMap)+import Data.Map as Octane.Core (Map)+import Data.Text as Octane.Core (Text)+import Data.Text.Encoding as Octane.Core (decodeLatin1, decodeUtf16LE, encodeUtf16LE)+import System.Environment as Octane.Core (getArgs)+import System.IO as Octane.Core (hPutStrLn, stderr, stdout)
+ library/Octane/Main.hs view
@@ -0,0 +1,163 @@+module Octane.Main (main) where++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.IntMap as IntMap+import qualified Data.Map as Map+import Octane.Core+import Octane.Types++main :: IO ()+main = do+ files <- getArgs+ contents <- mapM BS.readFile files+ results <- mapM decodeFileOrFail files+ mapM_ debug (zip3 files contents results)++debug :: (String, ByteString, Either (ByteOffset, String) Replay) -> IO ()+debug (file, contents, result) = case result of+ Left (offset, message) -> hPutStrLn stderr+ (file ++ " @ byte " ++ show offset ++ " - " ++ message)+ Right replay -> do+ putStrLn file++ let inputSize = contents & BS.length & fromIntegral+ let outputSize = replay & encode & BSL.length+ when (inputSize /= outputSize) $ do+ hPutStrLn stderr+ ( "input size ("+ ++ show inputSize+ ++ ") not equal to output size ("+ ++ show outputSize ++ ")!"+ )+ putStrLn ""++ putStrLn "# SIZE 1 #\n"+ print (getInt32LE (replaySize1 replay))+ putStrLn ""++ putStrLn "# CRC 1 #\n"+ print (getInt32LE (replayCRC1 replay))+ putStrLn ""++ putStrLn "# VERSION #\n"+ print (getInt32LE (replayVersion1 replay))+ print (getInt32LE (replayVersion2 replay))+ putStrLn ""++ putStrLn "# LABEL #\n"+ print (getPCString (replayLabel replay))+ putStrLn ""++ putStrLn "# PROPERTIES #\n"+ mapM_+ (\ (name, property) -> do+ putStr (show (getPCString name) ++ "\t=> ")+ debugProperty property)+ (Map.assocs (getTable (replayProperties replay)))+ putStrLn ""++ putStrLn "# SIZE 2 #\n"+ print (getInt32LE (replaySize2 replay))+ putStrLn ""++ putStrLn "# CRC 2 #\n"+ print (getInt32LE (replayCRC2 replay))+ putStrLn ""++ putStrLn "# EFFECTS #\n"+ mapM_+ (\ effect -> do+ print (getPCString effect))+ (getList (replayEffects replay))+ putStrLn ""++ putStrLn "# KEY FRAMES #\n"+ mapM_+ (\ keyFrame -> do+ putStrLn (show (getFloat32LE (keyFrameTime keyFrame)) ++ "s @ frame " ++ show (getInt32LE (keyFrameFrame keyFrame)) ++ " - bit " ++ show (getInt32LE (keyFramePosition keyFrame))))+ (getList (replayKeyFrames replay))+ putStrLn ""++ putStrLn "# FRAMES #\n"+ putStrLn (show (BS.length (replayFrames replay)) ++ " bytes")+ putStrLn ""++ putStrLn "# MESSAGES #\n"+ mapM_+ (\ message -> do+ putStrLn (show (getPCString (messageName message)) ++ " @ frame " ++ show (getInt32LE (messageFrame message)) ++ ": " ++ show (getPCString (messageContent message))))+ (getList (replayMessages replay))+ putStrLn ""++ putStrLn "# MARKS #\n"+ mapM_+ (\ goal -> do+ putStrLn (show (getPCString (markLabel goal)) ++ " @ frame " ++ show (getInt32LE (markFrame goal))))+ (getList (replayMarks replay))+ putStrLn ""++ putStrLn "# PACKAGES #\n"+ mapM_+ (\ package -> do+ print (getPCString package))+ (getList (replayPackages replay))+ putStrLn ""++ putStrLn "# OBJECTS #\n"+ mapM_+ (\ (index, object) -> do+ putStrLn (show index ++ "\t=> " ++ show (getPCString object)))+ (IntMap.assocs (getObjectMap (replayObjectMap replay)))+ putStrLn ""++ putStrLn "# NAMES #\n"+ mapM_+ (\ name -> do+ print (getPCString name))+ (getList (replayNames replay))+ putStrLn ""++ putStrLn "# ACTORS #\n"+ mapM_+ (\ (index, actor) -> do+ putStrLn (show index ++ "\t=> " ++ show (getPCString actor)))+ (IntMap.assocs (getActorMap (replayActorMap replay)))+ putStrLn ""++ putStrLn "# CACHE #\n"+ mapM_+ (\ cacheItem -> do+ let a = cacheItem & cacheItemTag & getInt32LE & fromIntegral+ let b = replay & replayObjectMap & getObjectMap & IntMap.lookup a & fmap getPCString+ putStrLn ("ID:\t" ++ show a ++ " (" ++ show b ++ ")")+ putStrLn ("Start:\t" ++ show (getInt32LE (cacheItemStart cacheItem)))+ putStrLn ("End:\t" ++ show (getInt32LE (cacheItemEnd cacheItem)))+ putStrLn "Properties:"+ mapM_+ (\ cacheProperty -> do+ let c = cacheProperty & cachePropertyIndex & getInt32LE & fromIntegral+ let d = replay & replayObjectMap & getObjectMap & IntMap.lookup c & fmap getPCString+ putStrLn ("- " ++ show (getInt32LE (cachePropertyTag cacheProperty)) ++ "\t=> " ++ show c ++ " (" ++ show d ++ ")"))+ (getList (cacheItemCacheProperties cacheItem))+ putStrLn "")+ (getList (replayCacheItems replay))++debugProperty :: Property -> IO ()+debugProperty property = case property of+ ArrayProperty _ (NewList array) -> do+ putStrLn "[array]"+ mapM_+ (\ (NewTable table) -> mapM_+ (\ (NewPCString k, v) -> do+ putStr ("\t" ++ show k ++ "\t=> ")+ debugProperty v)+ (Map.assocs table) >> putStrLn "")+ array+ BoolProperty _ (NewBoolean value) -> print value+ ByteProperty _ (NewPCString key, NewPCString value) -> putStrLn (show key ++ ": " ++ show value)+ FloatProperty _ (NewFloat32LE value) -> print value+ IntProperty _ (NewInt32LE value) -> print value+ NameProperty _ (NewPCString value) -> putStrLn (show value ++ " [name]")+ QWordProperty _ (NewInt64LE value) -> print value+ StrProperty _ (NewPCString value) -> print value
+ library/Octane/Types.hs view
@@ -0,0 +1,19 @@+module Octane.Types (module Octane.Types) where++import Octane.Types.Actor as Octane.Types+import Octane.Types.ActorMap as Octane.Types+import Octane.Types.Boolean as Octane.Types+import Octane.Types.CacheItem as Octane.Types+import Octane.Types.CacheProperty as Octane.Types+import Octane.Types.Float32LE as Octane.Types+import Octane.Types.Int32LE as Octane.Types+import Octane.Types.Int64LE as Octane.Types+import Octane.Types.KeyFrame as Octane.Types+import Octane.Types.List as Octane.Types+import Octane.Types.Mark as Octane.Types+import Octane.Types.Message as Octane.Types+import Octane.Types.ObjectMap as Octane.Types+import Octane.Types.PCString as Octane.Types+import Octane.Types.Property as Octane.Types+import Octane.Types.Replay as Octane.Types+import Octane.Types.Table as Octane.Types
+ library/Octane/Types/Actor.hs view
@@ -0,0 +1,23 @@+module Octane.Types.Actor (Actor(..)) where++import Octane.Core+import Octane.Types.Int32LE+import Octane.Types.PCString++data Actor = NewActor+ { actorName :: PCString+ , actorTag :: Int32LE+ } deriving (Show)++instance Binary Actor where+ get = do+ name <- get+ tag <- get+ return NewActor+ { actorName = name+ , actorTag = tag+ }++ put actor = do+ actor & actorName & put+ actor & actorTag & put
+ library/Octane/Types/ActorMap.hs view
@@ -0,0 +1,32 @@+module Octane.Types.ActorMap (ActorMap(..)) where++import qualified Data.IntMap as IntMap+import Octane.Core+import Octane.Types.Actor+import Octane.Types.Int32LE+import Octane.Types.List+import Octane.Types.PCString++newtype ActorMap = NewActorMap+ { getActorMap :: IntMap.IntMap PCString+ } deriving (Show)++instance Binary ActorMap where+ get = do+ (NewList actors) <- get+ actors & map toTuple & IntMap.fromList & NewActorMap & return++ put (NewActorMap actors) = do+ actors & IntMap.assocs & map fromTuple & NewList & put++toTuple :: Actor -> (Int, PCString)+toTuple actor =+ ( actor & actorTag & getInt32LE & fromIntegral+ , actor & actorName+ )++fromTuple :: (Int, PCString) -> Actor+fromTuple (tag, name) = NewActor+ { actorName = name+ , actorTag = tag & fromIntegral & NewInt32LE+ }
+ library/Octane/Types/Boolean.hs view
@@ -0,0 +1,17 @@+module Octane.Types.Boolean (Boolean(..)) where++import Octane.Core++newtype Boolean = NewBoolean+ { getBoolean :: Bool+ } deriving (Eq, Show)++instance Binary Boolean where+ get = do+ boolean <- getWord8+ if boolean > 1+ then fail "out of bounds"+ else boolean & fromIntegral & toEnum & NewBoolean & return++ put (NewBoolean boolean) = do+ boolean & fromEnum & fromIntegral & putWord8
+ library/Octane/Types/CacheItem.hs view
@@ -0,0 +1,32 @@+module Octane.Types.CacheItem (CacheItem(..)) where++import Octane.Core+import Octane.Types.CacheProperty+import Octane.Types.Int32LE+import Octane.Types.List++data CacheItem = NewCacheItem+ { cacheItemTag :: Int32LE+ , cacheItemStart :: Int32LE+ , cacheItemEnd :: Int32LE+ , cacheItemCacheProperties :: List CacheProperty+ } deriving (Show)++instance Binary CacheItem where+ get = do+ tag <- get+ start <- get+ end <- get+ cacheProperties <- get+ return NewCacheItem+ { cacheItemTag = tag+ , cacheItemStart = start+ , cacheItemEnd = end+ , cacheItemCacheProperties = cacheProperties+ }++ put cacheItem = do+ cacheItem & cacheItemTag & put+ cacheItem & cacheItemStart & put+ cacheItem & cacheItemEnd & put+ cacheItem & cacheItemCacheProperties & put
+ library/Octane/Types/CacheProperty.hs view
@@ -0,0 +1,22 @@+module Octane.Types.CacheProperty (CacheProperty(..)) where++import Octane.Core+import Octane.Types.Int32LE++data CacheProperty = NewCacheProperty+ { cachePropertyIndex :: Int32LE+ , cachePropertyTag :: Int32LE+ } deriving (Show)++instance Binary CacheProperty where+ get = do+ index <- get+ tag <- get+ return NewCacheProperty+ { cachePropertyIndex = index+ , cachePropertyTag = tag+ }++ put cacheProperty = do+ cacheProperty & cachePropertyIndex & put+ cacheProperty & cachePropertyTag & put
+ library/Octane/Types/Float32LE.hs view
@@ -0,0 +1,15 @@+module Octane.Types.Float32LE (Float32LE(..)) where++import Octane.Core++newtype Float32LE = NewFloat32LE+ { getFloat32LE :: Float+ } deriving (Show)++instance Binary Float32LE where+ get = do+ float <- getFloat32le+ float & NewFloat32LE & return++ put (NewFloat32LE float) = do+ float & putFloat32le
+ library/Octane/Types/Int32LE.hs view
@@ -0,0 +1,15 @@+module Octane.Types.Int32LE (Int32LE(..)) where++import Octane.Core++newtype Int32LE = NewInt32LE+ { getInt32LE :: Int32+ } deriving (Show)++instance Binary Int32LE where+ get = do+ int <- getWord32le+ int & fromIntegral & NewInt32LE & return++ put (NewInt32LE int) = do+ int & fromIntegral & putWord32le
+ library/Octane/Types/Int64LE.hs view
@@ -0,0 +1,15 @@+module Octane.Types.Int64LE (Int64LE(..)) where++import Octane.Core++newtype Int64LE = NewInt64LE+ { getInt64LE :: Int64+ } deriving (Show)++instance Binary Int64LE where+ get = do+ int <- getWord64le+ int & fromIntegral & NewInt64LE & return++ put (NewInt64LE int) = do+ int & fromIntegral & putWord64le
+ library/Octane/Types/KeyFrame.hs view
@@ -0,0 +1,27 @@+module Octane.Types.KeyFrame (KeyFrame(..)) where++import Octane.Core+import Octane.Types.Float32LE+import Octane.Types.Int32LE++data KeyFrame = NewKeyFrame+ { keyFrameTime :: Float32LE+ , keyFrameFrame :: Int32LE+ , keyFramePosition :: Int32LE+ } deriving (Show)++instance Binary KeyFrame where+ get = do+ time <- get+ frame <- get+ position <- get+ return NewKeyFrame+ { keyFrameTime = time+ , keyFrameFrame = frame+ , keyFramePosition = position+ }++ put keyFrame = do+ keyFrame & keyFrameTime & put+ keyFrame & keyFrameFrame & put+ keyFrame & keyFramePosition & put
+ library/Octane/Types/List.hs view
@@ -0,0 +1,18 @@+module Octane.Types.List (List(..)) where++import Octane.Core+import Octane.Types.Int32LE++newtype List a = NewList+ { getList :: [a]+ } deriving (Show)++instance (Binary a) => Binary (List a) where+ get = do+ (NewInt32LE size) <- get+ elements <- replicateM (fromIntegral size) get+ elements & NewList & return++ put (NewList list) = do+ list & length & fromIntegral & NewInt32LE & put+ list & mapM_ put
+ library/Octane/Types/Mark.hs view
@@ -0,0 +1,23 @@+module Octane.Types.Mark (Mark(..)) where++import Octane.Core+import Octane.Types.Int32LE+import Octane.Types.PCString++data Mark = NewMark+ { markLabel :: PCString+ , markFrame :: Int32LE+ } deriving (Show)++instance Binary Mark where+ get = do+ label <- get+ frame <- get+ return NewMark+ { markLabel = label+ , markFrame = frame+ }++ put mark = do+ mark & markLabel & put+ mark & markFrame & put
+ library/Octane/Types/Message.hs view
@@ -0,0 +1,27 @@+module Octane.Types.Message (Message(..)) where++import Octane.Core+import Octane.Types.Int32LE+import Octane.Types.PCString++data Message = NewMessage+ { messageFrame :: Int32LE+ , messageName :: PCString+ , messageContent :: PCString+ } deriving (Show)++instance Binary Message where+ get = do+ frame <- get+ name <- get+ content <- get+ return NewMessage+ { messageFrame = frame+ , messageName = name+ , messageContent = content+ }++ put message = do+ message & messageFrame & put+ message & messageName & put+ message & messageContent & put
+ library/Octane/Types/ObjectMap.hs view
@@ -0,0 +1,18 @@+module Octane.Types.ObjectMap (ObjectMap(..)) where++import qualified Data.IntMap as IntMap+import Octane.Core+import Octane.Types.List+import Octane.Types.PCString++newtype ObjectMap = NewObjectMap+ { getObjectMap :: IntMap PCString+ } deriving (Show)++instance Binary ObjectMap where+ get = do+ NewList objects <- get+ objects & zip [0 ..] & IntMap.fromAscList & NewObjectMap & return++ put (NewObjectMap objects) = do+ objects & IntMap.elems & NewList & put
+ library/Octane/Types/PCString.hs view
@@ -0,0 +1,37 @@+module Octane.Types.PCString (PCString(..)) where++import qualified Data.ByteString.Char8 as BS8+import qualified Data.Text as Text+import Octane.Core+import Octane.Types.Int32LE++newtype PCString = NewPCString+ { getPCString :: Text+ } deriving (Eq, Ord, Show)++instance Binary PCString where+ get = do+ (NewInt32LE size) <- get+ string <- if size < 0+ then do+ let actualSize = 2 * negate size+ bytes <- getByteString (fromIntegral actualSize)+ bytes & decodeUtf16LE & return+ else do+ bytes <- getByteString (fromIntegral size)+ bytes & decodeLatin1 & return+ string & Text.dropEnd 1 & NewPCString & return++ put (NewPCString string) = do+ let cString = Text.snoc string '\NUL'+ let size = cString & Text.length & fromIntegral+ if Text.all isLatin1 cString+ then do+ size & NewInt32LE & put+ cString & encodeLatin1 & putByteString+ else do+ size & negate & NewInt32LE & put+ cString & encodeUtf16LE & putByteString++encodeLatin1 :: Text -> ByteString+encodeLatin1 text = text & Text.unpack & BS8.pack
+ library/Octane/Types/Property.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE OverloadedStrings #-}++module Octane.Types.Property (Property(..)) where++import Octane.Core+import Octane.Types.Boolean+import Octane.Types.Float32LE+import Octane.Types.Int32LE+import Octane.Types.Int64LE+import Octane.Types.List+import Octane.Types.PCString+import Octane.Types.Table++data Property+ = ArrayProperty Int64LE (List (Table Property))+ | BoolProperty Int64LE Boolean+ | ByteProperty Int64LE (PCString, PCString)+ | FloatProperty Int64LE Float32LE+ | IntProperty Int64LE Int32LE+ | NameProperty Int64LE PCString+ | QWordProperty Int64LE Int64LE+ | StrProperty Int64LE PCString+ deriving (Show)++instance Binary Property where+ get = do+ kind <- get+ size <- get+ case getPCString kind of+ "ArrayProperty" -> do+ value <- get+ ArrayProperty size value & return+ "BoolProperty" -> do+ value <- get+ BoolProperty size value & return+ "ByteProperty" -> do+ key <- get+ value <- get+ ByteProperty size (key, value) & return+ "FloatProperty" -> do+ value <- case size of+ NewInt64LE 4 -> get+ _ -> fail ("unknown FloatProperty size " ++ show size)+ FloatProperty size value & return+ "IntProperty" -> do+ value <- case size of+ NewInt64LE 4 -> get+ _ -> fail ("unknown IntProperty size " ++ show size)+ IntProperty size value & return+ "NameProperty" -> do+ value <- get+ NameProperty size value & return+ "StrProperty" -> do+ value <- get+ StrProperty size value & return+ "QWordProperty" -> do+ value <- case size of+ NewInt64LE 8 -> get+ _ -> fail ("unknown QWordProperty size " ++ show size)+ QWordProperty size value & return+ _ -> fail ("unknown property type " ++ show kind)++ put property = case property of+ ArrayProperty size value -> do+ "ArrayProperty" & NewPCString & put+ size & put+ value & put+ BoolProperty size value -> do+ "BoolProperty" & NewPCString & put+ size & put+ value & put+ ByteProperty size (key, value) -> do+ "ByteProperty" & NewPCString & put+ size & put+ key & put+ value & put+ FloatProperty size value -> do+ "FloatProperty" & NewPCString & put+ size & put+ value & put+ IntProperty size value -> do+ "IntProperty" & NewPCString & put+ size & put+ value & put+ NameProperty size value -> do+ "NameProperty" & NewPCString & put+ size & put+ value & put+ QWordProperty size value -> do+ "QWordProperty" & NewPCString & put+ size & put+ value & put+ StrProperty size value -> do+ "StrProperty" & NewPCString & put+ size & put+ value & put
+ library/Octane/Types/Replay.hs view
@@ -0,0 +1,108 @@+module Octane.Types.Replay (Replay(..)) where++import qualified Data.ByteString as BS+import Octane.Core+import Octane.Types.ActorMap+import Octane.Types.CacheItem+import Octane.Types.Int32LE+import Octane.Types.KeyFrame+import Octane.Types.List+import Octane.Types.Mark+import Octane.Types.Message+import Octane.Types.ObjectMap+import Octane.Types.PCString+import Octane.Types.Property+import Octane.Types.Table++data Replay = NewReplay+ { replaySize1 :: Int32LE+ , replayCRC1 :: Int32LE+ , replayVersion1 :: Int32LE+ , replayVersion2 :: Int32LE+ , replayLabel :: PCString+ , replayProperties :: Table Property+ , replaySize2 :: Int32LE+ , replayCRC2 :: Int32LE+ , replayEffects :: List PCString+ , replayKeyFrames :: List KeyFrame+ , replayFrames :: ByteString+ , replayMessages :: List Message+ , replayMarks :: List Mark+ , replayPackages :: List PCString+ , replayObjectMap :: ObjectMap+ , replayNames :: List PCString+ , replayActorMap :: ActorMap+ , replayCacheItems :: List CacheItem+ } deriving (Show)++instance Binary Replay where+ get = do+ size1 <- get+ crc1 <- get+ version1 <- get+ version2 <- get+ label <- get+ properties <- get+ size2 <- get+ crc2 <- get+ effects <- get+ keyFrames <- get+ frames <- getFrameBytes+ messages <- get+ marks <- get+ packages <- get+ objectMap <- get+ names <- get+ actorMap <- get+ cacheItems <- get+ return NewReplay+ { replaySize1 = size1+ , replayCRC1 = crc1+ , replayVersion1 = version1+ , replayVersion2 = version2+ , replayLabel = label+ , replayProperties = properties+ , replaySize2 = size2+ , replayCRC2 = crc2+ , replayEffects = effects+ , replayKeyFrames = keyFrames+ , replayFrames = frames+ , replayMessages = messages+ , replayMarks = marks+ , replayPackages = packages+ , replayObjectMap = objectMap+ , replayNames = names+ , replayActorMap = actorMap+ , replayCacheItems = cacheItems+ }++ put replay = do+ replay & replaySize1 & put+ replay & replayCRC1 & put+ replay & replayVersion1 & put+ replay & replayVersion2 & put+ replay & replayLabel & put+ replay & replayProperties & put+ replay & replaySize2 & put+ replay & replayCRC2 & put+ replay & replayEffects & put+ replay & replayKeyFrames & put+ replay & replayFrames & putFrameBytes+ replay & replayMessages & put+ replay & replayMarks & put+ replay & replayPackages & put+ replay & replayObjectMap & put+ replay & replayNames & put+ replay & replayActorMap & put+ replay & replayCacheItems & put++getFrameBytes :: Get ByteString+getFrameBytes = do+ NewInt32LE size <- get+ frames <- getByteString (fromIntegral size)+ return frames++putFrameBytes :: ByteString -> Put+putFrameBytes frames = do+ frames & BS.length & fromIntegral & NewInt32LE & put+ frames & putByteString
+ library/Octane/Types/Table.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}++module Octane.Types.Table (Table(..)) where++import qualified Data.Map as Map+import Octane.Core+import Octane.Types.PCString++newtype Table a = NewTable+ { getTable :: Map PCString a+ } deriving (Show)++instance (Binary a) => Binary (Table a) where+ get = do+ row <- getRow+ if Map.null row+ then do+ row & NewTable & return+ else do+ NewTable rows <- get+ rows & Map.union row & NewTable & return++ put (NewTable rows) = do+ rows & Map.assocs & mapM_ putRow+ "None" & NewPCString & put++getRow :: (Binary a) => Get (Map PCString a)+getRow = do+ key <- get+ if key == NewPCString "None"+ then do+ return Map.empty+ else do+ value <- get+ return (Map.singleton key value)+++putRow :: (Binary a) => (PCString, a) -> Put+putRow (key, value) = do+ key & put+ value & put
+ octane.cabal view
@@ -0,0 +1,99 @@+name: octane+version: 0.1.0+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE.md+maintainer: Taylor Fausak+homepage: https://github.com/tfausak/octane+synopsis: A Rocket League replay parser.+description:+ <https://github.com/tfausak/octane#readme>+category: Game+extra-source-files:+ CHANGELOG.md+ README.md+ stack.yaml++source-repository head+ type: git+ location: https://github.com/tfausak/octane++library+ exposed-modules:+ Octane+ Octane.Main+ Octane.Types+ Octane.Types.Actor+ Octane.Types.ActorMap+ Octane.Types.Boolean+ Octane.Types.CacheItem+ Octane.Types.CacheProperty+ Octane.Types.Float32LE+ Octane.Types.Int32LE+ Octane.Types.Int64LE+ Octane.Types.KeyFrame+ Octane.Types.List+ Octane.Types.Mark+ Octane.Types.Message+ Octane.Types.ObjectMap+ Octane.Types.PCString+ Octane.Types.Property+ Octane.Types.Replay+ Octane.Types.Table+ build-depends:+ base ==4.8.*,+ binary ==0.7.*,+ bytestring ==0.10.*,+ containers ==0.5.*,+ data-binary-ieee754 ==0.4.*,+ text ==1.2.*+ default-language: Haskell2010+ hs-source-dirs: library+ other-modules:+ Octane.Core+ ghc-options: -Wall++executable octane+ main-is: Executable.hs+ build-depends:+ base -any,+ octane -any+ default-language: Haskell2010+ hs-source-dirs: executable+ ghc-options: -threaded -Wall++test-suite octane-test-suite+ type: exitcode-stdio-1.0+ main-is: TestSuite.hs+ build-depends:+ base -any,+ binary -any,+ bytestring -any,+ octane -any,+ tasty ==0.11.*,+ tasty-hspec ==1.1.*+ default-language: Haskell2010+ hs-source-dirs: test-suite+ other-modules:+ OctaneSpec+ Octane.MainSpec+ Octane.TypesSpec+ Octane.Types.ActorSpec+ Octane.Types.ActorMapSpec+ Octane.Types.BooleanSpec+ Octane.Types.CacheItemSpec+ Octane.Types.CachePropertySpec+ Octane.Types.Float32LESpec+ Octane.Types.Int32LESpec+ Octane.Types.Int64LESpec+ Octane.Types.KeyFrameSpec+ Octane.Types.ListSpec+ Octane.Types.MarkSpec+ Octane.Types.MessageSpec+ Octane.Types.ObjectMapSpec+ Octane.Types.PCStringSpec+ Octane.Types.PropertySpec+ Octane.Types.ReplaySpec+ Octane.Types.TableSpec+ ghc-options: -threaded -Wall
+ stack.yaml view
@@ -0,0 +1,4 @@+install-ghc: true+packages: [.]+require-stack-version: '>=1.0'+resolver: nightly-2016-01-30
+ test-suite/Octane/MainSpec.hs view
@@ -0,0 +1,7 @@+module Octane.MainSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Main" $ do+ return ()
+ test-suite/Octane/Types/ActorMapSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.ActorMapSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "ActorMap" $ do+ return ()
+ test-suite/Octane/Types/ActorSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.ActorSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Actor" $ do+ return ()
+ test-suite/Octane/Types/BooleanSpec.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}++module Octane.Types.BooleanSpec (spec) where++import qualified Data.Binary as Binary+import qualified Data.Binary.Get as Binary+import qualified Data.ByteString.Lazy as BSL+import qualified Octane as Octane+import Test.Tasty.Hspec++spec :: Spec+spec = describe "Boolean" $ do+ it "can be decoded" $ do+ decodeBoolean "\0" `shouldBe` Right ("", 1, Octane.NewBoolean False)+ decodeBoolean "\1" `shouldBe` Right ("", 1, Octane.NewBoolean True)+ it "can be encoded" $ do+ Binary.encode (Octane.NewBoolean False) `shouldBe` "\0"+ Binary.encode (Octane.NewBoolean True) `shouldBe` "\1"+ it "handles extra data when decoding" $ do+ decodeBoolean "\0extra" `shouldBe` Right ("extra", 1, Octane.NewBoolean False)+ it "does not raise a runtime error when decoding garbage" $ do+ decodeBoolean "garbage" `shouldBe` Left ("arbage", 1, "out of bounds")++decodeBoolean :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Boolean)+decodeBoolean = Binary.decodeOrFail
+ test-suite/Octane/Types/CacheItemSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.CacheItemSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "CacheItem" $ do+ return ()
+ test-suite/Octane/Types/CachePropertySpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.CachePropertySpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "CacheProperty" $ do+ return ()
+ test-suite/Octane/Types/Float32LESpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.Float32LESpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Float32LE" $ do+ return ()
+ test-suite/Octane/Types/Int32LESpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.Int32LESpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Int32LE" $ do+ return ()
+ test-suite/Octane/Types/Int64LESpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.Int64LESpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Int64LE" $ do+ return ()
+ test-suite/Octane/Types/KeyFrameSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.KeyFrameSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "KeyFrame" $ do+ return ()
+ test-suite/Octane/Types/ListSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.ListSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "ListSpec" $ do+ return ()
+ test-suite/Octane/Types/MarkSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.MarkSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Mark" $ do+ return ()
+ test-suite/Octane/Types/MessageSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.MessageSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Message" $ do+ return ()
+ test-suite/Octane/Types/ObjectMapSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.ObjectMapSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "ObjectMap" $ do+ return ()
+ test-suite/Octane/Types/PCStringSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.PCStringSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "PCString" $ do+ return ()
+ test-suite/Octane/Types/PropertySpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.PropertySpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Property" $ do+ return ()
+ test-suite/Octane/Types/ReplaySpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.ReplaySpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Replay" $ do+ return ()
+ test-suite/Octane/Types/TableSpec.hs view
@@ -0,0 +1,7 @@+module Octane.Types.TableSpec (spec) where++import Test.Tasty.Hspec++spec :: Spec+spec = describe "Table" $ do+ return ()
+ test-suite/Octane/TypesSpec.hs view
@@ -0,0 +1,40 @@+module Octane.TypesSpec (spec) where++import qualified Octane.Types.ActorSpec as ActorSpec+import qualified Octane.Types.ActorMapSpec as ActorMapSpec+import qualified Octane.Types.BooleanSpec as BooleanSpec+import qualified Octane.Types.CacheItemSpec as CacheItemSpec+import qualified Octane.Types.CachePropertySpec as CachePropertySpec+import qualified Octane.Types.Float32LESpec as Float32LESpec+import qualified Octane.Types.Int32LESpec as Int32LESpec+import qualified Octane.Types.Int64LESpec as Int64LESpec+import qualified Octane.Types.KeyFrameSpec as KeyFrameSpec+import qualified Octane.Types.ListSpec as ListSpec+import qualified Octane.Types.MarkSpec as MarkSpec+import qualified Octane.Types.MessageSpec as MessageSpec+import qualified Octane.Types.ObjectMapSpec as ObjectMapSpec+import qualified Octane.Types.PCStringSpec as PCStringSpec+import qualified Octane.Types.PropertySpec as PropertySpec+import qualified Octane.Types.ReplaySpec as ReplaySpec+import qualified Octane.Types.TableSpec as TableSpec+import Test.Tasty.Hspec++spec :: Spec+spec = describe "Types" $ do+ ActorSpec.spec+ ActorMapSpec.spec+ BooleanSpec.spec+ CacheItemSpec.spec+ CachePropertySpec.spec+ Float32LESpec.spec+ Int32LESpec.spec+ Int64LESpec.spec+ KeyFrameSpec.spec+ ListSpec.spec+ MarkSpec.spec+ MessageSpec.spec+ ObjectMapSpec.spec+ PCStringSpec.spec+ PropertySpec.spec+ ReplaySpec.spec+ TableSpec.spec
+ test-suite/OctaneSpec.hs view
@@ -0,0 +1,10 @@+module OctaneSpec (spec) where++import qualified Octane.MainSpec as MainSpec+import qualified Octane.TypesSpec as TypesSpec+import Test.Tasty.Hspec++spec :: Spec+spec = describe "Octane" $ do+ MainSpec.spec+ TypesSpec.spec
+ test-suite/TestSuite.hs view
@@ -0,0 +1,11 @@+import qualified OctaneSpec as OctaneSpec+import qualified Test.Tasty+import Test.Tasty.Hspec++main :: IO ()+main = do+ test <- testSpec "octane" spec+ Test.Tasty.defaultMain test++spec :: Spec+spec = parallel OctaneSpec.spec