packages feed

octane 0.13.0 → 0.13.1

raw patch · 25 files changed

+440/−234 lines, 25 filesdep ~http-clientdep ~http-client-tlsPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: http-client, http-client-tls

API changes (from Hackage documentation)

- Octane.Utility.Parser: parseFrames :: ReplayWithoutFrames -> [Frame]
+ Octane.Type.CompressedWord: CompressedWord :: Word -> Word -> CompressedWord
+ Octane.Type.CompressedWord: [limit] :: CompressedWord -> Word
+ Octane.Type.CompressedWord: [value] :: CompressedWord -> Word
+ Octane.Type.CompressedWord: data CompressedWord
+ Octane.Type.CompressedWord: fromCompressedWord :: (Integral a) => CompressedWord -> a
+ Octane.Type.CompressedWord: instance Control.DeepSeq.NFData Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.CompressedWord: instance Data.Aeson.Types.Class.ToJSON Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.CompressedWord: instance Data.Binary.Bits.BinaryBit Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.CompressedWord: instance GHC.Classes.Eq Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.CompressedWord: instance GHC.Generics.Generic Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.CompressedWord: instance GHC.Show.Show Octane.Type.CompressedWord.CompressedWord
+ Octane.Type.Initialization: getInitialization :: Text -> BitGet Initialization
+ Octane.Type.Initialization: putInitialization :: Initialization -> BitPut ()
+ Octane.Type.Vector: getFloatVector :: BitGet (Vector Float)
+ Octane.Type.Vector: getInt8Vector :: BitGet (Vector Int8)
+ Octane.Type.Vector: getIntVector :: BitGet (Vector Int)
+ Octane.Type.Vector: putInt8Vector :: Vector Int8 -> BitPut ()
+ Octane.Type.Vector: putIntVector :: Vector Int -> BitPut ()
+ Octane.Utility.Generator: generateStream :: [Frame] -> List Text -> List Text -> List ClassItem -> List CacheItem -> Stream
+ Octane.Utility.Parser: parseStream :: ReplayWithoutFrames -> [Frame]
- Octane.Type.Replication: Replication :: Word -> Text -> Text -> State -> Maybe Initialization -> Map Text Value -> Replication
+ Octane.Type.Replication: Replication :: CompressedWord -> Text -> Text -> State -> Maybe Initialization -> Map Text Value -> Replication
- Octane.Type.Value: VLoadoutOnline :: [[(Word32, Int)]] -> Value
+ Octane.Type.Value: VLoadoutOnline :: [[(Word32, CompressedWord)]] -> Value
- Octane.Type.Value: VReservation :: Int -> Word8 -> RemoteId -> (Maybe Word8) -> (Maybe Text) -> Boolean -> Boolean -> Value
+ Octane.Type.Value: VReservation :: CompressedWord -> Word8 -> RemoteId -> (Maybe Word8) -> (Maybe Text) -> Boolean -> Boolean -> Value

Files

library/Octane/Main.hs view
@@ -65,7 +65,7 @@  decode :: String -> IO Replay.Replay decode x = do-    case Client.parseUrl x of+    case Client.parseUrlThrow x of         Nothing -> do             let file = x             Binary.decodeFile file
library/Octane/Type/ClassItem.hs view
@@ -14,7 +14,7 @@   -- | A class (like @Core.Object@) and it's associated ID in the net stream--- (like 0).+-- (like @0@). data ClassItem = ClassItem     { name :: Text.Text     -- ^ The class's name.
+ library/Octane/Type/CompressedWord.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}++module Octane.Type.CompressedWord (CompressedWord(..), fromCompressedWord) where++import Data.Aeson ((.=))+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 BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Bits as Bits+import qualified GHC.Generics as Generics+import qualified Octane.Type.Boolean as Boolean++-- $setup+-- >>> import qualified Data.Binary.Get as Binary+-- >>> import qualified Data.Binary.Put as Binary+++-- | A compressed, unsigned integer. When serialized, the least significant bit+-- is first. Bits are serialized until the next bit would be greater than the+-- limit, or the number of bits necessary to reach the limit has been reached,+-- whichever comes first.+data CompressedWord = CompressedWord+    { limit :: Word+    , value :: Word+    } deriving (Eq, Generics.Generic, Show)++-- | Abuses the first argument to 'BinaryBit.getBits' as the maximum value.+--+-- >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 4)) "\x7f" :: CompressedWord+-- CompressedWord {limit = 4, value = 2}+--+-- >>> Binary.runPut (BinaryBit.runBitPut (BinaryBit.putBits 0 (CompressedWord 4 2)))+-- "\128"+instance BinaryBit.BinaryBit CompressedWord where+    getBits n = do+        let theLimit = fromIntegral n+        theValue <- getStep theLimit (bitSize theLimit) 0 0+        pure (CompressedWord theLimit theValue)++    putBits _ compressedWord = do+        let theLimit = fromIntegral (limit compressedWord)+        let theValue = fromIntegral (value compressedWord)+        let maxBits = bitSize theLimit+        let upper = (2 ^ (maxBits - 1)) - 1+        let lower = theLimit - upper+        let numBits = if lower > theValue || theValue > upper+                then maxBits+                else maxBits - 1+        BinaryBit.putWord64be numBits theValue++instance DeepSeq.NFData CompressedWord where++-- | Encoded as an object.+--+-- >>> Aeson.encode (CompressedWord 2 1)+-- "{\"Value\":1,\"Limit\":2}"+instance Aeson.ToJSON CompressedWord where+    toJSON compressedWord = Aeson.object+        [ "Limit" .= limit compressedWord+        , "Value" .= value compressedWord+        ]+++-- | Converts a 'CompressedWord' into any integral value. This is a lossy+-- conversion because it discards the compressed word's maximum value.+--+-- >>> fromCompressedWord (CompressedWord 2 1) :: Int+-- 1+fromCompressedWord :: (Integral a) => CompressedWord -> a+fromCompressedWord compressedWord = compressedWord & value & fromIntegral+++bitSize :: (Integral a, Integral b) => a -> b+bitSize x = x & fromIntegral & logBase (2 :: Double) & ceiling+++getStep :: Word -> Word -> Word -> Word -> BinaryBit.BitGet Word+getStep theLimit maxBits position theValue = do+    let x = Bits.shiftL 1 (fromIntegral position)+    if position < maxBits && theValue + x <= theLimit+    then do+        bit <- BinaryBit.getBits 0+        let newValue = if Boolean.unpack bit then theValue + x else theValue+        getStep theLimit maxBits (position + 1) newValue+    else pure theValue
library/Octane/Type/Float32.hs view
@@ -29,7 +29,9 @@     { unpack :: Float     } deriving (Eq, Fractional, Generics.Generic, Num, Ord) --- | >>> Binary.decode "\x9a\x99\x99\x3f" :: Float32+-- | Little-endian.+--+-- >>> Binary.decode "\x9a\x99\x99\x3f" :: Float32 -- 1.2 -- -- >>> Binary.encode (1.2 :: Float32)@@ -43,7 +45,9 @@         & unpack         & IEEE754.putFloat32le --- | >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x59\x99\x99\xfc" :: Float32+-- | Little-endian with the bits in each byte reversed.+--+-- >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x59\x99\x99\xfc" :: Float32 -- 1.2 -- -- >>> Binary.runPut (BinaryBit.runBitPut (BinaryBit.putBits 0 (1.2 :: Float32)))
library/Octane/Type/Frame.hs view
@@ -16,6 +16,7 @@ import qualified Data.Text as StrictText import qualified GHC.Generics as Generics import qualified Octane.Data as Data+import qualified Octane.Type.CompressedWord as CompressedWord import qualified Octane.Type.Float32 as Float32 import qualified Octane.Type.Initialization as Initialization import qualified Octane.Type.Replication as Replication@@ -61,7 +62,7 @@ instance Aeson.ToJSON Spawned where     toJSON (Spawned xs) = xs         & map (\ x -> do-            let k = x & Replication.actorId & show & StrictText.pack+            let k = x & Replication.actorId & CompressedWord.value & show & StrictText.pack             let v = Aeson.object                     [ "Name" .= Replication.objectName x                     , "Class" .= Replication.className x@@ -87,6 +88,7 @@         & map (\ x -> do             let k = x                     & Replication.actorId+                    & CompressedWord.value                     & show                     & StrictText.pack             let v = x@@ -117,6 +119,7 @@ instance Aeson.ToJSON Destroyed where     toJSON (Destroyed xs) = xs         & map Replication.actorId+        & map CompressedWord.value         & Aeson.toJSON  getDestroyed :: [Replication.Replication] -> Destroyed
library/Octane/Type/Initialization.hs view
@@ -2,10 +2,19 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE StrictData #-} -module Octane.Type.Initialization (Initialization(..)) where+module Octane.Type.Initialization+    ( Initialization(..)+    , getInitialization+    , putInitialization+    ) where  import qualified Control.DeepSeq as DeepSeq+import qualified Data.Binary.Bits.Get as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+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.Int8 as Int8 import qualified Octane.Type.Vector as Vector @@ -22,3 +31,27 @@     } deriving (Eq, Generics.Generic, Show)  instance DeepSeq.NFData Initialization where+++-- | Gets the 'Initialization' for a given class.+getInitialization :: StrictText.Text -> BinaryBit.BitGet Initialization+getInitialization className = do+    location' <- if Set.member className Data.classesWithLocation+        then fmap Just Vector.getIntVector+        else pure Nothing+    rotation' <- if Set.member className Data.classesWithRotation+        then fmap Just Vector.getInt8Vector+        else pure Nothing+    pure Initialization { location = location', rotation = rotation' }+++-- | Puts the 'Initialization'. Note that unlike 'getInitialization', this does+-- not require the class name.+putInitialization :: Initialization -> BinaryBit.BitPut ()+putInitialization initialization = do+    case location initialization of+        Nothing -> pure ()+        Just x -> Vector.putIntVector x+    case rotation initialization of+        Nothing -> pure ()+        Just x -> Vector.putInt8Vector x
library/Octane/Type/Int32.hs view
@@ -25,7 +25,9 @@     { unpack :: Int.Int32     } deriving (Eq, Generics.Generic, Num, Ord) --- | >>> Binary.decode "\x01\x00\x00\x00" :: Int32+-- | Little-endian.+--+-- >>> Binary.decode "\x01\x00\x00\x00" :: Int32 -- 1 -- -- >>> Binary.encode (1 :: Int32)@@ -39,7 +41,9 @@         let value = unpack int32         Binary.putInt32le value --- | >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00" :: Int32+-- | Little-endian with the bits in each byte reversed.+--+-- >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00" :: Int32 -- 1 -- -- >>> Binary.runPut (BinaryBit.runBitPut (BinaryBit.putBits 0 (1 :: Int32)))@@ -64,7 +68,7 @@  -- | Shown as @1234@. ----- show (1 :: Int32)+-- >>> show (1 :: Int32) -- "1" instance Show Int32 where     show int32 = show (unpack int32)
library/Octane/Type/Int8.hs view
@@ -70,7 +70,7 @@  -- | Shown as @1234@. ----- show (1 :: Int8)+-- >>> show (1 :: Int8) -- "1" instance Show Int8 where     show int8 = show (unpack int8)
library/Octane/Type/RemoteId.hs view
@@ -32,6 +32,7 @@ -- >>> import qualified Data.Binary.Get as Binary -- >>> import qualified Data.Binary.Put as Binary + -- | A player's canonical remote ID. This is the best way to uniquely identify -- players data RemoteId
library/Octane/Type/ReplayWithFrames.hs view
@@ -8,7 +8,6 @@  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@@ -20,9 +19,9 @@ 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.Generator as Generator import qualified Octane.Utility.Parser as Parser  @@ -70,7 +69,7 @@         , properties = replayWithoutFrames & ReplayWithoutFrames.properties         , levels = replayWithoutFrames & ReplayWithoutFrames.levels         , keyFrames = replayWithoutFrames & ReplayWithoutFrames.keyFrames-        , frames = replayWithoutFrames & Parser.parseFrames+        , frames = replayWithoutFrames & Parser.parseStream         , messages = replayWithoutFrames & ReplayWithoutFrames.messages         , marks = replayWithoutFrames & ReplayWithoutFrames.marks         , packages = replayWithoutFrames & ReplayWithoutFrames.packages@@ -92,7 +91,12 @@         , ReplayWithoutFrames.properties = replayWithFrames & properties         , ReplayWithoutFrames.levels = replayWithFrames & levels         , ReplayWithoutFrames.keyFrames = replayWithFrames & keyFrames-        , ReplayWithoutFrames.stream = Stream.Stream LazyBytes.empty -- TODO+        , ReplayWithoutFrames.stream = Generator.generateStream+            (replayWithFrames & frames)+            (replayWithFrames & objects)+            (replayWithFrames & names)+            (replayWithFrames & classes)+            (replayWithFrames & cache)         , ReplayWithoutFrames.messages = replayWithFrames & messages         , ReplayWithoutFrames.marks = replayWithFrames & marks         , ReplayWithoutFrames.packages = replayWithFrames & packages
library/Octane/Type/Replication.hs view
@@ -8,6 +8,7 @@ import qualified Data.Map.Strict as Map import qualified Data.Text as StrictText import qualified GHC.Generics as Generics+import qualified Octane.Type.CompressedWord as CompressedWord import qualified Octane.Type.Initialization as Initialization import qualified Octane.Type.State as State import qualified Octane.Type.Value as Value@@ -18,7 +19,7 @@ -- This cannot be an instance of 'Data.Binary.Bits.BinaryBit' because it -- requires out-of-band information (the class property map) to decode. data Replication = Replication-    { actorId :: Word+    { actorId :: CompressedWord.CompressedWord     -- ^ The actor's ID.     , objectName :: StrictText.Text     -- ^ The name of the actor's object.
library/Octane/Type/Stream.hs view
@@ -43,8 +43,11 @@  -- | Doesn't show the actual bytes to avoid dumping tons of text. ----- >>> show (Stream "\xf0")+-- >>> show (Stream "\x00") -- "Stream {unpack = \"1 byte\"}"+--+-- >>> show (Stream "\x00\x00")+-- "Stream {unpack = \"2 bytes\"}" instance Show Stream where     show stream = do         let size = stream & unpack & LazyBytes.length
library/Octane/Type/Value.hs view
@@ -7,6 +7,7 @@ import qualified Control.DeepSeq as DeepSeq import qualified GHC.Generics as Generics import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.CompressedWord as CompressedWord import qualified Octane.Type.Float32 as Float32 import qualified Octane.Type.Int32 as Int32 import qualified Octane.Type.RemoteId as RemoteId@@ -65,7 +66,7 @@         Word32.Word32         (Maybe Word32.Word32)     | VLoadoutOnline-        [[(Word32.Word32, Int)]]+        [[(Word32.Word32, CompressedWord.CompressedWord)]]     | VLocation         (Vector.Vector Int)     | VMusicStinger@@ -88,7 +89,7 @@     | VRelativeRotation         (Vector.Vector Float)     | VReservation-        Int+        CompressedWord.CompressedWord         Word8.Word8         RemoteId.RemoteId         (Maybe Word8.Word8)
library/Octane/Type/Vector.hs view
@@ -2,11 +2,25 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE StrictData #-} -module Octane.Type.Vector (Vector(..)) where+module Octane.Type.Vector+    ( Vector(..)+    , getFloatVector+    , getInt8Vector+    , getIntVector+    , putInt8Vector+    , putIntVector+    ) where  import qualified Control.DeepSeq as DeepSeq import qualified Data.Aeson as Aeson+import qualified Data.Bits as Bits+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+import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.CompressedWord as CompressedWord+import qualified Octane.Type.Int8 as Int8   -- | Three values packed together. Although the fields are called @x@, @y@, and@@ -28,8 +42,76 @@ -- Aeson.encode (Vector 1 2 3 :: Vector Int) -- "[1,2,3]" instance (Aeson.ToJSON a) => Aeson.ToJSON (Vector a) where-    toJSON vector = Aeson.toJSON-        [ x vector-        , y vector-        , z vector-        ]+    toJSON vector = Aeson.toJSON [x vector, y vector, z vector]+++-- | Gets a 'Vector' full of 'Float's.+getFloatVector :: BinaryBit.BitGet (Vector Float)+getFloatVector = do+    let maxValue = 1+    let numBits = 16++    x' <- getFloat maxValue numBits+    y' <- getFloat maxValue numBits+    z' <- getFloat maxValue numBits++    pure Vector { x = x', y = y', z = z' }+++getFloat :: Int -> Int -> BinaryBit.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 <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits serIntMax)+    let unscaledValue = (delta :: Int) - bias+    if maxValue > maxBitValue+    then do+        let invScale = fromIntegral maxValue / fromIntegral maxBitValue+        pure (fromIntegral unscaledValue * invScale)+    else do+        let scale = fromIntegral maxBitValue / fromIntegral maxValue+        let invScale = 1.0 / scale+        pure (fromIntegral unscaledValue * invScale)+++-- | Gets a 'Vector' full of 'Int8's.+getInt8Vector :: BinaryBit.BitGet (Vector Int8.Int8)+getInt8Vector = do+    hasX <- BinaryBit.getBits 0+    x' <- if Boolean.unpack hasX then BinaryBit.getBits 0 else pure 0++    hasY <- BinaryBit.getBits 0+    y' <- if Boolean.unpack hasY then BinaryBit.getBits 0 else pure 0++    hasZ <- BinaryBit.getBits 0+    z' <- if Boolean.unpack hasZ then BinaryBit.getBits 0 else pure 0++    pure Vector { x = x' , y = y' , z = z' }+++-- | Gets a 'Vector' full of 'Int's.+getIntVector :: BinaryBit.BitGet (Vector Int)+getIntVector = do+    numBits <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits 19)+    let bias = Bits.shiftL 1 (numBits + 1)+    let maxBits = numBits + 2+    let maxValue = 2 ^ maxBits++    dx <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits maxValue)+    dy <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits maxValue)+    dz <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits maxValue)++    pure Vector { x = dx - bias , y = dy - bias , z = dz - bias }+++-- | Puts a 'Vector' full of 'Int8's.+putInt8Vector :: Vector Int8.Int8 -> BinaryBit.BitPut ()+putInt8Vector _ = do+    pure ()+++-- | Puts a 'Vector' full of 'Int's.+putIntVector :: Vector Int -> BinaryBit.BitPut ()+putIntVector _ = do+    pure ()
library/Octane/Type/Word16.hs view
@@ -21,7 +21,9 @@     { unpack :: Word.Word16     } deriving (Eq, Generics.Generic, Num, Ord) --- | >>> Binary.decode "\x01\x00" :: Word16+-- | Little-endian.+--+-- >>> Binary.decode "\x01\x00" :: Word16 -- 0x0001 -- -- >>> Binary.encode (1 :: Word16)
library/Octane/Type/Word32.hs view
@@ -30,7 +30,9 @@     { unpack :: Word.Word32     } deriving (Eq, Generics.Generic, Num, Ord) --- | >>> Binary.decode "\x01\x00\x00\x00" :: Word32+-- | Little-endian.+--+-- >>> Binary.decode "\x01\x00\x00\x00" :: Word32 -- 0x00000001 -- -- >>> Binary.encode (1 :: Word32)@@ -44,7 +46,9 @@         let value = unpack word32         Binary.putWord32le value --- | >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00" :: Word32+-- | Little-endian with the bits in each byte reversed.+--+-- >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00" :: Word32 -- 0x00000001 -- -- >>> Binary.runPut (BinaryBit.runBitPut (BinaryBit.putBits 0 (1 :: Word32)))
library/Octane/Type/Word64.hs view
@@ -30,7 +30,9 @@     { unpack :: Word.Word64     } deriving (Eq, Generics.Generic, Num, Ord) --- | >>> Binary.decode "\x01\x00\x00\x00\x00\x00\x00\x00" :: Word64+-- | Little-endian.+--+-- >>> Binary.decode "\x01\x00\x00\x00\x00\x00\x00\x00" :: Word64 -- 0x0000000000000001 -- -- >>> Binary.encode (1 :: Word64)@@ -44,7 +46,9 @@         let value = unpack word64         Binary.putWord64le value --- | >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00\x00\x00\x00\x00" :: Word64+-- | Little-endian with the bits in each byte reversed.+--+-- >>> Binary.runGet (BinaryBit.runBitGet (BinaryBit.getBits 0)) "\x80\x00\x00\x00\x00\x00\x00\x00" :: Word64 -- 0x0000000000000001 -- -- >>> Binary.runPut (BinaryBit.runBitPut (BinaryBit.putBits 0 (1 :: Word64)))
library/Octane/Utility.hs view
@@ -3,6 +3,7 @@     , module Octane.Utility.CRC     , module Octane.Utility.Embed     , module Octane.Utility.Endian+    , module Octane.Utility.Generator     , module Octane.Utility.Optimizer     , module Octane.Utility.Parser     ) where@@ -11,5 +12,6 @@ import Octane.Utility.CRC import Octane.Utility.Embed import Octane.Utility.Endian+import Octane.Utility.Generator import Octane.Utility.Optimizer import Octane.Utility.Parser
library/Octane/Utility/ClassPropertyMap.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PackageImports #-}+ -- | 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.@@ -22,7 +24,7 @@ import qualified Octane.Type.ReplayWithoutFrames as ReplayWithoutFrames import qualified Octane.Type.Text as Text import qualified Octane.Type.Word32 as Word32-import qualified Text.Regex as Regex+import qualified "regex-compat" Text.Regex as Regex   -- | The class property map is a map from class IDs in the stream to a map from
+ library/Octane/Utility/Generator.hs view
@@ -0,0 +1,91 @@+module Octane.Utility.Generator (generateStream) where++import Data.Function ((&))++import qualified Data.Binary.Bits as BinaryBit+import qualified Data.Binary.Bits.Put as BinaryBit+import qualified Data.Binary.Put as Binary+import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.CacheItem as CacheItem+import qualified Octane.Type.ClassItem as ClassItem+import qualified Octane.Type.Frame as Frame+import qualified Octane.Type.Initialization as Initialization+import qualified Octane.Type.List as List+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+++-- | Generates a network stream.+generateStream+    :: [Frame.Frame]+    -> List.List Text.Text+    -> List.List Text.Text+    -> List.List ClassItem.ClassItem+    -> List.List CacheItem.CacheItem+    -> Stream.Stream+generateStream frames _objects _names _classes _cache = do+    let bitPut = putFrames frames+    let bytePut = BinaryBit.runBitPut bitPut+    let bytes = Binary.runPut bytePut+    Stream.Stream bytes+++putFrames :: [Frame.Frame] -> BinaryBit.BitPut ()+putFrames frames = do+    case frames of+        [] -> pure ()+        frame : rest -> do+            putFrame frame+            putFrames rest+++putFrame :: Frame.Frame -> BinaryBit.BitPut ()+putFrame frame = do+    frame & Frame.time & BinaryBit.putBits 32+    frame & Frame.delta & BinaryBit.putBits 32+    frame & Frame.replications & putReplications+++putReplications :: [Replication.Replication] -> BinaryBit.BitPut ()+putReplications replications = do+    case replications of+        [] -> do+            False & Boolean.Boolean & BinaryBit.putBits 1+        replication : rest -> do+            True & Boolean.Boolean & BinaryBit.putBits 1+            putReplication replication+            putReplications rest+++putReplication :: Replication.Replication -> BinaryBit.BitPut ()+putReplication replication = do+    replication & Replication.actorId & BinaryBit.putBits 0+    case Replication.state replication of+        State.SOpening -> putNewReplication replication+        State.SExisting -> putExistingReplication replication+        State.SClosing -> putClosedReplication replication+++putNewReplication :: Replication.Replication -> BinaryBit.BitPut ()+putNewReplication replication = do+    True & Boolean.Boolean & BinaryBit.putBits 1 -- open+    True & Boolean.Boolean & BinaryBit.putBits 1 -- new+    False & Boolean.Boolean & BinaryBit.putBits 1 -- unknown+    -- TODO: convert object name into ID and put it+    case Replication.initialization replication of+        Nothing -> pure ()+        Just x -> Initialization.putInitialization x+++putExistingReplication :: Replication.Replication -> BinaryBit.BitPut ()+putExistingReplication _replication = do+    True & Boolean.Boolean & BinaryBit.putBits 1 -- open+    False & Boolean.Boolean & BinaryBit.putBits 1 -- existing+    -- TODO: put props+++putClosedReplication :: Replication.Replication -> BinaryBit.BitPut ()+putClosedReplication _replication = do+    False & Boolean.Boolean & BinaryBit.putBits 1 -- closed
library/Octane/Utility/Optimizer.hs view
@@ -8,6 +8,7 @@ import qualified Data.IntMap.Strict as IntMap import qualified Data.Map.Strict as Map import qualified Data.Text as StrictText+import qualified Octane.Type.CompressedWord as CompressedWord import qualified Octane.Type.Frame as Frame import qualified Octane.Type.Replication as Replication import qualified Octane.Type.State as State@@ -43,7 +44,7 @@             & Replication.state             & (== State.SOpening))         & map Replication.actorId-        & map fromIntegral+        & map CompressedWord.fromCompressedWord     state2 = spawned         & foldr             (IntMap.alter (\ maybeValue -> Just (case maybeValue of@@ -57,7 +58,7 @@             & Replication.state             & (== State.SClosing))         & map Replication.actorId-        & map fromIntegral+        & map CompressedWord.fromCompressedWord     state3 = destroyed         & foldr             (IntMap.alter (\ maybeValue -> Just (case maybeValue of@@ -82,7 +83,7 @@                             (Replication.properties replication)                             properties                         )))-                (replication & Replication.actorId & fromIntegral))+                (replication & Replication.actorId & CompressedWord.fromCompressedWord))             state3      in state4@@ -96,7 +97,7 @@         & reject (\ replication -> let             isOpening = Replication.state replication == State.SOpening             actorId = Replication.actorId replication-            currentState = IntMap.lookup (fromIntegral actorId) state+            currentState = IntMap.lookup (CompressedWord.fromCompressedWord actorId) state             isAlive = fmap fst currentState             wasAlreadyAlive = isAlive == Just True             in isOpening && wasAlreadyAlive)@@ -106,7 +107,7 @@             then let                 actorId = Replication.actorId replication                 currentState = IntMap.findWithDefault-                    (True, Map.empty) (fromIntegral actorId) state+                    (True, Map.empty) (CompressedWord.fromCompressedWord actorId) state                 currentProperties = snd currentState                 newProperties = Replication.properties replication                 changes = newProperties
library/Octane/Utility/Parser.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE StrictData #-} -module Octane.Utility.Parser (parseFrames) where+module Octane.Utility.Parser (parseStream) where  import Data.Function ((&)) @@ -11,7 +11,6 @@ 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.IntMap.Strict as IntMap import qualified Data.Map.Strict as Map import qualified Data.Set as Set@@ -20,12 +19,12 @@ import qualified GHC.Generics as Generics import qualified Octane.Data as Data import qualified Octane.Type.Boolean as Boolean+import qualified Octane.Type.CompressedWord as CompressedWord 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@@ -46,8 +45,8 @@   -- | Parses the network stream and returns a list of frames.-parseFrames :: ReplayWithoutFrames.ReplayWithoutFrames -> [Frame.Frame]-parseFrames replay = let+parseStream :: ReplayWithoutFrames.ReplayWithoutFrames -> [Frame.Frame]+parseStream replay = let     numFrames = replay         & ReplayWithoutFrames.properties         & Dictionary.unpack@@ -127,7 +126,7 @@  getReplication :: Context -> Bits.BitGet (Context, Replication.Replication) getReplication context = do-    actorId <- getActorId+    actorId <- BinaryBit.getBits maxActorId     isOpen <- getBool     let go =             if Boolean.unpack isOpen@@ -137,7 +136,7 @@   getOpenReplication :: Context-                   -> Int+                   -> CompressedWord.CompressedWord                    -> Bits.BitGet (Context, Replication.Replication) getOpenReplication context actorId = do     isNew <- getBool@@ -149,7 +148,7 @@   getNewReplication :: Context-                  -> Int+                  -> CompressedWord.CompressedWord                   -> Bits.BitGet (Context, Replication.Replication) getNewReplication context actorId = do     unknownFlag <- getBool@@ -160,10 +159,12 @@     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.classes (contextClassMap context) (Int32.fromInt32 objectId) of-        Nothing -> fail ("could not find class for object id " ++ show objectId)-        Just x -> pure x-    classInit <- getInitialization className+    (classId, className) <- CPM.getClass+        (contextObjectMap context)+        Data.classes+        (contextClassMap context)+        (Int32.fromInt32 objectId)+    classInit <- Initialization.getInitialization className     let thing = Thing             { thingFlag = unknownFlag             , thingObjectId = objectId@@ -173,12 +174,12 @@             , thingInitialization = classInit             }     let things = contextThings context-    let newThings = IntMap.insert actorId thing things+    let newThings = IntMap.insert (CompressedWord.fromCompressedWord actorId) thing things     let newContext = context { contextThings = newThings }     pure         ( newContext         , Replication.Replication-          { Replication.actorId = fromIntegral actorId+          { Replication.actorId = actorId           , Replication.objectName = objectName           , Replication.className = className           , Replication.state = State.SOpening@@ -188,15 +189,15 @@   getExistingReplication :: Context-                       -> Int+                       -> CompressedWord.CompressedWord                        -> Bits.BitGet (Context, Replication.Replication) getExistingReplication context actorId = do-    thing <- case context & contextThings & IntMap.lookup actorId of+    thing <- case context & contextThings & IntMap.lookup (CompressedWord.fromCompressedWord actorId) of         Nothing -> fail ("could not find thing for existing actor " ++ show actorId)         Just x -> pure x     props <- getProps context thing     pure (context, Replication.Replication-        { Replication.actorId = fromIntegral actorId+        { Replication.actorId = actorId         , Replication.objectName = thingObjectName thing         , Replication.className = thingClassName thing         , Replication.state = State.SExisting@@ -206,18 +207,18 @@   getClosedReplication :: Context-                     -> Int+                     -> CompressedWord.CompressedWord                      -> Bits.BitGet (Context, Replication.Replication) getClosedReplication context actorId = do-    thing <- case context & contextThings & IntMap.lookup actorId of+    thing <- case context & contextThings & IntMap.lookup (CompressedWord.fromCompressedWord actorId) of         Nothing -> fail ("could not find thing for closed actor " ++ show actorId)         Just x -> pure x-    let newThings = context & contextThings & IntMap.delete actorId+    let newThings = context & contextThings & IntMap.delete (CompressedWord.fromCompressedWord actorId)     let newContext = context { contextThings = newThings }     pure         ( newContext         , Replication.Replication-          { Replication.actorId = fromIntegral actorId+          { Replication.actorId = actorId           , Replication.objectName = thingObjectName thing           , Replication.className = thingClassName thing           , Replication.state = State.SClosing@@ -254,7 +255,7 @@         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+    pid <- fmap CompressedWord.fromCompressedWord (BinaryBit.getBits maxId)     name <- case props & IntMap.lookup pid of         Nothing -> fail ("could not find property name for property id " ++ show pid)         Just x -> pure x@@ -323,8 +324,8 @@     atk <- getWord32     vicFlag <- getBool     vic <- getWord32-    vec1 <- getVector-    vec2 <- getVector+    vec1 <- Vector.getIntVector+    vec2 <- Vector.getIntVector     pure (Value.VDemolish atkFlag atk vicFlag vic vec1 vec2)  @@ -343,7 +344,7 @@     a <- if Boolean.unpack noGoal         then pure Nothing         else fmap Just getInt32-    b <- getVector+    b <- Vector.getIntVector     pure (Value.VExplosion noGoal a b)  @@ -374,12 +375,12 @@  getLoadoutOnlineProperty :: Bits.BitGet Value.Value getLoadoutOnlineProperty = do-    size <- getWord8-    values <- Monad.replicateM (size & Word8.unpack & fromIntegral) (do-        innerSize <- getWord8-        Monad.replicateM (innerSize & Word8.unpack & fromIntegral) (do+    size <- fmap Word8.fromWord8 getWord8+    values <- Monad.replicateM size (do+        innerSize <- fmap Word8.fromWord8 getWord8+        Monad.replicateM innerSize (do             x <- getWord32-            y <- getInt 27+            y <- BinaryBit.getBits 27             pure (x, y)))     pure (Value.VLoadoutOnline values) @@ -394,17 +395,13 @@     antenna <- getWord32     topper <- getWord32     g <- getWord32-    h <- if version > 10-        then do-            value <- getWord32-            pure (Just value)-        else pure Nothing+    h <- if version > 10 then fmap Just getWord32 else pure Nothing     pure (Value.VLoadout version body decal wheels rocketTrail antenna topper g h)   getLocationProperty :: Bits.BitGet Value.Value getLocationProperty = do-    vector <- getVector+    vector <- Vector.getIntVector     pure (Value.VLocation vector)  @@ -445,7 +442,7 @@  getRelativeRotationProperty :: Bits.BitGet Value.Value getRelativeRotationProperty = do-    vector <- getFloatVector+    vector <- Vector.getFloatVector     pure (Value.VRelativeRotation vector)  getReservationProperty :: Context -> Bits.BitGet Value.Value@@ -453,11 +450,9 @@     -- 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+    number <- BinaryBit.getBits maxConnectionNumber     (systemId, remoteId, localId) <- getUniqueId-    playerName <- if systemId == 0 then pure Nothing else do-        string <- getText-        pure (Just string)+    playerName <- if systemId == 0 then pure Nothing else fmap Just getText     -- No idea what these two flags are. Might be for bots?     a <- getBool     b <- getBool@@ -473,21 +468,17 @@     pure (Value.VReservation number systemId remoteId localId playerName a b)  -neoTokyoVersion :: Version.Version-neoTokyoVersion = Version.makeVersion [868, 12]-- getRigidBodyStateProperty :: Bits.BitGet Value.Value getRigidBodyStateProperty = do     flag <- getBool-    position <- getVector-    rotation <- getFloatVector+    position <- Vector.getIntVector+    rotation <- Vector.getFloatVector     x <- if Boolean.unpack flag         then pure Nothing-        else fmap Just getVector+        else fmap Just Vector.getIntVector     y <- if Boolean.unpack flag         then pure Nothing-        else fmap Just getVector+        else fmap Just Vector.getIntVector     pure (Value.VRigidBodyState flag position rotation x y)  @@ -517,36 +508,24 @@ -- specially because it sometimes doesn't have the remote or local IDs. getPartyLeaderProperty :: Bits.BitGet Value.Value getPartyLeaderProperty = do-    systemId <- getSystemId+    systemId <- getWord8     (remoteId, localId) <- if systemId == 0         then pure (RemoteId.RemoteSplitscreenId (RemoteId.SplitscreenId Nothing), Nothing)         else do             remoteId <- getRemoteId systemId-            localId <- getLocalId+            localId <- fmap Just getWord8             pure (remoteId, localId)     pure (Value.VUniqueId systemId remoteId localId)  -getFloat32 :: Bits.BitGet Float32.Float32-getFloat32 = BinaryBit.getBits 0---getText :: Bits.BitGet Text.Text-getText = BinaryBit.getBits 0-- getUniqueId :: Bits.BitGet (Word8.Word8, RemoteId.RemoteId, Maybe Word8.Word8) getUniqueId = do-    systemId <- getSystemId+    systemId <- getWord8     remoteId <- getRemoteId systemId-    localId <- getLocalId+    localId <- fmap Just getWord8     pure (systemId, remoteId, localId)  -getSystemId :: Bits.BitGet Word8.Word8-getSystemId = getWord8-- getRemoteId :: Word8.Word8 -> Bits.BitGet RemoteId.RemoteId getRemoteId systemId = case systemId of     0 -> do@@ -564,8 +543,7 @@     _ -> fail ("unknown system id " ++ show systemId)  -getLocalId :: Bits.BitGet (Maybe Word8.Word8)-getLocalId = fmap Just getWord8+-- Data types   data Thing = Thing@@ -605,8 +583,7 @@   extractContext :: ReplayWithoutFrames.ReplayWithoutFrames -> Context-extractContext replay =-    Context+extractContext replay = Context     { contextObjectMap = CPM.getPropertyMap replay     , contextClassPropertyMap = CPM.getClassPropertyMap replay     , contextThings = IntMap.empty@@ -624,152 +601,47 @@     }  -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-    pure-        Vector.Vector-        { Vector.x = dx - bias-        , Vector.y = dy - bias-        , Vector.z = dz - bias-        }+-- Constants  -getVectorBytewise-    :: Bits.BitGet (Vector.Vector Int8.Int8)-getVectorBytewise = do-    hasX <- getBool-    x <- if Boolean.unpack hasX then getInt8 else pure 0-    hasY <- getBool-    y <- if Boolean.unpack hasY then getInt8 else pure 0-    hasZ <- getBool-    z <- if Boolean.unpack hasZ then getInt8 else pure 0-    pure-        Vector.Vector-        { Vector.x = x-        , Vector.y = y-        , Vector.z = z-        }+neoTokyoVersion :: Version.Version+neoTokyoVersion = Version.makeVersion [868, 12]  -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-    pure Vector.Vector { Vector.x = x, Vector.y = y, Vector.z = z }+maxActorId :: Int+maxActorId = 1024  -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-        pure (fromIntegral unscaledValue * invScale)-    else do-        let scale = fromIntegral maxBitValue / fromIntegral maxValue-        let invScale = 1.0 / scale-        pure (fromIntegral unscaledValue * invScale)+maxConnectionNumber :: Int+maxConnectionNumber = 7  -getInitialization :: StrictText.Text -> Bits.BitGet Initialization.Initialization-getInitialization className = do-    location <--        if Set.member className Data.classesWithLocation-            then do-                vector <- getVector-                pure (Just vector)-            else pure Nothing-    rotation <--        if Set.member className Data.classesWithRotation-            then do-                vector <- getVectorBytewise-                pure (Just vector)-            else pure Nothing-    pure-        Initialization.Initialization-        { Initialization.location = location-        , Initialization.rotation = rotation-        }+-- Type-restricted helpers.  -bitSize-    :: (Integral a)-    => a -> a-bitSize x = x & fromIntegral & logBase (2 :: Double) & ceiling+getBool :: Bits.BitGet Boolean.Boolean+getBool = BinaryBit.getBits 0  --- 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 pure value-    go 0 0+getFloat32 :: Bits.BitGet Float32.Float32+getFloat32 = BinaryBit.getBits 0   getInt32 :: Bits.BitGet Int32.Int32 getInt32 = BinaryBit.getBits 0  -getInt8 :: Bits.BitGet Int8.Int8-getInt8 = BinaryBit.getBits 0---getWord64 :: Bits.BitGet Word64.Word64-getWord64 = BinaryBit.getBits 0---getWord32 :: Bits.BitGet Word32.Word32-getWord32 = BinaryBit.getBits 0+getText :: Bits.BitGet Text.Text+getText = BinaryBit.getBits 0   getWord8 :: Bits.BitGet Word8.Word8 getWord8 = BinaryBit.getBits 0  -getActorId :: Bits.BitGet Int-getActorId = getInt 1024---getNumVectorBits :: Bits.BitGet Int-getNumVectorBits = getInt 19---getInt7 :: Bits.BitGet Int-getInt7 = getInt 7+getWord32 :: Bits.BitGet Word32.Word32+getWord32 = BinaryBit.getBits 0  -getBool :: Bits.BitGet Boolean.Boolean-getBool = BinaryBit.getBits 0+getWord64 :: Bits.BitGet Word64.Word64+getWord64 = BinaryBit.getBits 0
octane.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           octane-version:        0.13.0+version:        0.13.1 synopsis:       Parse Rocket League replays. description:    Octane parses Rocket League replays. category:       Game@@ -46,8 +46,8 @@     , data-binary-ieee754 ==0.4.*     , deepseq ==1.4.*     , file-embed ==0.0.*-    , http-client ==0.4.*-    , http-client-tls ==0.2.*+    , http-client >=0.4.30 && <0.6+    , http-client-tls >=0.2 && <0.4     , regex-compat ==0.95.*     , text ==1.2.*     , unordered-containers ==0.2.*@@ -61,6 +61,7 @@       Octane.Type.CacheItem       Octane.Type.CacheProperty       Octane.Type.ClassItem+      Octane.Type.CompressedWord       Octane.Type.Dictionary       Octane.Type.Float32       Octane.Type.Frame@@ -93,6 +94,7 @@       Octane.Utility.CRC       Octane.Utility.Embed       Octane.Utility.Endian+      Octane.Utility.Generator       Octane.Utility.Optimizer       Octane.Utility.Parser   default-language: Haskell2010
package.yaml view
@@ -43,8 +43,8 @@   - data-binary-ieee754 ==0.4.*   - deepseq ==1.4.*   - file-embed ==0.0.*-  - http-client ==0.4.*-  - http-client-tls ==0.2.*+  - http-client >=0.4.30 && <0.6+  - http-client-tls >=0.2 && <0.4   - regex-compat ==0.95.*   - text ==1.2.*   - unordered-containers ==0.2.*@@ -79,4 +79,4 @@     - -with-rtsopts=-N     main: Main.hs     source-dirs: test-suite-version: '0.13.0'+version: '0.13.1'
stack.yaml view
@@ -1,1 +1,1 @@-resolver: nightly-2016-06-04+resolver: nightly-2016-07-05