diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # Change log
 
-Octane uses [Semantic Versioning][]. The change log is available [on GitHub][].
+Octane uses [Semantic Versioning][].
+The change log is available through the [releases on GitHub][].
 
 [Semantic Versioning]: http://semver.org/spec/v2.0.0.html
-[on GitHub]: https://github.com/tfausak/octane/releases
+[releases on GitHub]: https://github.com/tfausak/octane/releases
diff --git a/LICENSE.md b/LICENSE.md
deleted file mode 100644
--- a/LICENSE.md
+++ /dev/null
@@ -1,23 +0,0 @@
-[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.
-
-[The MIT License (MIT)]: http://opensource.org/licenses/MIT
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # [Octane][]
 
-[![Version badge][]][package version]
-[![Build badge][]][build status]
+[![Version badge][]][version]
+[![Build badge][]][build]
 
 Octane parses [Rocket League][] replays.
 
@@ -9,14 +9,14 @@
 
 1.  Install [Stack][].
 
-2.  Install Octane with `stack build`.
+2.  `stack --resolver nightly install octane`
 
-3.  Parse a replay with `stack exec octane path/to/the.replay`.
+3.  `stack exec octane path/to.replay`
 
 [Octane]: https://github.com/tfausak/octane
 [Version badge]: https://www.stackage.org/package/octane/badge/nightly?label=version
-[package version]: https://www.stackage.org/package/octane
+[version]: https://www.stackage.org/package/octane
 [Build badge]: https://travis-ci.org/tfausak/octane.svg?branch=main
-[build status]: https://travis-ci.org/tfausak/octane
+[build]: https://travis-ci.org/tfausak/octane
 [Rocket League]: http://rocketleague.psyonix.com
 [Stack]: http://haskellstack.org
diff --git a/benchmark/Octane/Type/ActorBench.hs b/benchmark/Octane/Type/ActorBench.hs
--- a/benchmark/Octane/Type/ActorBench.hs
+++ b/benchmark/Octane/Type/ActorBench.hs
@@ -15,7 +15,7 @@
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (Actor
         (PCString "")
-        (Int32LE 0)))
+        (Word32LE 0)))
     ]
 
 decodeActor :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Actor)
diff --git a/benchmark/Octane/Type/CacheItemBench.hs b/benchmark/Octane/Type/CacheItemBench.hs
--- a/benchmark/Octane/Type/CacheItemBench.hs
+++ b/benchmark/Octane/Type/CacheItemBench.hs
@@ -16,9 +16,9 @@
         \\0\0\0\0\
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (CacheItem
-        (Int32LE 0)
-        (Int32LE 0)
-        (Int32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
         (List [])))
     ]
 
diff --git a/benchmark/Octane/Type/CachePropertyBench.hs b/benchmark/Octane/Type/CachePropertyBench.hs
--- a/benchmark/Octane/Type/CachePropertyBench.hs
+++ b/benchmark/Octane/Type/CachePropertyBench.hs
@@ -14,8 +14,8 @@
         \\0\0\0\0\
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (CacheProperty
-        (Int32LE 0) 
-        (Int32LE 0)))
+        (Word32LE 0)
+        (Word32LE 0)))
     ]
 
 decodeCacheProperty :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, CacheProperty)
diff --git a/benchmark/Octane/Type/FrameBench.hs b/benchmark/Octane/Type/FrameBench.hs
--- a/benchmark/Octane/Type/FrameBench.hs
+++ b/benchmark/Octane/Type/FrameBench.hs
@@ -3,8 +3,11 @@
 module Octane.Type.FrameBench (benchmarks) where
 
 import Criterion
-import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits as Bits
+import qualified Data.Binary.Bits.Get as Bits
+import qualified Data.Binary.Bits.Put as Bits
 import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
 import qualified Data.ByteString.Lazy as BSL
 import Octane
 
@@ -12,11 +15,16 @@
 benchmarks = bgroup "Frame"
     [ bench "decode basic" (nf decodeFrame "\
         \\0\0\0\0\
-        \\0\0\0\0")
-    , bench "encode basic" (nf Binary.encode (Frame
+        \\0\0\0\0\
+        \\0")
+    , bench "encode basic" (nf encodeFrame (Frame
         (Float32LE 0.0)
-        (Float32LE 0.0)))
+        (Float32LE 0.0)
+        []))
     ]
 
 decodeFrame :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Frame)
-decodeFrame = Binary.decodeOrFail
+decodeFrame bytes = Binary.runGetOrFail (Bits.runBitGet (Bits.getBits undefined)) bytes
+
+encodeFrame :: Frame -> BSL.ByteString
+encodeFrame frame = Binary.runPut (Bits.runBitPut (Bits.putBits undefined frame))
diff --git a/benchmark/Octane/Type/KeyFrameBench.hs b/benchmark/Octane/Type/KeyFrameBench.hs
--- a/benchmark/Octane/Type/KeyFrameBench.hs
+++ b/benchmark/Octane/Type/KeyFrameBench.hs
@@ -16,8 +16,8 @@
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (KeyFrame
         (Float32LE 0.0)
-        (Int32LE 0)
-        (Int32LE 0)))
+        (Word32LE 0)
+        (Word32LE 0)))
     ]
 
 decodeKeyFrame :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, KeyFrame)
diff --git a/benchmark/Octane/Type/MarkBench.hs b/benchmark/Octane/Type/MarkBench.hs
--- a/benchmark/Octane/Type/MarkBench.hs
+++ b/benchmark/Octane/Type/MarkBench.hs
@@ -15,7 +15,7 @@
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (Mark
         (PCString "")
-        (Int32LE 0)))
+        (Word32LE 0)))
     ]
 
 decodeMark :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Mark)
diff --git a/benchmark/Octane/Type/MessageBench.hs b/benchmark/Octane/Type/MessageBench.hs
--- a/benchmark/Octane/Type/MessageBench.hs
+++ b/benchmark/Octane/Type/MessageBench.hs
@@ -15,7 +15,7 @@
         \\1\0\0\0\0\
         \\1\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (Message
-        (Int32LE 0)
+        (Word32LE 0)
         (PCString "")
         (PCString "")))
     ]
diff --git a/benchmark/Octane/Type/Primitive/Int32LEBench.hs b/benchmark/Octane/Type/Primitive/Int32LEBench.hs
deleted file mode 100644
--- a/benchmark/Octane/Type/Primitive/Int32LEBench.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Type.Primitive.Int32LEBench (benchmarks) where
-
-import Criterion
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import Octane
-
-benchmarks :: Benchmark
-benchmarks = bgroup "Int32LE"
-    [ bench "decode basic" (nf decodeInt32LE "\0\0\0\0")
-    , bench "encode basic" (nf Binary.encode (Int32LE 0))
-    ]
-
-decodeInt32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int32LE)
-decodeInt32LE = Binary.decodeOrFail
diff --git a/benchmark/Octane/Type/Primitive/Int64LEBench.hs b/benchmark/Octane/Type/Primitive/Int64LEBench.hs
deleted file mode 100644
--- a/benchmark/Octane/Type/Primitive/Int64LEBench.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Type.Primitive.Int64LEBench (benchmarks) where
-
-import Criterion
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import Octane
-
-benchmarks :: Benchmark
-benchmarks = bgroup "Int64LE"
-    [ bench "decode basic" (nf decodeInt64LE "\0\0\0\0")
-    , bench "encode basic" (nf Binary.encode (Int64LE 0))
-    ]
-
-decodeInt64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int64LE)
-decodeInt64LE = Binary.decodeOrFail
diff --git a/benchmark/Octane/Type/Primitive/Word32LEBench.hs b/benchmark/Octane/Type/Primitive/Word32LEBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Octane/Type/Primitive/Word32LEBench.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Word32LEBench (benchmarks) where
+
+import Criterion
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import Octane
+
+benchmarks :: Benchmark
+benchmarks = bgroup "Word32LE"
+    [ bench "decode basic" (nf decodeWord32LE "\0\0\0\0")
+    , bench "encode basic" (nf Binary.encode (Word32LE 0))
+    ]
+
+decodeWord32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Word32LE)
+decodeWord32LE = Binary.decodeOrFail
diff --git a/benchmark/Octane/Type/Primitive/Word64LEBench.hs b/benchmark/Octane/Type/Primitive/Word64LEBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Octane/Type/Primitive/Word64LEBench.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Word64LEBench (benchmarks) where
+
+import Criterion
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import Octane
+
+benchmarks :: Benchmark
+benchmarks = bgroup "Word64LE"
+    [ bench "decode basic" (nf decodeWord64LE "\0\0\0\0")
+    , bench "encode basic" (nf Binary.encode (Word64LE 0))
+    ]
+
+decodeWord64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Word64LE)
+decodeWord64LE = Binary.decodeOrFail
diff --git a/benchmark/Octane/Type/PrimitiveBench.hs b/benchmark/Octane/Type/PrimitiveBench.hs
--- a/benchmark/Octane/Type/PrimitiveBench.hs
+++ b/benchmark/Octane/Type/PrimitiveBench.hs
@@ -4,20 +4,20 @@
 import qualified Octane.Type.Primitive.BooleanBench
 import qualified Octane.Type.Primitive.DictionaryBench
 import qualified Octane.Type.Primitive.Float32LEBench
-import qualified Octane.Type.Primitive.Int32LEBench
-import qualified Octane.Type.Primitive.Int64LEBench
 import qualified Octane.Type.Primitive.ListBench
 import qualified Octane.Type.Primitive.PCStringBench
 import qualified Octane.Type.Primitive.StreamBench
+import qualified Octane.Type.Primitive.Word32LEBench
+import qualified Octane.Type.Primitive.Word64LEBench
 
 benchmarks :: Benchmark
 benchmarks = bgroup "Primitive"
     [ Octane.Type.Primitive.BooleanBench.benchmarks
     , Octane.Type.Primitive.DictionaryBench.benchmarks
     , Octane.Type.Primitive.Float32LEBench.benchmarks
-    , Octane.Type.Primitive.Int32LEBench.benchmarks
-    , Octane.Type.Primitive.Int64LEBench.benchmarks
     , Octane.Type.Primitive.ListBench.benchmarks
     , Octane.Type.Primitive.PCStringBench.benchmarks
     , Octane.Type.Primitive.StreamBench.benchmarks
+    , Octane.Type.Primitive.Word32LEBench.benchmarks
+    , Octane.Type.Primitive.Word64LEBench.benchmarks
     ]
diff --git a/benchmark/Octane/Type/PropertyBench.hs b/benchmark/Octane/Type/PropertyBench.hs
--- a/benchmark/Octane/Type/PropertyBench.hs
+++ b/benchmark/Octane/Type/PropertyBench.hs
@@ -16,7 +16,7 @@
             \\0\0\0\0\0\0\0\0\
             \\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (ArrayProperty
-            (Int64LE 0)
+            (Word64LE 0)
             (List [])))
         ]
     , bgroup "Bool"
@@ -25,7 +25,7 @@
             \\0\0\0\0\0\0\0\0\
             \\0")
         , bench "encode basic" (nf Binary.encode (BoolProperty
-            (Int64LE 0)
+            (Word64LE 0)
             (Boolean False)))
         ]
     , bgroup "Byte"
@@ -35,7 +35,7 @@
             \\1\0\0\0\0\
             \\1\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (ByteProperty
-            (Int64LE 0)
+            (Word64LE 0)
             (PCString "", PCString "")))
         ]
     , bgroup "Float"
@@ -44,7 +44,7 @@
             \\4\0\0\0\0\0\0\0\
             \\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (FloatProperty
-            (Int64LE 4)
+            (Word64LE 4)
             (Float32LE 0.0)))
         ]
     , bgroup "Int"
@@ -53,8 +53,8 @@
             \\4\0\0\0\0\0\0\0\
             \\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (IntProperty
-            (Int64LE 4)
-            (Int32LE 0)))
+            (Word64LE 4)
+            (Word32LE 0)))
         ]
     , bgroup "Name"
         [ bench "decode basic" (nf decodeProperty "\
@@ -62,7 +62,7 @@
             \\0\0\0\0\0\0\0\0\
             \\1\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (NameProperty
-            (Int64LE 0)
+            (Word64LE 0)
             (PCString "")))
         ]
     , bgroup "QWord"
@@ -71,8 +71,8 @@
             \\8\0\0\0\0\0\0\0\
             \\0\0\0\0\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (QWordProperty
-            (Int64LE 8)
-            (Int64LE 0)))
+            (Word64LE 8)
+            (Word64LE 0)))
         ]
     , bgroup "String"
         [ bench "decode basic" (nf decodeProperty "\
@@ -80,7 +80,7 @@
             \\0\0\0\0\0\0\0\0\
             \\1\0\0\0\0")
         , bench "encode basic" (nf Binary.encode (StrProperty
-            (Int64LE 0)
+            (Word64LE 0)
             (PCString "")))
         ]
     ]
diff --git a/benchmark/Octane/Type/ReplayBench.hs b/benchmark/Octane/Type/ReplayBench.hs
--- a/benchmark/Octane/Type/ReplayBench.hs
+++ b/benchmark/Octane/Type/ReplayBench.hs
@@ -31,14 +31,14 @@
         \\0\0\0\0\
         \\0\0\0\0")
     , bench "encode basic" (nf Binary.encode (Replay
-        (Int32LE 0)
-        (Int32LE 0)
-        (Int32LE 0)
-        (Int32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
         (PCString "")
         (Dictionary Map.empty)
-        (Int32LE 0)
-        (Int32LE 0)
+        (Word32LE 0)
+        (Word32LE 0)
         (List [])
         (List [])
         (Stream "")
diff --git a/library/Octane/Core.hs b/library/Octane/Core.hs
--- a/library/Octane/Core.hs
+++ b/library/Octane/Core.hs
@@ -1,37 +1,27 @@
 module Octane.Core
     ( module Control.DeepSeq
-    , module Control.Monad
     , module Data.Binary
-    , module Data.Binary.Get
-    , module Data.Binary.IEEE754
-    , module Data.Binary.Put
+    , module Data.Binary.Bits
+    , module Data.Binary.Bits.Get
+    , module Data.Binary.Bits.Put
     , module Data.ByteString
-    , module Data.Char
     , module Data.Function
-    , module Data.Int
     , module Data.IntMap
     , module Data.Map
     , module Data.Text
-    , module Data.Text.Encoding
+    , module Data.Word
     , module GHC.Generics
-    , module System.Environment
-    , module System.IO
     ) where
 
 import Control.DeepSeq (NFData)
-import Control.Monad (replicateM, when)
-import Data.Binary (Binary, Get, Put, decodeFileOrFail, encode, get, getWord8, put, putWord8)
-import Data.Binary.Get (ByteOffset, getByteString, getWord32le, getWord64le)
-import Data.Binary.IEEE754 (getFloat32le, putFloat32le)
-import Data.Binary.Put (putByteString, putWord32le, putWord64le)
+import Data.Binary (Binary, Get, Put, get, put)
+import Data.Binary.Bits (BinaryBit, getBits, putBits)
+import Data.Binary.Bits.Get (BitGet)
+import Data.Binary.Bits.Put (BitPut)
 import Data.ByteString (ByteString)
-import Data.Char (isLatin1)
 import Data.Function ((&))
-import Data.Int (Int32, Int64)
 import Data.IntMap (IntMap)
 import Data.Map (Map)
 import Data.Text (Text)
-import Data.Text.Encoding (decodeLatin1, decodeUtf16LE, encodeUtf16LE)
+import Data.Word (Word32, Word64)
 import GHC.Generics (Generic)
-import System.Environment (getArgs)
-import System.IO (hPutStrLn, stderr, stdout)
diff --git a/library/Octane/Main.hs b/library/Octane/Main.hs
--- a/library/Octane/Main.hs
+++ b/library/Octane/Main.hs
@@ -1,24 +1,29 @@
 module Octane.Main (main) where
 
+import qualified Control.Monad as Monad
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BSL
 import Octane.Core
 import Octane.Type
+import qualified System.Environment as Environment
+import qualified System.IO as IO
 
 main :: IO ()
 main = do
-    files <- getArgs
+    files <- Environment.getArgs
     contents <- mapM BS.readFile files
-    results <- mapM decodeFileOrFail files
+    results <- mapM Binary.decodeFileOrFail files
     mapM_ debug (zip3 files contents results)
 
-debug :: (String, ByteString, Either (ByteOffset, String) Replay) -> IO ()
+debug :: (String, ByteString, Either (Binary.ByteOffset, String) Replay) -> IO ()
 debug (file, contents, result) = case result of
-    Left (offset, message) -> hPutStrLn stderr
+    Left (offset, message) -> IO.hPutStrLn IO.stderr
         (file ++ " @ byte " ++ show offset ++ " - " ++ message)
     Right replay -> do
         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 ++ ")!")
+        let outputSize = replay & Binary.encode & BSL.length
+        Monad.when (inputSize /= outputSize) $ do
+            IO.hPutStrLn IO.stderr ("input size (" ++ show inputSize ++ ") not equal to output size (" ++ show outputSize ++ ")!")
         print replay
diff --git a/library/Octane/Type.hs b/library/Octane/Type.hs
--- a/library/Octane/Type.hs
+++ b/library/Octane/Type.hs
@@ -9,6 +9,7 @@
     , module Octane.Type.Primitive
     , module Octane.Type.Property
     , module Octane.Type.Replay
+    , module Octane.Type.Replication
     ) where
 
 import Octane.Type.Actor
@@ -21,3 +22,4 @@
 import Octane.Type.Primitive
 import Octane.Type.Property
 import Octane.Type.Replay
+import Octane.Type.Replication
diff --git a/library/Octane/Type/Actor.hs b/library/Octane/Type/Actor.hs
--- a/library/Octane/Type/Actor.hs
+++ b/library/Octane/Type/Actor.hs
@@ -5,11 +5,11 @@
 
 import Octane.Core
 import Octane.Type.Primitive.PCString
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 data Actor = Actor
     { actorName :: PCString
-    , actorTag :: Int32LE
+    , actorTag :: Word32LE
     } deriving (Eq, Generic, NFData, Show)
 
 instance Binary Actor where
diff --git a/library/Octane/Type/CacheItem.hs b/library/Octane/Type/CacheItem.hs
--- a/library/Octane/Type/CacheItem.hs
+++ b/library/Octane/Type/CacheItem.hs
@@ -5,13 +5,13 @@
 
 import Octane.Core
 import Octane.Type.CacheProperty
-import Octane.Type.Primitive.Int32LE
 import Octane.Type.Primitive.List
+import Octane.Type.Primitive.Word32LE
 
 data CacheItem = CacheItem
-    { cacheItemTag :: Int32LE
-    , cacheItemStart :: Int32LE
-    , cacheItemEnd :: Int32LE
+    { cacheItemTag :: Word32LE
+    , cacheItemStart :: Word32LE
+    , cacheItemEnd :: Word32LE
     , cacheItemCacheProperties :: List CacheProperty
     } deriving (Eq, Generic, NFData, Show)
 
diff --git a/library/Octane/Type/CacheProperty.hs b/library/Octane/Type/CacheProperty.hs
--- a/library/Octane/Type/CacheProperty.hs
+++ b/library/Octane/Type/CacheProperty.hs
@@ -4,11 +4,11 @@
 module Octane.Type.CacheProperty (CacheProperty(..)) where
 
 import Octane.Core
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 data CacheProperty = CacheProperty
-    { cachePropertyIndex :: Int32LE
-    , cachePropertyTag :: Int32LE
+    { cachePropertyIndex :: Word32LE
+    , cachePropertyTag :: Word32LE
     } deriving (Eq, Generic, NFData, Show)
 
 instance Binary CacheProperty where
diff --git a/library/Octane/Type/Frame.hs b/library/Octane/Type/Frame.hs
--- a/library/Octane/Type/Frame.hs
+++ b/library/Octane/Type/Frame.hs
@@ -3,23 +3,55 @@
 
 module Octane.Type.Frame (Frame(..)) where
 
+import qualified Data.Binary.Bits.Get as Bits
+import qualified Data.Binary.Bits.Put as Bits
+import qualified Data.Word as Word
 import Octane.Core
 import Octane.Type.Primitive.Float32LE
+import Octane.Type.Replication
 
 data Frame = Frame
     { frameTime :: Float32LE
     , frameDelta :: Float32LE
+    , frameReplications :: [Replication]
     } deriving (Eq, Generic, NFData, Show)
 
-instance Binary Frame where
-    get = do
-        time <- get
-        delta <- get
+instance BinaryBit Frame where
+    getBits _ = do
+        time <- Bits.getWord32be 32
+        delta <- Bits.getWord32be 32
+        replications <- getReplications
         return Frame
-            { frameTime = time
-            , frameDelta = delta
+            { frameTime = wordToFloat time
+            , frameDelta = wordToFloat delta
+            , frameReplications = replications
             }
 
-    put frame = do
-        frame & frameTime & put
-        frame & frameDelta & put
+    putBits _ frame = do
+        frame & frameTime & floatToWord & Bits.putWord32be 32
+        frame & frameDelta & floatToWord & Bits.putWord32be 32
+        frame & frameReplications & putReplications
+
+wordToFloat :: Word.Word32 -> Float32LE
+wordToFloat _ = Float32LE 0 -- TODO
+
+floatToWord :: Float32LE -> Word.Word32
+floatToWord _ = 0 -- TODO
+
+getReplications :: BitGet [Replication]
+getReplications = do
+    hasReplication <- Bits.getBool
+    if hasReplication
+    then do
+        replication <- getBits undefined
+        replications <- getReplications
+        return (replication : replications)
+    else return []
+
+putReplications :: [Replication] -> BitPut ()
+putReplications replications = case replications of
+    [] -> Bits.putBool False
+    replication : rest -> do
+        Bits.putBool True
+        putBits undefined replication
+        putReplications rest
diff --git a/library/Octane/Type/KeyFrame.hs b/library/Octane/Type/KeyFrame.hs
--- a/library/Octane/Type/KeyFrame.hs
+++ b/library/Octane/Type/KeyFrame.hs
@@ -5,15 +5,15 @@
 
 import Octane.Core
 import Octane.Type.Primitive.Float32LE
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 -- | A key frame. Each key frame has the time since the beginning of the match,
 -- | the frame it corresponds to, and that frame's bit position in the network
 -- | stream.
 data KeyFrame = KeyFrame
     { keyFrameTime :: Float32LE
-    , keyFrameFrame :: Int32LE
-    , keyFramePosition :: Int32LE
+    , keyFrameFrame :: Word32LE
+    , keyFramePosition :: Word32LE
     } deriving (Eq, Generic, NFData, Show)
 
 instance Binary KeyFrame where
diff --git a/library/Octane/Type/Mark.hs b/library/Octane/Type/Mark.hs
--- a/library/Octane/Type/Mark.hs
+++ b/library/Octane/Type/Mark.hs
@@ -5,13 +5,13 @@
 
 import Octane.Core
 import Octane.Type.Primitive.PCString
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 -- | A tick mark on the replay. The only thing that creates tick marks are
 -- | goals.
 data Mark = Mark
     { markLabel :: PCString
-    , markFrame :: Int32LE
+    , markFrame :: Word32LE
     } deriving (Eq, Generic, NFData, Show)
 
 instance Binary Mark where
diff --git a/library/Octane/Type/Message.hs b/library/Octane/Type/Message.hs
--- a/library/Octane/Type/Message.hs
+++ b/library/Octane/Type/Message.hs
@@ -5,11 +5,11 @@
 
 import Octane.Core
 import Octane.Type.Primitive.PCString
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 -- | A debugging message. Replays do not have any of these anymore.
 data Message = Message
-    { messageFrame :: Int32LE
+    { messageFrame :: Word32LE
     , messageName :: PCString
     , messageContent :: PCString
     } deriving (Eq, Generic, NFData, Show)
diff --git a/library/Octane/Type/Primitive.hs b/library/Octane/Type/Primitive.hs
--- a/library/Octane/Type/Primitive.hs
+++ b/library/Octane/Type/Primitive.hs
@@ -1,19 +1,19 @@
 module Octane.Type.Primitive
     ( module Octane.Type.Primitive.Boolean
+    , module Octane.Type.Primitive.Dictionary
     , module Octane.Type.Primitive.Float32LE
-    , module Octane.Type.Primitive.Int32LE
-    , module Octane.Type.Primitive.Int64LE
     , module Octane.Type.Primitive.List
     , module Octane.Type.Primitive.PCString
-    , module Octane.Type.Primitive.Dictionary
     , module Octane.Type.Primitive.Stream
+    , module Octane.Type.Primitive.Word32LE
+    , module Octane.Type.Primitive.Word64LE
     ) where
 
 import Octane.Type.Primitive.Boolean
+import Octane.Type.Primitive.Dictionary
 import Octane.Type.Primitive.Float32LE
-import Octane.Type.Primitive.Int32LE
-import Octane.Type.Primitive.Int64LE
 import Octane.Type.Primitive.List
 import Octane.Type.Primitive.PCString
-import Octane.Type.Primitive.Dictionary
 import Octane.Type.Primitive.Stream
+import Octane.Type.Primitive.Word32LE
+import Octane.Type.Primitive.Word64LE
diff --git a/library/Octane/Type/Primitive/Boolean.hs b/library/Octane/Type/Primitive/Boolean.hs
--- a/library/Octane/Type/Primitive/Boolean.hs
+++ b/library/Octane/Type/Primitive/Boolean.hs
@@ -3,6 +3,7 @@
 
 module Octane.Type.Primitive.Boolean (Boolean(..)) where
 
+import qualified Data.Binary as Binary
 import Octane.Core
 
 -- | A boolean value, stored in the first bit of a byte.
@@ -12,10 +13,10 @@
 
 instance Binary Boolean where
     get = do
-        boolean <- getWord8
+        boolean <- Binary.getWord8
         if boolean > 1
         then fail ("invalid Boolean value " ++ show boolean)
         else boolean & fromIntegral & toEnum & Boolean & return
 
     put (Boolean boolean) = do
-        boolean & fromEnum & fromIntegral & putWord8
+        boolean & fromEnum & fromIntegral & Binary.putWord8
diff --git a/library/Octane/Type/Primitive/Float32LE.hs b/library/Octane/Type/Primitive/Float32LE.hs
--- a/library/Octane/Type/Primitive/Float32LE.hs
+++ b/library/Octane/Type/Primitive/Float32LE.hs
@@ -3,6 +3,7 @@
 
 module Octane.Type.Primitive.Float32LE (Float32LE(..)) where
 
+import qualified Data.Binary.IEEE754 as IEEE754
 import Octane.Core
 
 -- | A 32-bit little-endian float.
@@ -12,8 +13,8 @@
 
 instance Binary Float32LE where
     get = do
-        float <- getFloat32le
+        float <- IEEE754.getFloat32le
         float & Float32LE & return
 
     put (Float32LE float) = do
-        float & putFloat32le
+        float & IEEE754.putFloat32le
diff --git a/library/Octane/Type/Primitive/Int32LE.hs b/library/Octane/Type/Primitive/Int32LE.hs
deleted file mode 100644
--- a/library/Octane/Type/Primitive/Int32LE.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module Octane.Type.Primitive.Int32LE (Int32LE(..)) where
-
-import Octane.Core
-
--- | A 32-bit little-endian integer.
-newtype Int32LE = Int32LE
-    { getInt32LE :: Int32
-    } deriving (Eq, Generic, NFData, Show)
-
-instance Binary Int32LE where
-    get = do
-        int <- getWord32le
-        int & fromIntegral & Int32LE & return
-
-    put (Int32LE int) = do
-        int & fromIntegral & putWord32le
diff --git a/library/Octane/Type/Primitive/Int64LE.hs b/library/Octane/Type/Primitive/Int64LE.hs
deleted file mode 100644
--- a/library/Octane/Type/Primitive/Int64LE.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module Octane.Type.Primitive.Int64LE (Int64LE(..)) where
-
-import Octane.Core
-
--- | A 64-bit little-endian integer.
-newtype Int64LE = Int64LE
-    { getInt64LE :: Int64
-    } deriving (Eq, Generic, NFData, Show)
-
-instance Binary Int64LE where
-    get = do
-        int <- getWord64le
-        int & fromIntegral & Int64LE & return
-
-    put (Int64LE int) = do
-        int & fromIntegral & putWord64le
diff --git a/library/Octane/Type/Primitive/List.hs b/library/Octane/Type/Primitive/List.hs
--- a/library/Octane/Type/Primitive/List.hs
+++ b/library/Octane/Type/Primitive/List.hs
@@ -3,8 +3,9 @@
 
 module Octane.Type.Primitive.List (List(..)) where
 
+import qualified Control.Monad as Monad
 import Octane.Core
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 -- | A length-prefixed list.
 newtype List a = List
@@ -13,10 +14,10 @@
 
 instance (Binary a) => Binary (List a) where
     get = do
-        (Int32LE size) <- get
-        elements <- replicateM (fromIntegral size) get
+        (Word32LE size) <- get
+        elements <- Monad.replicateM (fromIntegral size) get
         elements & List & return
 
     put (List list) = do
-        list & length & fromIntegral & Int32LE & put
+        list & length & fromIntegral & Word32LE & put
         list & mapM_ put
diff --git a/library/Octane/Type/Primitive/PCString.hs b/library/Octane/Type/Primitive/PCString.hs
--- a/library/Octane/Type/Primitive/PCString.hs
+++ b/library/Octane/Type/Primitive/PCString.hs
@@ -3,10 +3,14 @@
 
 module Octane.Type.Primitive.PCString (PCString(..)) where
 
+import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
 import qualified Data.ByteString.Char8 as BS8
+import qualified Data.Char as Char
 import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Encoding
 import Octane.Core
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 -- | A length-prefixed null-terminated string.
 newtype PCString = PCString
@@ -15,29 +19,29 @@
 
 instance Binary PCString where
     get = do
-        (Int32LE size) <- get
+        (Word32LE size) <- get
         string <- if size == 0
             then fail ("invalid PCString size " ++ show size)
             else if size < 0
             then do
                 let actualSize = 2 * negate size
-                bytes <- getByteString (fromIntegral actualSize)
-                bytes & decodeUtf16LE & return
+                bytes <- Binary.getByteString (fromIntegral actualSize)
+                bytes & Encoding.decodeUtf16LE & return
             else do
-                bytes <- getByteString (fromIntegral size)
-                bytes & decodeLatin1 & return
+                bytes <- Binary.getByteString (fromIntegral size)
+                bytes & Encoding.decodeLatin1 & return
         string & Text.dropEnd 1 & PCString & return
 
     put (PCString string) = do
         let cString = Text.snoc string '\NUL'
         let size = cString & Text.length & fromIntegral
-        if Text.all isLatin1 cString
+        if Text.all Char.isLatin1 cString
         then do
-            size & Int32LE & put
-            cString & encodeLatin1 & putByteString
+            size & Word32LE & put
+            cString & encodeLatin1 & Binary.putByteString
         else do
-            size & negate & Int32LE & put
-            cString & encodeUtf16LE & putByteString
+            size & negate & Word32LE & put
+            cString & Encoding.encodeUtf16LE & Binary.putByteString
 
 encodeLatin1 :: Text -> ByteString
 encodeLatin1 text = text & Text.unpack & BS8.pack
diff --git a/library/Octane/Type/Primitive/Stream.hs b/library/Octane/Type/Primitive/Stream.hs
--- a/library/Octane/Type/Primitive/Stream.hs
+++ b/library/Octane/Type/Primitive/Stream.hs
@@ -3,9 +3,13 @@
 
 module Octane.Type.Primitive.Stream (Stream(..)) where
 
+import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
+import qualified Data.Bits as Bits
 import qualified Data.ByteString as BS
+import qualified Data.Word as Word
 import Octane.Core
-import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.Word32LE
 
 newtype Stream = Stream
     { getStream :: ByteString
@@ -14,11 +18,22 @@
 instance Binary Stream where
     get = do
         size <- get
-        content <- size & getInt32LE & fromIntegral & getByteString
-        content & Stream & return
+        content <- size & getWord32LE & fromIntegral & Binary.getByteString
+        content & BS.map reverseBits & Stream & return
 
     put stream = do
         let content = getStream stream
         let size = BS.length content
-        size & fromIntegral & Int32LE & put
-        content & putByteString
+        size & fromIntegral & Word32LE & put
+        content & BS.map reverseBits & Binary.putByteString
+
+reverseBits :: Word.Word8 -> Word.Word8
+reverseBits word =
+    (if Bits.testBit word 0 then 128 else 0) Bits..|.
+    (if Bits.testBit word 1 then 64 else 0) Bits..|.
+    (if Bits.testBit word 2 then 32 else 0) Bits..|.
+    (if Bits.testBit word 3 then 16 else 0) Bits..|.
+    (if Bits.testBit word 4 then 8 else 0) Bits..|.
+    (if Bits.testBit word 5 then 4 else 0) Bits..|.
+    (if Bits.testBit word 6 then 2 else 0) Bits..|.
+    (if Bits.testBit word 7 then 1 else 0)
diff --git a/library/Octane/Type/Primitive/Word32LE.hs b/library/Octane/Type/Primitive/Word32LE.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Word32LE.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Octane.Type.Primitive.Word32LE (Word32LE(..)) where
+
+import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
+import Octane.Core
+
+-- | A 32-bit little-endian integer.
+newtype Word32LE = Word32LE
+    { getWord32LE :: Word32
+    } deriving (Eq, Generic, NFData, Show)
+
+instance Binary Word32LE where
+    get = do
+        int <- Binary.getWord32le
+        int & fromIntegral & Word32LE & return
+
+    put (Word32LE int) = do
+        int & fromIntegral & Binary.putWord32le
diff --git a/library/Octane/Type/Primitive/Word64LE.hs b/library/Octane/Type/Primitive/Word64LE.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Word64LE.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Octane.Type.Primitive.Word64LE (Word64LE(..)) where
+
+import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
+import Octane.Core
+
+-- | A 64-bit little-endian integer.
+newtype Word64LE = Word64LE
+    { getWord64LE :: Word64
+    } deriving (Eq, Generic, NFData, Show)
+
+instance Binary Word64LE where
+    get = do
+        int <- Binary.getWord64le
+        int & fromIntegral & Word64LE & return
+
+    put (Word64LE int) = do
+        int & fromIntegral & Binary.putWord64le
diff --git a/library/Octane/Type/Property.hs b/library/Octane/Type/Property.hs
--- a/library/Octane/Type/Property.hs
+++ b/library/Octane/Type/Property.hs
@@ -5,23 +5,23 @@
 module Octane.Type.Property (Property(..)) where
 
 import Octane.Core
-import Octane.Type.Primitive.PCString
 import Octane.Type.Primitive.Boolean
+import Octane.Type.Primitive.Dictionary
 import Octane.Type.Primitive.Float32LE
-import Octane.Type.Primitive.Int32LE
-import Octane.Type.Primitive.Int64LE
 import Octane.Type.Primitive.List
-import Octane.Type.Primitive.Dictionary
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Word32LE
+import Octane.Type.Primitive.Word64LE
 
 data Property
-    = ArrayProperty Int64LE (List (Dictionary Property))
-    | BoolProperty Int64LE Boolean
-    | ByteProperty Int64LE (PCString, PCString)
-    | FloatProperty Int64LE Float32LE
-    | IntProperty Int64LE Int32LE
-    | NameProperty Int64LE PCString
-    | QWordProperty Int64LE Int64LE
-    | StrProperty Int64LE PCString
+    = ArrayProperty Word64LE (List (Dictionary Property))
+    | BoolProperty Word64LE Boolean
+    | ByteProperty Word64LE (PCString, PCString)
+    | FloatProperty Word64LE Float32LE
+    | IntProperty Word64LE Word32LE
+    | NameProperty Word64LE PCString
+    | QWordProperty Word64LE Word64LE
+    | StrProperty Word64LE PCString
     deriving (Eq, Generic, NFData, Show)
 
 instance Binary Property where
@@ -43,13 +43,13 @@
                 ByteProperty size (key, value) & return
             "FloatProperty" -> do
                 size <- get
-                value <- case getInt64LE size of
+                value <- case getWord64LE size of
                     4 -> get
                     x -> fail ("unknown FloatProperty size " ++ show x)
                 FloatProperty size value & return
             "IntProperty" -> do
                 size <- get
-                value <- case getInt64LE size of
+                value <- case getWord64LE size of
                     4 -> get
                     x -> fail ("unknown IntProperty size " ++ show x)
                 IntProperty size value & return
@@ -59,7 +59,7 @@
                 NameProperty size value & return
             "QWordProperty" -> do
                 size <- get
-                value <- case getInt64LE size of
+                value <- case getWord64LE size of
                     8 -> get
                     x -> fail ("unknown QWordProperty size " ++ show x)
                 QWordProperty size value & return
diff --git a/library/Octane/Type/Replay.hs b/library/Octane/Type/Replay.hs
--- a/library/Octane/Type/Replay.hs
+++ b/library/Octane/Type/Replay.hs
@@ -9,22 +9,22 @@
 import Octane.Type.KeyFrame
 import Octane.Type.Mark
 import Octane.Type.Message
-import Octane.Type.Primitive.PCString
-import Octane.Type.Primitive.Int32LE
-import Octane.Type.Primitive.List
-import Octane.Type.Property
 import Octane.Type.Primitive.Dictionary
+import Octane.Type.Primitive.List
+import Octane.Type.Primitive.PCString
 import Octane.Type.Primitive.Stream
+import Octane.Type.Primitive.Word32LE
+import Octane.Type.Property
 
 data Replay = Replay
-    { replaySize1 :: Int32LE
-    , replayCRC1 :: Int32LE
-    , replayVersion1 :: Int32LE
-    , replayVersion2 :: Int32LE
+    { replaySize1 :: Word32LE
+    , replayCRC1 :: Word32LE
+    , replayVersion1 :: Word32LE
+    , replayVersion2 :: Word32LE
     , replayLabel :: PCString
     , replayProperties :: Dictionary Property
-    , replaySize2 :: Int32LE
-    , replayCRC2 :: Int32LE
+    , replaySize2 :: Word32LE
+    , replayCRC2 :: Word32LE
     , replayEffects :: List PCString
     , replayKeyFrames :: List KeyFrame
     , replayStream :: Stream
diff --git a/library/Octane/Type/Replication.hs b/library/Octane/Type/Replication.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Replication.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Octane.Type.Replication (Replication(..)) where
+
+import Octane.Core
+
+data Replication = Replication
+    {
+    } deriving (Eq, Generic, NFData, Show)
+
+instance BinaryBit Replication where
+    getBits _ = do
+        return Replication
+            {
+            }
+
+    putBits _ _ = do
+        return ()
diff --git a/octane.cabal b/octane.cabal
--- a/octane.cabal
+++ b/octane.cabal
@@ -1,139 +1,152 @@
-name: octane
-version: 0.4.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
+-- This file has been generated from package.yaml by hpack version 0.9.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           octane
+version:        0.4.1
+synopsis:       Parse Rocket League replays.
+description:    Octane parses Rocket League replays.
+category:       Game
+homepage:       https://github.com/tfausak/octane#readme
+bug-reports:    https://github.com/tfausak/octane/issues
+maintainer:     Taylor Fausak
+license:        MIT
+build-type:     Simple
+cabal-version:  >= 1.10
+
 extra-source-files:
     CHANGELOG.md
+    package.yaml
     README.md
     stack.yaml
 
 source-repository head
-    type: git
-    location: https://github.com/tfausak/octane
+  type: git
+  location: https://github.com/tfausak/octane
 
 library
-    exposed-modules:
-        Octane
-        Octane.Main
-        Octane.Type
-        Octane.Type.Actor
-        Octane.Type.CacheItem
-        Octane.Type.CacheProperty
-        Octane.Type.Frame
-        Octane.Type.KeyFrame
-        Octane.Type.Mark
-        Octane.Type.Message
-        Octane.Type.Primitive
-        Octane.Type.Primitive.Boolean
-        Octane.Type.Primitive.Float32LE
-        Octane.Type.Primitive.Int32LE
-        Octane.Type.Primitive.Int64LE
-        Octane.Type.Primitive.List
-        Octane.Type.Primitive.PCString
-        Octane.Type.Primitive.Dictionary
-        Octane.Type.Property
-        Octane.Type.Replay
-        Octane.Type.Primitive.Stream
-    build-depends:
-        base ==4.8.*,
-        binary ==0.7.*,
-        bytestring ==0.10.*,
-        containers ==0.5.*,
-        data-binary-ieee754 ==0.4.*,
-        deepseq ==1.4.*,
-        text ==1.2.*
-    default-language: Haskell2010
-    hs-source-dirs: library
-    other-modules:
-        Octane.Core
-    ghc-options: -Wall
+  hs-source-dirs:
+      library
+  ghc-options: -Wall
+  build-depends:
+      base ==4.8.*
+    , binary ==0.7.*
+    , binary-bits ==0.5.*
+    , bytestring ==0.10.*
+    , containers ==0.5.*
+    , data-binary-ieee754 ==0.4.*
+    , deepseq ==1.4.*
+    , text ==1.2.*
+  exposed-modules:
+      Octane
+      Octane.Main
+      Octane.Type
+      Octane.Type.Actor
+      Octane.Type.CacheItem
+      Octane.Type.CacheProperty
+      Octane.Type.Frame
+      Octane.Type.KeyFrame
+      Octane.Type.Mark
+      Octane.Type.Message
+      Octane.Type.Primitive
+      Octane.Type.Primitive.Boolean
+      Octane.Type.Primitive.Dictionary
+      Octane.Type.Primitive.Float32LE
+      Octane.Type.Primitive.List
+      Octane.Type.Primitive.PCString
+      Octane.Type.Primitive.Stream
+      Octane.Type.Primitive.Word32LE
+      Octane.Type.Primitive.Word64LE
+      Octane.Type.Property
+      Octane.Type.Replay
+      Octane.Type.Replication
+  other-modules:
+      Octane.Core
+  default-language: Haskell2010
 
 executable octane
-    main-is: Executable.hs
-    build-depends:
-        base -any,
-        octane -any
-    default-language: Haskell2010
-    hs-source-dirs: executable
-    ghc-options: -threaded -Wall
+  main-is: Executable.hs
+  hs-source-dirs:
+      executable
+  ghc-options: -Wall
+  build-depends:
+      base
+    , octane
+  default-language: Haskell2010
 
 test-suite octane-test-suite
-    type: exitcode-stdio-1.0
-    main-is: TestSuite.hs
-    build-depends:
-        base -any,
-        binary -any,
-        bytestring -any,
-        containers -any,
-        octane -any,
-        tasty ==0.11.*,
-        tasty-hspec ==1.1.*
-    default-language: Haskell2010
-    hs-source-dirs: test-suite
-    other-modules:
-        OctaneSpec
-        Octane.MainSpec
-        Octane.TypeSpec
-        Octane.Type.ActorSpec
-        Octane.Type.CacheItemSpec
-        Octane.Type.CachePropertySpec
-        Octane.Type.FrameSpec
-        Octane.Type.KeyFrameSpec
-        Octane.Type.MarkSpec
-        Octane.Type.MessageSpec
-        Octane.Type.PrimitiveSpec
-        Octane.Type.Primitive.BooleanSpec
-        Octane.Type.Primitive.Float32LESpec
-        Octane.Type.Primitive.Int32LESpec
-        Octane.Type.Primitive.Int64LESpec
-        Octane.Type.Primitive.ListSpec
-        Octane.Type.Primitive.PCStringSpec
-        Octane.Type.Primitive.DictionarySpec
-        Octane.Type.PropertySpec
-        Octane.Type.ReplaySpec
-        Octane.Type.Primitive.StreamSpec
-    ghc-options: -threaded -Wall
+  type: exitcode-stdio-1.0
+  main-is: TestSuite.hs
+  hs-source-dirs:
+      test-suite
+  ghc-options: -Wall -threaded
+  build-depends:
+      base
+    , binary
+    , binary-bits
+    , bytestring
+    , containers
+    , octane
+    , tasty ==0.11.*
+    , tasty-hspec ==1.1.*
+  other-modules:
+      Octane.MainSpec
+      Octane.Type.ActorSpec
+      Octane.Type.CacheItemSpec
+      Octane.Type.CachePropertySpec
+      Octane.Type.FrameSpec
+      Octane.Type.KeyFrameSpec
+      Octane.Type.MarkSpec
+      Octane.Type.MessageSpec
+      Octane.Type.Primitive.BooleanSpec
+      Octane.Type.Primitive.DictionarySpec
+      Octane.Type.Primitive.Float32LESpec
+      Octane.Type.Primitive.ListSpec
+      Octane.Type.Primitive.PCStringSpec
+      Octane.Type.Primitive.StreamSpec
+      Octane.Type.Primitive.Word32LESpec
+      Octane.Type.Primitive.Word64LESpec
+      Octane.Type.PrimitiveSpec
+      Octane.Type.PropertySpec
+      Octane.Type.ReplaySpec
+      Octane.TypeSpec
+      OctaneSpec
+  default-language: Haskell2010
 
 benchmark octane-benchmark
-    type: exitcode-stdio-1.0
-    main-is: Benchmark.hs
-    build-depends:
-        base -any,
-        binary -any,
-        bytestring -any,
-        containers -any,
-        octane -any,
-        criterion ==1.1.*
-    default-language: Haskell2010
-    hs-source-dirs: benchmark
-    other-modules:
-        OctaneBench
-        Octane.MainBench
-        Octane.TypeBench
-        Octane.Type.ActorBench
-        Octane.Type.CacheItemBench
-        Octane.Type.CachePropertyBench
-        Octane.Type.FrameBench
-        Octane.Type.KeyFrameBench
-        Octane.Type.MarkBench
-        Octane.Type.MessageBench
-        Octane.Type.PrimitiveBench
-        Octane.Type.Primitive.BooleanBench
-        Octane.Type.Primitive.DictionaryBench
-        Octane.Type.Primitive.Float32LEBench
-        Octane.Type.Primitive.Int32LEBench
-        Octane.Type.Primitive.Int64LEBench
-        Octane.Type.Primitive.ListBench
-        Octane.Type.Primitive.PCStringBench
-        Octane.Type.PropertyBench
-        Octane.Type.ReplayBench
-        Octane.Type.Primitive.StreamBench
-    ghc-options: -threaded -Wall
+  type: exitcode-stdio-1.0
+  main-is: Benchmark.hs
+  hs-source-dirs:
+      benchmark
+  ghc-options: -Wall -threaded
+  build-depends:
+      base
+    , binary
+    , binary-bits
+    , bytestring
+    , containers
+    , criterion ==1.1.*
+    , octane
+  other-modules:
+      Octane.MainBench
+      Octane.Type.ActorBench
+      Octane.Type.CacheItemBench
+      Octane.Type.CachePropertyBench
+      Octane.Type.FrameBench
+      Octane.Type.KeyFrameBench
+      Octane.Type.MarkBench
+      Octane.Type.MessageBench
+      Octane.Type.Primitive.BooleanBench
+      Octane.Type.Primitive.DictionaryBench
+      Octane.Type.Primitive.Float32LEBench
+      Octane.Type.Primitive.ListBench
+      Octane.Type.Primitive.PCStringBench
+      Octane.Type.Primitive.StreamBench
+      Octane.Type.Primitive.Word32LEBench
+      Octane.Type.Primitive.Word64LEBench
+      Octane.Type.PrimitiveBench
+      Octane.Type.PropertyBench
+      Octane.Type.ReplayBench
+      Octane.TypeBench
+      OctaneBench
+  default-language: Haskell2010
diff --git a/package.yaml b/package.yaml
new file mode 100644
--- /dev/null
+++ b/package.yaml
@@ -0,0 +1,66 @@
+name: octane
+version: 0.4.1
+
+category: Game
+description: Octane parses Rocket League replays.
+extra-source-files:
+- CHANGELOG.md
+- package.yaml
+- README.md
+- stack.yaml
+ghc-options: -Wall
+github: tfausak/octane
+license: MIT
+maintainer: Taylor Fausak
+synopsis: Parse Rocket League replays.
+
+library:
+  dependencies:
+  - base ==4.8.*
+  - binary ==0.7.*
+  - binary-bits ==0.5.*
+  - bytestring ==0.10.*
+  - containers ==0.5.*
+  - data-binary-ieee754 ==0.4.*
+  - deepseq ==1.4.*
+  - text ==1.2.*
+  other-modules:
+  - Octane.Core
+  source-dirs: library
+
+executables:
+  octane:
+    dependencies:
+    - base
+    - octane
+    main: Executable.hs
+    source-dirs: executable
+
+tests:
+  octane-test-suite:
+    dependencies:
+    - base
+    - binary
+    - binary-bits
+    - bytestring
+    - containers
+    - octane
+    - tasty ==0.11.*
+    - tasty-hspec ==1.1.*
+    ghc-options: -threaded
+    main: TestSuite.hs
+    source-dirs: test-suite
+
+benchmarks:
+  octane-benchmark:
+    dependencies:
+    - base
+    - binary
+    - binary-bits
+    - bytestring
+    - containers
+    - criterion ==1.1.*
+    - octane
+    ghc-options: -threaded
+    main: Benchmark.hs
+    source-dirs: benchmark
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,4 +1,5 @@
+extra-deps:
+- binary-bits-0.5
 install-ghc: true
-packages: [.]
-require-stack-version: '>=1.0'
-resolver: nightly-2016-02-01
+require-stack-version: '>=1.0.4'
+resolver: nightly-2016-02-20
diff --git a/test-suite/Octane/Type/ActorSpec.hs b/test-suite/Octane/Type/ActorSpec.hs
--- a/test-suite/Octane/Type/ActorSpec.hs
+++ b/test-suite/Octane/Type/ActorSpec.hs
@@ -17,26 +17,26 @@
                 \\0\0\0\0")
             (Right ("", 9, Actor
                 (PCString "")
-                (Int32LE 0)))
+                (Word32LE 0)))
         shouldBe
             (decodeActor "\
                 \\2\0\0\0a\0\
                 \\2\0\0\0")
             (Right ("", 10, Actor
                 (PCString "a")
-                (Int32LE 2)))
+                (Word32LE 2)))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (Actor
                 (PCString "")
-                (Int32LE 0)))
+                (Word32LE 0)))
             "\
                 \\1\0\0\0\0\
                 \\0\0\0\0"
         shouldBe
             (Binary.encode (Actor
                 (PCString "a")
-                (Int32LE 2)))
+                (Word32LE 2)))
             "\
                 \\2\0\0\0a\0\
                 \\2\0\0\0"
diff --git a/test-suite/Octane/Type/CacheItemSpec.hs b/test-suite/Octane/Type/CacheItemSpec.hs
--- a/test-suite/Octane/Type/CacheItemSpec.hs
+++ b/test-suite/Octane/Type/CacheItemSpec.hs
@@ -18,9 +18,9 @@
                 \\0\0\0\0\
                 \\0\0\0\0")
             (Right ("", 16, CacheItem
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (List [])))
         shouldBe
             (decodeCacheItem "\
@@ -30,16 +30,16 @@
                 \\1\0\0\0\
                 \\4\0\0\0\5\0\0\0")
             (Right ("", 24, CacheItem
-                (Int32LE 1)
-                (Int32LE 2)
-                (Int32LE 3)
-                (List [CacheProperty (Int32LE 4) (Int32LE 5)])))
+                (Word32LE 1)
+                (Word32LE 2)
+                (Word32LE 3)
+                (List [CacheProperty (Word32LE 4) (Word32LE 5)])))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (CacheItem
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (List [])))
             "\
                 \\0\0\0\0\
@@ -48,10 +48,10 @@
                 \\0\0\0\0"
         shouldBe
             (Binary.encode (CacheItem
-                (Int32LE 1)
-                (Int32LE 2)
-                (Int32LE 3)
-                (List [CacheProperty (Int32LE 4) (Int32LE 5)])))
+                (Word32LE 1)
+                (Word32LE 2)
+                (Word32LE 3)
+                (List [CacheProperty (Word32LE 4) (Word32LE 5)])))
             "\
                 \\1\0\0\0\
                 \\2\0\0\0\
diff --git a/test-suite/Octane/Type/CachePropertySpec.hs b/test-suite/Octane/Type/CachePropertySpec.hs
--- a/test-suite/Octane/Type/CachePropertySpec.hs
+++ b/test-suite/Octane/Type/CachePropertySpec.hs
@@ -16,27 +16,27 @@
                 \\0\0\0\0\
                 \\0\0\0\0")
             (Right ("", 8, CacheProperty
-                (Int32LE 0)
-                (Int32LE 0)))
+                (Word32LE 0)
+                (Word32LE 0)))
         shouldBe
             (decodeCacheProperty "\
                 \\1\0\0\0\
                 \\2\0\0\0")
             (Right ("", 8, CacheProperty
-                (Int32LE 1)
-                (Int32LE 2)))
+                (Word32LE 1)
+                (Word32LE 2)))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (CacheProperty
-                (Int32LE 0)
-                (Int32LE 0)))
+                (Word32LE 0)
+                (Word32LE 0)))
             "\
                 \\0\0\0\0\
                 \\0\0\0\0"
         shouldBe
             (Binary.encode (CacheProperty
-                (Int32LE 1)
-                (Int32LE 2)))
+                (Word32LE 1)
+                (Word32LE 2)))
             "\
                 \\1\0\0\0\
                 \\2\0\0\0"
diff --git a/test-suite/Octane/Type/FrameSpec.hs b/test-suite/Octane/Type/FrameSpec.hs
--- a/test-suite/Octane/Type/FrameSpec.hs
+++ b/test-suite/Octane/Type/FrameSpec.hs
@@ -2,8 +2,11 @@
 
 module Octane.Type.FrameSpec (spec) where
 
-import qualified Data.Binary as Binary
+import qualified Data.Binary.Bits as Bits
+import qualified Data.Binary.Bits.Get as Bits
+import qualified Data.Binary.Bits.Put as Bits
 import qualified Data.Binary.Get as Binary
+import qualified Data.Binary.Put as Binary
 import qualified Data.ByteString.Lazy as BSL
 import Octane
 import Test.Tasty.Hspec
@@ -14,18 +17,25 @@
         shouldBe
             (decodeFrame "\
                 \\0\0\0\0\
-                \\0\0\0\0")
-            (Right ("", 8, Frame
+                \\0\0\0\0\
+                \\0")
+            (Right ("", 9, Frame
                 (Float32LE 0.0)
-                (Float32LE 0.0)))
+                (Float32LE 0.0)
+                []))
     it "can be encoded" $ do
         shouldBe
-            (Binary.encode (Frame
+            (encodeFrame (Frame
                 (Float32LE 0.0)
-                (Float32LE 0.0)))
+                (Float32LE 0.0)
+                []))
             "\
                 \\0\0\0\0\
-                \\0\0\0\0"
+                \\0\0\0\0\
+                \\0"
 
 decodeFrame :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Frame)
-decodeFrame = Binary.decodeOrFail
+decodeFrame bytes = Binary.runGetOrFail (Bits.runBitGet (Bits.getBits undefined)) bytes
+
+encodeFrame :: Frame -> BSL.ByteString
+encodeFrame frame = Binary.runPut (Bits.runBitPut (Bits.putBits undefined frame))
diff --git a/test-suite/Octane/Type/KeyFrameSpec.hs b/test-suite/Octane/Type/KeyFrameSpec.hs
--- a/test-suite/Octane/Type/KeyFrameSpec.hs
+++ b/test-suite/Octane/Type/KeyFrameSpec.hs
@@ -18,8 +18,8 @@
                 \\0\0\0\0")
             (Right ("", 12, KeyFrame
                 (Float32LE 0.0)
-                (Int32LE 0)
-                (Int32LE 0)))
+                (Word32LE 0)
+                (Word32LE 0)))
         shouldBe
             (decodeKeyFrame "\
                 \\0\0\128\63\
@@ -27,14 +27,14 @@
                 \\3\0\0\0")
             (Right ("", 12, KeyFrame
                 (Float32LE 1.0)
-                (Int32LE 2)
-                (Int32LE 3)))
+                (Word32LE 2)
+                (Word32LE 3)))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (KeyFrame
                 (Float32LE 0.0)
-                (Int32LE 0)
-                (Int32LE 0)))
+                (Word32LE 0)
+                (Word32LE 0)))
             "\
                 \\0\0\0\0\
                 \\0\0\0\0\
@@ -42,8 +42,8 @@
         shouldBe
             (Binary.encode (KeyFrame
                 (Float32LE 1.0)
-                (Int32LE 2)
-                (Int32LE 3)))
+                (Word32LE 2)
+                (Word32LE 3)))
             "\
                 \\0\0\128\63\
                 \\2\0\0\0\
diff --git a/test-suite/Octane/Type/MarkSpec.hs b/test-suite/Octane/Type/MarkSpec.hs
--- a/test-suite/Octane/Type/MarkSpec.hs
+++ b/test-suite/Octane/Type/MarkSpec.hs
@@ -17,26 +17,26 @@
                 \\0\0\0\0")
             (Right ("", 9, Mark
                 (PCString "")
-                (Int32LE 0)))
+                (Word32LE 0)))
         shouldBe
             (decodeMark "\
                 \\2\0\0\0a\0\
                 \\1\0\0\0")
             (Right ("", 10, Mark
                 (PCString "a")
-                (Int32LE 1)))
+                (Word32LE 1)))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (Mark
                 (PCString "")
-                (Int32LE 0)))
+                (Word32LE 0)))
             "\
                 \\1\0\0\0\0\
                 \\0\0\0\0"
         shouldBe
             (Binary.encode (Mark
                 (PCString "a")
-                (Int32LE 1)))
+                (Word32LE 1)))
             "\
                 \\2\0\0\0a\0\
                 \\1\0\0\0"
diff --git a/test-suite/Octane/Type/MessageSpec.hs b/test-suite/Octane/Type/MessageSpec.hs
--- a/test-suite/Octane/Type/MessageSpec.hs
+++ b/test-suite/Octane/Type/MessageSpec.hs
@@ -17,7 +17,7 @@
                 \\1\0\0\0\0\
                 \\1\0\0\0\0")
             (Right ("", 14, Message
-                (Int32LE 0)
+                (Word32LE 0)
                 (PCString "")
                 (PCString "")))
         shouldBe
@@ -26,18 +26,18 @@
                 \\2\0\0\0a\0\
                 \\2\0\0\0b\0")
             (Right ("", 16, Message
-                (Int32LE 1)
+                (Word32LE 1)
                 (PCString "a")
                 (PCString "b")))
     it "can be encoded" $ do
         shouldBe
-            (Binary.encode (Message (Int32LE 0) (PCString "") (PCString "")))
+            (Binary.encode (Message (Word32LE 0) (PCString "") (PCString "")))
             "\
                 \\0\0\0\0\
                 \\1\0\0\0\0\
                 \\1\0\0\0\0"
         shouldBe
-            (Binary.encode (Message (Int32LE 1) (PCString "a") (PCString "b")))
+            (Binary.encode (Message (Word32LE 1) (PCString "a") (PCString "b")))
             "\
                 \\1\0\0\0\
                 \\2\0\0\0a\0\
diff --git a/test-suite/Octane/Type/Primitive/Int32LESpec.hs b/test-suite/Octane/Type/Primitive/Int32LESpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Type/Primitive/Int32LESpec.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Type.Primitive.Int32LESpec (spec) where
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import Octane
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Int32LE" $ do
-    it "can be decoded" $ do
-        shouldBe
-            (decodeInt32LE "\0\0\0\0")
-            (Right ("", 4, Int32LE 0))
-        shouldBe
-            (decodeInt32LE "\1\0\0\0")
-            (Right ("", 4, Int32LE 1))
-    it "can be encoded" $ do
-        shouldBe
-            (Binary.encode (Int32LE 0))
-            "\0\0\0\0"
-        shouldBe
-            (Binary.encode (Int32LE 1))
-            "\1\0\0\0"
-
-decodeInt32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int32LE)
-decodeInt32LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/Int64LESpec.hs b/test-suite/Octane/Type/Primitive/Int64LESpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Type/Primitive/Int64LESpec.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Type.Primitive.Int64LESpec (spec) where
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import Octane
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Int64LE" $ do
-    it "can be decoded" $ do
-        shouldBe
-            (decodeInt64LE "\0\0\0\0\0\0\0\0")
-            (Right ("", 8, Int64LE 0))
-        shouldBe
-            (decodeInt64LE "\1\0\0\0\0\0\0\0")
-            (Right ("", 8, Int64LE 1))
-    it "can be encoded" $ do
-        shouldBe
-            (Binary.encode (Int64LE 0))
-            "\0\0\0\0\0\0\0\0"
-        shouldBe
-            (Binary.encode (Int64LE 1))
-            "\1\0\0\0\0\0\0\0"
-
-decodeInt64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Int64LE)
-decodeInt64LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/ListSpec.hs b/test-suite/Octane/Type/Primitive/ListSpec.hs
--- a/test-suite/Octane/Type/Primitive/ListSpec.hs
+++ b/test-suite/Octane/Type/Primitive/ListSpec.hs
@@ -16,7 +16,7 @@
             (Right ("", 4, List [] :: List Boolean))
         shouldBe
             (decodeList "\1\0\0\0\0\0\0\0")
-            (Right ("", 8, List [Int32LE 0]))
+            (Right ("", 8, List [Word32LE 0]))
         shouldBe
             (decodeList "\2\0\0\0\0\1")
             (Right ("", 6, List [Boolean False, Boolean True]))
@@ -25,7 +25,7 @@
             (Binary.encode (List [] :: List Boolean))
             "\0\0\0\0"
         shouldBe
-            (Binary.encode (List [Int32LE 0]))
+            (Binary.encode (List [Word32LE 0]))
             "\1\0\0\0\0\0\0\0"
         shouldBe
             (Binary.encode (List [Boolean False, Boolean True]))
diff --git a/test-suite/Octane/Type/Primitive/StreamSpec.hs b/test-suite/Octane/Type/Primitive/StreamSpec.hs
--- a/test-suite/Octane/Type/Primitive/StreamSpec.hs
+++ b/test-suite/Octane/Type/Primitive/StreamSpec.hs
@@ -14,10 +14,18 @@
         shouldBe
             (decodeStream "\0\0\0\0")
             (Right ("", 4, Stream ""))
+    it "reverses the bits within each byte when decoding" $ do
+        shouldBe
+            (decodeStream "\2\0\0\0\128\64")
+            (Right ("", 6, Stream "\1\2"))
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (Stream ""))
             "\0\0\0\0"
+    it "reverses the bits within each byte when encoding" $ do
+        shouldBe
+            (Binary.encode (Stream "\1\2"))
+            "\2\0\0\0\128\64"
 
 decodeStream :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Stream)
 decodeStream = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/Word32LESpec.hs b/test-suite/Octane/Type/Primitive/Word32LESpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/Word32LESpec.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Word32LESpec (spec) where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Word32LE" $ do
+    it "can be decoded" $ do
+        shouldBe
+            (decodeWord32LE "\0\0\0\0")
+            (Right ("", 4, Word32LE 0))
+        shouldBe
+            (decodeWord32LE "\1\0\0\0")
+            (Right ("", 4, Word32LE 1))
+    it "can be encoded" $ do
+        shouldBe
+            (Binary.encode (Word32LE 0))
+            "\0\0\0\0"
+        shouldBe
+            (Binary.encode (Word32LE 1))
+            "\1\0\0\0"
+
+decodeWord32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Word32LE)
+decodeWord32LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/Word64LESpec.hs b/test-suite/Octane/Type/Primitive/Word64LESpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/Word64LESpec.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Word64LESpec (spec) where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Word64LE" $ do
+    it "can be decoded" $ do
+        shouldBe
+            (decodeWord64LE "\0\0\0\0\0\0\0\0")
+            (Right ("", 8, Word64LE 0))
+        shouldBe
+            (decodeWord64LE "\1\0\0\0\0\0\0\0")
+            (Right ("", 8, Word64LE 1))
+    it "can be encoded" $ do
+        shouldBe
+            (Binary.encode (Word64LE 0))
+            "\0\0\0\0\0\0\0\0"
+        shouldBe
+            (Binary.encode (Word64LE 1))
+            "\1\0\0\0\0\0\0\0"
+
+decodeWord64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Word64LE)
+decodeWord64LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/PrimitiveSpec.hs b/test-suite/Octane/Type/PrimitiveSpec.hs
--- a/test-suite/Octane/Type/PrimitiveSpec.hs
+++ b/test-suite/Octane/Type/PrimitiveSpec.hs
@@ -3,11 +3,11 @@
 import qualified Octane.Type.Primitive.BooleanSpec
 import qualified Octane.Type.Primitive.DictionarySpec
 import qualified Octane.Type.Primitive.Float32LESpec
-import qualified Octane.Type.Primitive.Int32LESpec
-import qualified Octane.Type.Primitive.Int64LESpec
 import qualified Octane.Type.Primitive.ListSpec
 import qualified Octane.Type.Primitive.PCStringSpec
 import qualified Octane.Type.Primitive.StreamSpec
+import qualified Octane.Type.Primitive.Word32LESpec
+import qualified Octane.Type.Primitive.Word64LESpec
 import Test.Tasty.Hspec
 
 spec :: Spec
@@ -15,8 +15,8 @@
     Octane.Type.Primitive.BooleanSpec.spec
     Octane.Type.Primitive.DictionarySpec.spec
     Octane.Type.Primitive.Float32LESpec.spec
-    Octane.Type.Primitive.Int32LESpec.spec
-    Octane.Type.Primitive.Int64LESpec.spec
     Octane.Type.Primitive.ListSpec.spec
     Octane.Type.Primitive.PCStringSpec.spec
     Octane.Type.Primitive.StreamSpec.spec
+    Octane.Type.Primitive.Word32LESpec.spec
+    Octane.Type.Primitive.Word64LESpec.spec
diff --git a/test-suite/Octane/Type/PropertySpec.hs b/test-suite/Octane/Type/PropertySpec.hs
--- a/test-suite/Octane/Type/PropertySpec.hs
+++ b/test-suite/Octane/Type/PropertySpec.hs
@@ -19,19 +19,19 @@
                     \\0\0\0\0\0\0\0\0\
                     \\0\0\0\0")
                 (Right ("", 30, ArrayProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (List [])))
             shouldBe
                 (decodeProperty "\
                     \\14\0\0\0ArrayProperty\0\
                     \\1\0\0\0\0\0\0\0\1\0\0\0\2\0\0\0a\0\13\0\0\0BoolProperty\0\2\0\0\0\0\0\0\0\1\5\0\0\0None\0")
                 (Right ("", 71, ArrayProperty
-                    (Int64LE 1)
-                    (List [Dictionary (Map.singleton (PCString "a") (BoolProperty (Int64LE 2) (Boolean True)))])))
+                    (Word64LE 1)
+                    (List [Dictionary (Map.singleton (PCString "a") (BoolProperty (Word64LE 2) (Boolean True)))])))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (ArrayProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (List [])))
                 "\
                     \\14\0\0\0ArrayProperty\0\
@@ -39,8 +39,8 @@
                     \\0\0\0\0"
             shouldBe
                 (Binary.encode (ArrayProperty
-                    (Int64LE 1)
-                    (List [Dictionary (Map.singleton (PCString "a") (BoolProperty (Int64LE 2) (Boolean True)))])))
+                    (Word64LE 1)
+                    (List [Dictionary (Map.singleton (PCString "a") (BoolProperty (Word64LE 2) (Boolean True)))])))
                 "\
                     \\14\0\0\0ArrayProperty\0\
                     \\1\0\0\0\0\0\0\0\1\0\0\0\2\0\0\0a\0\13\0\0\0BoolProperty\0\2\0\0\0\0\0\0\0\1\5\0\0\0None\0"
@@ -52,7 +52,7 @@
                     \\0\0\0\0\0\0\0\0\
                     \\0")
                 (Right ("", 26, BoolProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (Boolean False)))
             shouldBe
                 (decodeProperty "\
@@ -60,12 +60,12 @@
                     \\1\0\0\0\0\0\0\0\
                     \\1")
                 (Right ("", 26, BoolProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (Boolean True)))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (BoolProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (Boolean False)))
                 "\
                     \\13\0\0\0BoolProperty\0\
@@ -73,7 +73,7 @@
                     \\0"
             shouldBe
                 (Binary.encode (BoolProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (Boolean True)))
                 "\
                     \\13\0\0\0BoolProperty\0\
@@ -87,7 +87,7 @@
                     \\0\0\0\0\0\0\0\0\
                     \\1\0\0\0\0\1\0\0\0\0")
                 (Right ("", 35, ByteProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "", PCString "")))
             shouldBe
                 (decodeProperty "\
@@ -95,12 +95,12 @@
                     \\1\0\0\0\0\0\0\0\
                     \\2\0\0\0a\0\2\0\0\0b\0")
                 (Right ("", 37, ByteProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a", PCString "b")))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (ByteProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "", PCString "")))
                 "\
                     \\13\0\0\0ByteProperty\0\
@@ -108,7 +108,7 @@
                     \\1\0\0\0\0\1\0\0\0\0"
             shouldBe
                 (Binary.encode (ByteProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a", PCString "b")))
                 "\
                     \\13\0\0\0ByteProperty\0\
@@ -122,7 +122,7 @@
                     \\4\0\0\0\0\0\0\0\
                     \\0\0\0\0")
                 (Right ("", 30, FloatProperty
-                    (Int64LE 4)
+                    (Word64LE 4)
                     (Float32LE 0.0)))
             shouldBe
                 (decodeProperty "\
@@ -130,12 +130,12 @@
                     \\4\0\0\0\0\0\0\0\
                     \\0\0\128\63")
                 (Right ("", 30, FloatProperty
-                    (Int64LE 4)
+                    (Word64LE 4)
                     (Float32LE 1.0)))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (FloatProperty
-                    (Int64LE 4)
+                    (Word64LE 4)
                     (Float32LE 0.0)))
                 "\
                     \\14\0\0\0FloatProperty\0\
@@ -143,7 +143,7 @@
                     \\0\0\0\0"
             shouldBe
                 (Binary.encode (FloatProperty
-                    (Int64LE 4)
+                    (Word64LE 4)
                     (Float32LE 1.0)))
                 "\
                     \\14\0\0\0FloatProperty\0\
@@ -163,29 +163,29 @@
                     \\4\0\0\0\0\0\0\0\
                     \\0\0\0\0")
                 (Right ("", 28, IntProperty
-                    (Int64LE 4)
-                    (Int32LE 0)))
+                    (Word64LE 4)
+                    (Word32LE 0)))
             shouldBe
                 (decodeProperty "\
                     \\12\0\0\0IntProperty\0\
                     \\4\0\0\0\0\0\0\0\
                     \\1\0\0\0")
                 (Right ("", 28, IntProperty
-                    (Int64LE 4)
-                    (Int32LE 1)))
+                    (Word64LE 4)
+                    (Word32LE 1)))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (IntProperty
-                    (Int64LE 4)
-                    (Int32LE 0)))
+                    (Word64LE 4)
+                    (Word32LE 0)))
                 "\
                     \\12\0\0\0IntProperty\0\
                     \\4\0\0\0\0\0\0\0\
                     \\0\0\0\0"
             shouldBe
                 (Binary.encode (IntProperty
-                    (Int64LE 4)
-                    (Int32LE 1)))
+                    (Word64LE 4)
+                    (Word32LE 1)))
                 "\
                     \\12\0\0\0IntProperty\0\
                     \\4\0\0\0\0\0\0\0\
@@ -204,7 +204,7 @@
                     \\0\0\0\0\0\0\0\0\
                     \\1\0\0\0\0")
                 (Right ("", 30, NameProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "")))
             shouldBe
                 (decodeProperty "\
@@ -212,12 +212,12 @@
                     \\1\0\0\0\0\0\0\0\
                     \\2\0\0\0a\0")
                 (Right ("", 31, NameProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a")))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (NameProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "")))
                 "\
                     \\13\0\0\0NameProperty\0\
@@ -225,7 +225,7 @@
                     \\1\0\0\0\0"
             shouldBe
                 (Binary.encode (NameProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a")))
                 "\
                     \\13\0\0\0NameProperty\0\
@@ -239,29 +239,29 @@
                     \\8\0\0\0\0\0\0\0\
                     \\0\0\0\0\0\0\0\0")
                 (Right ("", 34, QWordProperty
-                    (Int64LE 8)
-                    (Int64LE 0)))
+                    (Word64LE 8)
+                    (Word64LE 0)))
             shouldBe
                 (decodeProperty "\
                     \\14\0\0\0QWordProperty\0\
                     \\8\0\0\0\0\0\0\0\
                     \\2\0\0\0\0\0\0\0")
                 (Right ("", 34, QWordProperty
-                    (Int64LE 8)
-                    (Int64LE 2)))
+                    (Word64LE 8)
+                    (Word64LE 2)))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (QWordProperty
-                    (Int64LE 8)
-                    (Int64LE 0)))
+                    (Word64LE 8)
+                    (Word64LE 0)))
                 "\
                     \\14\0\0\0QWordProperty\0\
                     \\8\0\0\0\0\0\0\0\
                     \\0\0\0\0\0\0\0\0"
             shouldBe
                 (Binary.encode (QWordProperty
-                    (Int64LE 8)
-                    (Int64LE 2)))
+                    (Word64LE 8)
+                    (Word64LE 2)))
                 "\
                     \\14\0\0\0QWordProperty\0\
                     \\8\0\0\0\0\0\0\0\
@@ -278,7 +278,7 @@
                     \\0\0\0\0\0\0\0\0\
                     \\1\0\0\0\0")
                 (Right ("", 29, StrProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "")))
             shouldBe
                 (decodeProperty "\
@@ -286,12 +286,12 @@
                     \\1\0\0\0\0\0\0\0\
                     \\2\0\0\0a\0")
                 (Right ("", 30, StrProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a")))
         it "can be encoded" $ do
             shouldBe
                 (Binary.encode (StrProperty
-                    (Int64LE 0)
+                    (Word64LE 0)
                     (PCString "")))
                 "\
                     \\12\0\0\0StrProperty\0\
@@ -299,7 +299,7 @@
                     \\1\0\0\0\0"
             shouldBe
                 (Binary.encode (StrProperty
-                    (Int64LE 1)
+                    (Word64LE 1)
                     (PCString "a")))
                 "\
                     \\12\0\0\0StrProperty\0\
diff --git a/test-suite/Octane/Type/ReplaySpec.hs b/test-suite/Octane/Type/ReplaySpec.hs
--- a/test-suite/Octane/Type/ReplaySpec.hs
+++ b/test-suite/Octane/Type/ReplaySpec.hs
@@ -33,14 +33,14 @@
                 \\0\0\0\0\
                 \\0\0\0\0")
             (Right ("", 78, Replay
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (PCString "")
                 (Dictionary Map.empty)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (List [])
                 (List [])
                 (Stream "")
@@ -54,14 +54,14 @@
     it "can be encoded" $ do
         shouldBe
             (Binary.encode (Replay
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (PCString "")
                 (Dictionary Map.empty)
-                (Int32LE 0)
-                (Int32LE 0)
+                (Word32LE 0)
+                (Word32LE 0)
                 (List [])
                 (List [])
                 (Stream "")
