packages feed

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 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