diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,5 +2,5 @@
 
 Octane uses [Semantic Versioning][]. The change log is available [on GitHub][].
 
-[semantic versioning]: http://semver.org/spec/v2.0.0.html
-[on github]: https://github.com/tfausak/octane/releases
+[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
+[on GitHub]: https://github.com/tfausak/octane/releases
diff --git a/LICENSE.md b/LICENSE.md
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-The MIT License (MIT)
+[The MIT License (MIT)][]
 
 Copyright (c) 2016 Taylor Fausak <taylor@fausak.me>
 
@@ -19,3 +19,5 @@
 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,6 +1,7 @@
 # [Octane][]
 
-[![Build badge]][build status]
+[![Version badge][]][package version]
+[![Build badge][]][build status]
 
 Octane parses [Rocket League][] replays.
 
@@ -12,8 +13,10 @@
 
 3.  Parse a replay with `stack exec octane path/to/the.replay`.
 
-[octane]: https://github.com/tfausak/octane
-[build badge]: https://img.shields.io/travis/tfausak/octane/main.svg
+[Octane]: https://github.com/tfausak/octane
+[Version badge]: https://www.stackage.org/package/octane/badge/nightly?label
+[package 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
-[rocket league]: http://rocketleague.psyonix.com
-[stack]: http://haskellstack.org
+[Rocket League]: http://rocketleague.psyonix.com
+[Stack]: http://haskellstack.org
diff --git a/library/Octane.hs b/library/Octane.hs
--- a/library/Octane.hs
+++ b/library/Octane.hs
@@ -1,4 +1,7 @@
-module Octane (module Octane) where
+module Octane
+    ( module Octane.Main
+    , module Octane.Type
+    ) where
 
-import Octane.Main as Octane
-import Octane.Types as Octane
+import Octane.Main
+import Octane.Type
diff --git a/library/Octane/Core.hs b/library/Octane/Core.hs
--- a/library/Octane/Core.hs
+++ b/library/Octane/Core.hs
@@ -1,17 +1,33 @@
-module Octane.Core (module Octane.Core) where
+module Octane.Core
+    ( module Control.Monad
+    , module Data.Binary
+    , module Data.Binary.Get
+    , module Data.Binary.IEEE754
+    , module Data.Binary.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 System.Environment
+    , module System.IO
+    ) where
 
-import Control.Monad as Octane.Core (replicateM, when)
-import Data.Binary as Octane.Core (Binary, Get, Put, decodeFileOrFail, encode, get, getWord8, put, putWord8)
-import Data.Binary.Get as Octane.Core (ByteOffset, getByteString, getWord32le, getWord64le)
-import Data.Binary.IEEE754 as Octane.Core (getFloat32le, putFloat32le)
-import Data.Binary.Put as Octane.Core (putByteString, putWord32le, putWord64le)
-import Data.ByteString as Octane.Core (ByteString)
-import Data.Char as Octane.Core (isLatin1)
-import Data.Function as Octane.Core ((&))
-import Data.Int as Octane.Core (Int32, Int64)
-import Data.IntMap as Octane.Core (IntMap)
-import Data.Map as Octane.Core (Map)
-import Data.Text as Octane.Core (Text)
-import Data.Text.Encoding as Octane.Core (decodeLatin1, decodeUtf16LE, encodeUtf16LE)
-import System.Environment as Octane.Core (getArgs)
-import System.IO as Octane.Core (hPutStrLn, stderr, stdout)
+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.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 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
@@ -5,7 +5,7 @@
 import qualified Data.IntMap as IntMap
 import qualified Data.Map as Map
 import Octane.Core
-import Octane.Types
+import Octane.Type
 
 main :: IO ()
 main = do
@@ -54,7 +54,7 @@
             (\ (name, property) -> do
                 putStr (show (getPCString name) ++ "\t=> ")
                 debugProperty property)
-            (Map.assocs (getTable (replayProperties replay)))
+            (Map.assocs (getDictionary (replayProperties replay)))
         putStrLn ""
 
         putStrLn "# SIZE 2 #\n"
@@ -148,11 +148,11 @@
     ArrayProperty _ (NewList array) -> do
         putStrLn "[array]"
         mapM_
-            (\ (NewTable table) -> mapM_
+            (\ (NewDictionary dictionary) -> mapM_
                 (\ (NewPCString k, v) -> do
                     putStr ("\t" ++ show k ++ "\t=> ")
                     debugProperty v)
-                (Map.assocs table) >> putStrLn "")
+                (Map.assocs dictionary) >> putStrLn "")
             array
     BoolProperty _ (NewBoolean value) -> print value
     ByteProperty _ (NewPCString key, NewPCString value) -> putStrLn (show key ++ ": " ++ show value)
diff --git a/library/Octane/Type.hs b/library/Octane/Type.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type.hs
@@ -0,0 +1,25 @@
+module Octane.Type
+    ( module Octane.Type.Actor
+    , module Octane.Type.ActorMap
+    , module Octane.Type.CacheItem
+    , module Octane.Type.CacheProperty
+    , module Octane.Type.KeyFrame
+    , module Octane.Type.Mark
+    , module Octane.Type.Message
+    , module Octane.Type.ObjectMap
+    , module Octane.Type.Primitive
+    , module Octane.Type.Property
+    , module Octane.Type.Replay
+    ) where
+
+import Octane.Type.Actor
+import Octane.Type.ActorMap
+import Octane.Type.CacheItem
+import Octane.Type.CacheProperty
+import Octane.Type.KeyFrame
+import Octane.Type.Mark
+import Octane.Type.Message
+import Octane.Type.ObjectMap
+import Octane.Type.Primitive
+import Octane.Type.Property
+import Octane.Type.Replay
diff --git a/library/Octane/Type/Actor.hs b/library/Octane/Type/Actor.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Actor.hs
@@ -0,0 +1,23 @@
+module Octane.Type.Actor (Actor(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Int32LE
+
+data Actor = NewActor
+    { actorName :: PCString
+    , actorTag :: Int32LE
+    } deriving (Show)
+
+instance Binary Actor where
+    get = do
+        name <- get
+        tag <- get
+        return NewActor
+            { actorName = name
+            , actorTag = tag
+            }
+
+    put actor = do
+        actor & actorName & put
+        actor & actorTag & put
diff --git a/library/Octane/Type/ActorMap.hs b/library/Octane/Type/ActorMap.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/ActorMap.hs
@@ -0,0 +1,32 @@
+module Octane.Type.ActorMap (ActorMap(..)) where
+
+import qualified Data.IntMap as IntMap
+import Octane.Core
+import Octane.Type.Actor
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.List
+
+newtype ActorMap = NewActorMap
+    { getActorMap :: IntMap.IntMap PCString
+    } deriving (Show)
+
+instance Binary ActorMap where
+    get = do
+        (NewList actors) <- get
+        actors & map toTuple & IntMap.fromList & NewActorMap & return
+
+    put (NewActorMap actors) = do
+        actors & IntMap.assocs & map fromTuple & NewList & put
+
+toTuple :: Actor -> (Int, PCString)
+toTuple actor =
+    ( actor & actorTag & getInt32LE & fromIntegral
+    , actor & actorName
+    )
+
+fromTuple :: (Int, PCString) -> Actor
+fromTuple (tag, name) = NewActor
+    { actorName = name
+    , actorTag = tag & fromIntegral & NewInt32LE
+    }
diff --git a/library/Octane/Type/CacheItem.hs b/library/Octane/Type/CacheItem.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/CacheItem.hs
@@ -0,0 +1,32 @@
+module Octane.Type.CacheItem (CacheItem(..)) where
+
+import Octane.Core
+import Octane.Type.CacheProperty
+import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.List
+
+data CacheItem = NewCacheItem
+    { cacheItemTag :: Int32LE
+    , cacheItemStart :: Int32LE
+    , cacheItemEnd :: Int32LE
+    , cacheItemCacheProperties :: List CacheProperty
+    } deriving (Show)
+
+instance Binary CacheItem where
+    get = do
+        tag <- get
+        start <- get
+        end <- get
+        cacheProperties <- get
+        return NewCacheItem
+            { cacheItemTag = tag
+            , cacheItemStart = start
+            , cacheItemEnd = end
+            , cacheItemCacheProperties = cacheProperties
+            }
+
+    put cacheItem = do
+        cacheItem & cacheItemTag & put
+        cacheItem & cacheItemStart & put
+        cacheItem & cacheItemEnd & put
+        cacheItem & cacheItemCacheProperties & put
diff --git a/library/Octane/Type/CacheProperty.hs b/library/Octane/Type/CacheProperty.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/CacheProperty.hs
@@ -0,0 +1,22 @@
+module Octane.Type.CacheProperty (CacheProperty(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.Int32LE
+
+data CacheProperty = NewCacheProperty
+    { cachePropertyIndex :: Int32LE
+    , cachePropertyTag :: Int32LE
+    } deriving (Show)
+
+instance Binary CacheProperty where
+    get = do
+        index <- get
+        tag <- get
+        return NewCacheProperty
+            { cachePropertyIndex = index
+            , cachePropertyTag = tag
+            }
+
+    put cacheProperty = do
+        cacheProperty & cachePropertyIndex & put
+        cacheProperty & cachePropertyTag & put
diff --git a/library/Octane/Type/KeyFrame.hs b/library/Octane/Type/KeyFrame.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/KeyFrame.hs
@@ -0,0 +1,27 @@
+module Octane.Type.KeyFrame (KeyFrame(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.Float32LE
+import Octane.Type.Primitive.Int32LE
+
+data KeyFrame = NewKeyFrame
+    { keyFrameTime :: Float32LE
+    , keyFrameFrame :: Int32LE
+    , keyFramePosition :: Int32LE
+    } deriving (Show)
+
+instance Binary KeyFrame where
+    get = do
+        time <- get
+        frame <- get
+        position <- get
+        return NewKeyFrame
+            { keyFrameTime = time
+            , keyFrameFrame = frame
+            , keyFramePosition = position
+            }
+
+    put keyFrame = do
+        keyFrame & keyFrameTime & put
+        keyFrame & keyFrameFrame & put
+        keyFrame & keyFramePosition & put
diff --git a/library/Octane/Type/Mark.hs b/library/Octane/Type/Mark.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Mark.hs
@@ -0,0 +1,23 @@
+module Octane.Type.Mark (Mark(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Int32LE
+
+data Mark = NewMark
+    { markLabel :: PCString
+    , markFrame :: Int32LE
+    } deriving (Show)
+
+instance Binary Mark where
+    get = do
+        label <- get
+        frame <- get
+        return NewMark
+            { markLabel = label
+            , markFrame = frame
+            }
+
+    put mark = do
+        mark & markLabel & put
+        mark & markFrame & put
diff --git a/library/Octane/Type/Message.hs b/library/Octane/Type/Message.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Message.hs
@@ -0,0 +1,27 @@
+module Octane.Type.Message (Message(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Int32LE
+
+data Message = NewMessage
+    { messageFrame :: Int32LE
+    , messageName :: PCString
+    , messageContent :: PCString
+    } deriving (Show)
+
+instance Binary Message where
+    get = do
+        frame <- get
+        name <- get
+        content <- get
+        return NewMessage
+            { messageFrame = frame
+            , messageName = name
+            , messageContent = content
+            }
+
+    put message = do
+        message & messageFrame & put
+        message & messageName & put
+        message & messageContent & put
diff --git a/library/Octane/Type/ObjectMap.hs b/library/Octane/Type/ObjectMap.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/ObjectMap.hs
@@ -0,0 +1,18 @@
+module Octane.Type.ObjectMap (ObjectMap(..)) where
+
+import qualified Data.IntMap as IntMap
+import Octane.Core
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.List
+
+newtype ObjectMap = NewObjectMap
+    { getObjectMap :: IntMap PCString
+    } deriving (Show)
+
+instance Binary ObjectMap where
+    get = do
+        NewList objects <- get
+        objects & zip [0 ..] & IntMap.fromAscList & NewObjectMap & return
+
+    put (NewObjectMap objects) = do
+        objects & IntMap.elems & NewList & put
diff --git a/library/Octane/Type/Primitive.hs b/library/Octane/Type/Primitive.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive.hs
@@ -0,0 +1,17 @@
+module Octane.Type.Primitive
+    ( module Octane.Type.Primitive.Boolean
+    , 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
+    ) where
+
+import Octane.Type.Primitive.Boolean
+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
diff --git a/library/Octane/Type/Primitive/Boolean.hs b/library/Octane/Type/Primitive/Boolean.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Boolean.hs
@@ -0,0 +1,17 @@
+module Octane.Type.Primitive.Boolean (Boolean(..)) where
+
+import Octane.Core
+
+newtype Boolean = NewBoolean
+    { getBoolean :: Bool
+    } deriving (Eq, Show)
+
+instance Binary Boolean where
+    get = do
+        boolean <- getWord8
+        if boolean > 1
+        then fail "out of bounds"
+        else boolean & fromIntegral & toEnum & NewBoolean & return
+
+    put (NewBoolean boolean) = do
+        boolean & fromEnum & fromIntegral & putWord8
diff --git a/library/Octane/Type/Primitive/Dictionary.hs b/library/Octane/Type/Primitive/Dictionary.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Dictionary.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Dictionary (Dictionary(..)) where
+
+import qualified Data.Map as Map
+import Octane.Core
+import Octane.Type.Primitive.PCString
+
+newtype Dictionary a = NewDictionary
+    { getDictionary :: Map PCString a
+    } deriving (Show)
+
+instance (Binary a) => Binary (Dictionary a) where
+    get = do
+        row <- getRow
+        if Map.null row
+        then do
+            row & NewDictionary & return
+        else do
+            NewDictionary rows <- get
+            rows & Map.union row & NewDictionary & return
+
+    put (NewDictionary rows) = do
+        rows & Map.assocs & mapM_ putRow
+        "None" & NewPCString & put
+
+getRow :: (Binary a) => Get (Map PCString a)
+getRow = do
+    key <- get
+    if key == NewPCString "None"
+    then do
+        return Map.empty
+    else do
+        value <- get
+        return (Map.singleton key value)
+
+
+putRow :: (Binary a) => (PCString, a) -> Put
+putRow (key, value) = do
+    key & put
+    value & put
diff --git a/library/Octane/Type/Primitive/Float32LE.hs b/library/Octane/Type/Primitive/Float32LE.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Float32LE.hs
@@ -0,0 +1,15 @@
+module Octane.Type.Primitive.Float32LE (Float32LE(..)) where
+
+import Octane.Core
+
+newtype Float32LE = NewFloat32LE
+    { getFloat32LE :: Float
+    } deriving (Eq, Show)
+
+instance Binary Float32LE where
+    get = do
+        float <- getFloat32le
+        float & NewFloat32LE & return
+
+    put (NewFloat32LE float) = do
+        float & putFloat32le
diff --git a/library/Octane/Type/Primitive/Int32LE.hs b/library/Octane/Type/Primitive/Int32LE.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Int32LE.hs
@@ -0,0 +1,15 @@
+module Octane.Type.Primitive.Int32LE (Int32LE(..)) where
+
+import Octane.Core
+
+newtype Int32LE = NewInt32LE
+    { getInt32LE :: Int32
+    } deriving (Eq, Show)
+
+instance Binary Int32LE where
+    get = do
+        int <- getWord32le
+        int & fromIntegral & NewInt32LE & return
+
+    put (NewInt32LE int) = do
+        int & fromIntegral & putWord32le
diff --git a/library/Octane/Type/Primitive/Int64LE.hs b/library/Octane/Type/Primitive/Int64LE.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/Int64LE.hs
@@ -0,0 +1,15 @@
+module Octane.Type.Primitive.Int64LE (Int64LE(..)) where
+
+import Octane.Core
+
+newtype Int64LE = NewInt64LE
+    { getInt64LE :: Int64
+    } deriving (Eq, Show)
+
+instance Binary Int64LE where
+    get = do
+        int <- getWord64le
+        int & fromIntegral & NewInt64LE & return
+
+    put (NewInt64LE int) = do
+        int & fromIntegral & putWord64le
diff --git a/library/Octane/Type/Primitive/List.hs b/library/Octane/Type/Primitive/List.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/List.hs
@@ -0,0 +1,18 @@
+module Octane.Type.Primitive.List (List(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.Int32LE
+
+newtype List a = NewList
+    { getList :: [a]
+    } deriving (Show)
+
+instance (Binary a) => Binary (List a) where
+    get = do
+        (NewInt32LE size) <- get
+        elements <- replicateM (fromIntegral size) get
+        elements & NewList & return
+
+    put (NewList list) = do
+        list & length & fromIntegral & NewInt32LE & put
+        list & mapM_ put
diff --git a/library/Octane/Type/Primitive/PCString.hs b/library/Octane/Type/Primitive/PCString.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Primitive/PCString.hs
@@ -0,0 +1,39 @@
+module Octane.Type.Primitive.PCString (PCString(..)) where
+
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.Text as Text
+import Octane.Core
+import Octane.Type.Primitive.Int32LE
+
+newtype PCString = NewPCString
+    { getPCString :: Text
+    } deriving (Eq, Ord, Show)
+
+instance Binary PCString where
+    get = do
+        (NewInt32LE size) <- get
+        string <- if size == 0
+            then fail "invalid size"
+            else if size < 0
+            then do
+                let actualSize = 2 * negate size
+                bytes <- getByteString (fromIntegral actualSize)
+                bytes & decodeUtf16LE & return
+            else do
+                bytes <- getByteString (fromIntegral size)
+                bytes & decodeLatin1 & return
+        string & Text.dropEnd 1 & NewPCString & return
+
+    put (NewPCString string) = do
+        let cString = Text.snoc string '\NUL'
+        let size = cString & Text.length & fromIntegral
+        if Text.all isLatin1 cString
+        then do
+            size & NewInt32LE & put
+            cString & encodeLatin1 & putByteString
+        else do
+            size & negate & NewInt32LE & put
+            cString & encodeUtf16LE & putByteString
+
+encodeLatin1 :: Text -> ByteString
+encodeLatin1 text = text & Text.unpack & BS8.pack
diff --git a/library/Octane/Type/Property.hs b/library/Octane/Type/Property.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Property.hs
@@ -0,0 +1,96 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Property (Property(..)) where
+
+import Octane.Core
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Boolean
+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
+
+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
+    deriving (Show)
+
+instance Binary Property where
+    get = do
+        kind <- get
+        size <- get
+        case getPCString kind of
+            "ArrayProperty" -> do
+                value <- get
+                ArrayProperty size value & return
+            "BoolProperty" -> do
+                value <- get
+                BoolProperty size value & return
+            "ByteProperty" -> do
+                key <- get
+                value <- get
+                ByteProperty size (key, value) & return
+            "FloatProperty" -> do
+                value <- case size of
+                    NewInt64LE 4 -> get
+                    _ -> fail ("unknown FloatProperty size " ++ show size)
+                FloatProperty size value & return
+            "IntProperty" -> do
+                value <- case size of
+                    NewInt64LE 4 -> get
+                    _ -> fail ("unknown IntProperty size " ++ show size)
+                IntProperty size value & return
+            "NameProperty" -> do
+                value <- get
+                NameProperty size value & return
+            "StrProperty" -> do
+                value <- get
+                StrProperty size value & return
+            "QWordProperty" -> do
+                value <- case size of
+                    NewInt64LE 8 -> get
+                    _ -> fail ("unknown QWordProperty size " ++ show size)
+                QWordProperty size value & return
+            _ -> fail ("unknown property type " ++ show kind)
+
+    put property = case property of
+        ArrayProperty size value -> do
+            "ArrayProperty" & NewPCString & put
+            size & put
+            value & put
+        BoolProperty size value -> do
+            "BoolProperty" & NewPCString & put
+            size & put
+            value & put
+        ByteProperty size (key, value) -> do
+            "ByteProperty" & NewPCString & put
+            size & put
+            key & put
+            value & put
+        FloatProperty size value -> do
+            "FloatProperty" & NewPCString & put
+            size & put
+            value & put
+        IntProperty size value -> do
+            "IntProperty" & NewPCString & put
+            size & put
+            value & put
+        NameProperty size value -> do
+            "NameProperty" & NewPCString & put
+            size & put
+            value & put
+        QWordProperty size value -> do
+            "QWordProperty" & NewPCString & put
+            size & put
+            value & put
+        StrProperty size value -> do
+            "StrProperty" & NewPCString & put
+            size & put
+            value & put
diff --git a/library/Octane/Type/Replay.hs b/library/Octane/Type/Replay.hs
new file mode 100644
--- /dev/null
+++ b/library/Octane/Type/Replay.hs
@@ -0,0 +1,108 @@
+module Octane.Type.Replay (Replay(..)) where
+
+import qualified Data.ByteString as BS
+import Octane.Core
+import Octane.Type.ActorMap
+import Octane.Type.CacheItem
+import Octane.Type.KeyFrame
+import Octane.Type.Mark
+import Octane.Type.Message
+import Octane.Type.ObjectMap
+import Octane.Type.Primitive.PCString
+import Octane.Type.Primitive.Int32LE
+import Octane.Type.Primitive.List
+import Octane.Type.Property
+import Octane.Type.Primitive.Dictionary
+
+data Replay = NewReplay
+    { replaySize1 :: Int32LE
+    , replayCRC1 :: Int32LE
+    , replayVersion1 :: Int32LE
+    , replayVersion2 :: Int32LE
+    , replayLabel :: PCString
+    , replayProperties :: Dictionary Property
+    , replaySize2 :: Int32LE
+    , replayCRC2 :: Int32LE
+    , replayEffects :: List PCString
+    , replayKeyFrames :: List KeyFrame
+    , replayFrames :: ByteString
+    , replayMessages :: List Message
+    , replayMarks :: List Mark
+    , replayPackages :: List PCString
+    , replayObjectMap :: ObjectMap
+    , replayNames :: List PCString
+    , replayActorMap :: ActorMap
+    , replayCacheItems :: List CacheItem
+    } deriving (Show)
+
+instance Binary Replay where
+    get = do
+        size1 <- get
+        crc1 <- get
+        version1 <- get
+        version2 <- get
+        label <- get
+        properties <- get
+        size2 <- get
+        crc2 <- get
+        effects <- get
+        keyFrames <- get
+        frames <- getFrameBytes
+        messages <- get
+        marks <- get
+        packages <- get
+        objectMap <- get
+        names <- get
+        actorMap <- get
+        cacheItems <- get
+        return NewReplay
+            { replaySize1 = size1
+            , replayCRC1 = crc1
+            , replayVersion1 = version1
+            , replayVersion2 = version2
+            , replayLabel = label
+            , replayProperties = properties
+            , replaySize2 = size2
+            , replayCRC2 = crc2
+            , replayEffects = effects
+            , replayKeyFrames = keyFrames
+            , replayFrames = frames
+            , replayMessages = messages
+            , replayMarks = marks
+            , replayPackages = packages
+            , replayObjectMap = objectMap
+            , replayNames = names
+            , replayActorMap = actorMap
+            , replayCacheItems = cacheItems
+            }
+
+    put replay = do
+        replay & replaySize1 & put
+        replay & replayCRC1 & put
+        replay & replayVersion1 & put
+        replay & replayVersion2 & put
+        replay & replayLabel & put
+        replay & replayProperties & put
+        replay & replaySize2 & put
+        replay & replayCRC2 & put
+        replay & replayEffects & put
+        replay & replayKeyFrames & put
+        replay & replayFrames & putFrameBytes
+        replay & replayMessages & put
+        replay & replayMarks & put
+        replay & replayPackages & put
+        replay & replayObjectMap & put
+        replay & replayNames & put
+        replay & replayActorMap & put
+        replay & replayCacheItems & put
+
+getFrameBytes :: Get ByteString
+getFrameBytes = do
+    NewInt32LE size <- get
+    frames <- getByteString (fromIntegral size)
+    return frames
+
+putFrameBytes :: ByteString -> Put
+putFrameBytes frames = do
+    frames & BS.length & fromIntegral & NewInt32LE & put
+    frames & putByteString
diff --git a/library/Octane/Types.hs b/library/Octane/Types.hs
deleted file mode 100644
--- a/library/Octane/Types.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Octane.Types (module Octane.Types) where
-
-import Octane.Types.Actor as Octane.Types
-import Octane.Types.ActorMap as Octane.Types
-import Octane.Types.Boolean as Octane.Types
-import Octane.Types.CacheItem as Octane.Types
-import Octane.Types.CacheProperty as Octane.Types
-import Octane.Types.Float32LE as Octane.Types
-import Octane.Types.Int32LE as Octane.Types
-import Octane.Types.Int64LE as Octane.Types
-import Octane.Types.KeyFrame as Octane.Types
-import Octane.Types.List as Octane.Types
-import Octane.Types.Mark as Octane.Types
-import Octane.Types.Message as Octane.Types
-import Octane.Types.ObjectMap as Octane.Types
-import Octane.Types.PCString as Octane.Types
-import Octane.Types.Property as Octane.Types
-import Octane.Types.Replay as Octane.Types
-import Octane.Types.Table as Octane.Types
diff --git a/library/Octane/Types/Actor.hs b/library/Octane/Types/Actor.hs
deleted file mode 100644
--- a/library/Octane/Types/Actor.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Octane.Types.Actor (Actor(..)) where
-
-import Octane.Core
-import Octane.Types.Int32LE
-import Octane.Types.PCString
-
-data Actor = NewActor
-    { actorName :: PCString
-    , actorTag :: Int32LE
-    } deriving (Show)
-
-instance Binary Actor where
-    get = do
-        name <- get
-        tag <- get
-        return NewActor
-            { actorName = name
-            , actorTag = tag
-            }
-
-    put actor = do
-        actor & actorName & put
-        actor & actorTag & put
diff --git a/library/Octane/Types/ActorMap.hs b/library/Octane/Types/ActorMap.hs
deleted file mode 100644
--- a/library/Octane/Types/ActorMap.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-module Octane.Types.ActorMap (ActorMap(..)) where
-
-import qualified Data.IntMap as IntMap
-import Octane.Core
-import Octane.Types.Actor
-import Octane.Types.Int32LE
-import Octane.Types.List
-import Octane.Types.PCString
-
-newtype ActorMap = NewActorMap
-    { getActorMap :: IntMap.IntMap PCString
-    } deriving (Show)
-
-instance Binary ActorMap where
-    get = do
-        (NewList actors) <- get
-        actors & map toTuple & IntMap.fromList & NewActorMap & return
-
-    put (NewActorMap actors) = do
-        actors & IntMap.assocs & map fromTuple & NewList & put
-
-toTuple :: Actor -> (Int, PCString)
-toTuple actor =
-    ( actor & actorTag & getInt32LE & fromIntegral
-    , actor & actorName
-    )
-
-fromTuple :: (Int, PCString) -> Actor
-fromTuple (tag, name) = NewActor
-    { actorName = name
-    , actorTag = tag & fromIntegral & NewInt32LE
-    }
diff --git a/library/Octane/Types/Boolean.hs b/library/Octane/Types/Boolean.hs
deleted file mode 100644
--- a/library/Octane/Types/Boolean.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Octane.Types.Boolean (Boolean(..)) where
-
-import Octane.Core
-
-newtype Boolean = NewBoolean
-    { getBoolean :: Bool
-    } deriving (Eq, Show)
-
-instance Binary Boolean where
-    get = do
-        boolean <- getWord8
-        if boolean > 1
-        then fail "out of bounds"
-        else boolean & fromIntegral & toEnum & NewBoolean & return
-
-    put (NewBoolean boolean) = do
-        boolean & fromEnum & fromIntegral & putWord8
diff --git a/library/Octane/Types/CacheItem.hs b/library/Octane/Types/CacheItem.hs
deleted file mode 100644
--- a/library/Octane/Types/CacheItem.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-module Octane.Types.CacheItem (CacheItem(..)) where
-
-import Octane.Core
-import Octane.Types.CacheProperty
-import Octane.Types.Int32LE
-import Octane.Types.List
-
-data CacheItem = NewCacheItem
-    { cacheItemTag :: Int32LE
-    , cacheItemStart :: Int32LE
-    , cacheItemEnd :: Int32LE
-    , cacheItemCacheProperties :: List CacheProperty
-    } deriving (Show)
-
-instance Binary CacheItem where
-    get = do
-        tag <- get
-        start <- get
-        end <- get
-        cacheProperties <- get
-        return NewCacheItem
-            { cacheItemTag = tag
-            , cacheItemStart = start
-            , cacheItemEnd = end
-            , cacheItemCacheProperties = cacheProperties
-            }
-
-    put cacheItem = do
-        cacheItem & cacheItemTag & put
-        cacheItem & cacheItemStart & put
-        cacheItem & cacheItemEnd & put
-        cacheItem & cacheItemCacheProperties & put
diff --git a/library/Octane/Types/CacheProperty.hs b/library/Octane/Types/CacheProperty.hs
deleted file mode 100644
--- a/library/Octane/Types/CacheProperty.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Octane.Types.CacheProperty (CacheProperty(..)) where
-
-import Octane.Core
-import Octane.Types.Int32LE
-
-data CacheProperty = NewCacheProperty
-    { cachePropertyIndex :: Int32LE
-    , cachePropertyTag :: Int32LE
-    } deriving (Show)
-
-instance Binary CacheProperty where
-    get = do
-        index <- get
-        tag <- get
-        return NewCacheProperty
-            { cachePropertyIndex = index
-            , cachePropertyTag = tag
-            }
-
-    put cacheProperty = do
-        cacheProperty & cachePropertyIndex & put
-        cacheProperty & cachePropertyTag & put
diff --git a/library/Octane/Types/Float32LE.hs b/library/Octane/Types/Float32LE.hs
deleted file mode 100644
--- a/library/Octane/Types/Float32LE.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Octane.Types.Float32LE (Float32LE(..)) where
-
-import Octane.Core
-
-newtype Float32LE = NewFloat32LE
-    { getFloat32LE :: Float
-    } deriving (Show)
-
-instance Binary Float32LE where
-    get = do
-        float <- getFloat32le
-        float & NewFloat32LE & return
-
-    put (NewFloat32LE float) = do
-        float & putFloat32le
diff --git a/library/Octane/Types/Int32LE.hs b/library/Octane/Types/Int32LE.hs
deleted file mode 100644
--- a/library/Octane/Types/Int32LE.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Octane.Types.Int32LE (Int32LE(..)) where
-
-import Octane.Core
-
-newtype Int32LE = NewInt32LE
-    { getInt32LE :: Int32
-    } deriving (Eq, Show)
-
-instance Binary Int32LE where
-    get = do
-        int <- getWord32le
-        int & fromIntegral & NewInt32LE & return
-
-    put (NewInt32LE int) = do
-        int & fromIntegral & putWord32le
diff --git a/library/Octane/Types/Int64LE.hs b/library/Octane/Types/Int64LE.hs
deleted file mode 100644
--- a/library/Octane/Types/Int64LE.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Octane.Types.Int64LE (Int64LE(..)) where
-
-import Octane.Core
-
-newtype Int64LE = NewInt64LE
-    { getInt64LE :: Int64
-    } deriving (Eq, Show)
-
-instance Binary Int64LE where
-    get = do
-        int <- getWord64le
-        int & fromIntegral & NewInt64LE & return
-
-    put (NewInt64LE int) = do
-        int & fromIntegral & putWord64le
diff --git a/library/Octane/Types/KeyFrame.hs b/library/Octane/Types/KeyFrame.hs
deleted file mode 100644
--- a/library/Octane/Types/KeyFrame.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-module Octane.Types.KeyFrame (KeyFrame(..)) where
-
-import Octane.Core
-import Octane.Types.Float32LE
-import Octane.Types.Int32LE
-
-data KeyFrame = NewKeyFrame
-    { keyFrameTime :: Float32LE
-    , keyFrameFrame :: Int32LE
-    , keyFramePosition :: Int32LE
-    } deriving (Show)
-
-instance Binary KeyFrame where
-    get = do
-        time <- get
-        frame <- get
-        position <- get
-        return NewKeyFrame
-            { keyFrameTime = time
-            , keyFrameFrame = frame
-            , keyFramePosition = position
-            }
-
-    put keyFrame = do
-        keyFrame & keyFrameTime & put
-        keyFrame & keyFrameFrame & put
-        keyFrame & keyFramePosition & put
diff --git a/library/Octane/Types/List.hs b/library/Octane/Types/List.hs
deleted file mode 100644
--- a/library/Octane/Types/List.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Octane.Types.List (List(..)) where
-
-import Octane.Core
-import Octane.Types.Int32LE
-
-newtype List a = NewList
-    { getList :: [a]
-    } deriving (Show)
-
-instance (Binary a) => Binary (List a) where
-    get = do
-        (NewInt32LE size) <- get
-        elements <- replicateM (fromIntegral size) get
-        elements & NewList & return
-
-    put (NewList list) = do
-        list & length & fromIntegral & NewInt32LE & put
-        list & mapM_ put
diff --git a/library/Octane/Types/Mark.hs b/library/Octane/Types/Mark.hs
deleted file mode 100644
--- a/library/Octane/Types/Mark.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Octane.Types.Mark (Mark(..)) where
-
-import Octane.Core
-import Octane.Types.Int32LE
-import Octane.Types.PCString
-
-data Mark = NewMark
-    { markLabel :: PCString
-    , markFrame :: Int32LE
-    } deriving (Show)
-
-instance Binary Mark where
-    get = do
-        label <- get
-        frame <- get
-        return NewMark
-            { markLabel = label
-            , markFrame = frame
-            }
-
-    put mark = do
-        mark & markLabel & put
-        mark & markFrame & put
diff --git a/library/Octane/Types/Message.hs b/library/Octane/Types/Message.hs
deleted file mode 100644
--- a/library/Octane/Types/Message.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-module Octane.Types.Message (Message(..)) where
-
-import Octane.Core
-import Octane.Types.Int32LE
-import Octane.Types.PCString
-
-data Message = NewMessage
-    { messageFrame :: Int32LE
-    , messageName :: PCString
-    , messageContent :: PCString
-    } deriving (Show)
-
-instance Binary Message where
-    get = do
-        frame <- get
-        name <- get
-        content <- get
-        return NewMessage
-            { messageFrame = frame
-            , messageName = name
-            , messageContent = content
-            }
-
-    put message = do
-        message & messageFrame & put
-        message & messageName & put
-        message & messageContent & put
diff --git a/library/Octane/Types/ObjectMap.hs b/library/Octane/Types/ObjectMap.hs
deleted file mode 100644
--- a/library/Octane/Types/ObjectMap.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Octane.Types.ObjectMap (ObjectMap(..)) where
-
-import qualified Data.IntMap as IntMap
-import Octane.Core
-import Octane.Types.List
-import Octane.Types.PCString
-
-newtype ObjectMap = NewObjectMap
-    { getObjectMap :: IntMap PCString
-    } deriving (Show)
-
-instance Binary ObjectMap where
-    get = do
-        NewList objects <- get
-        objects & zip [0 ..] & IntMap.fromAscList & NewObjectMap & return
-
-    put (NewObjectMap objects) = do
-        objects & IntMap.elems & NewList & put
diff --git a/library/Octane/Types/PCString.hs b/library/Octane/Types/PCString.hs
deleted file mode 100644
--- a/library/Octane/Types/PCString.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Octane.Types.PCString (PCString(..)) where
-
-import qualified Data.ByteString.Char8 as BS8
-import qualified Data.Text as Text
-import Octane.Core
-import Octane.Types.Int32LE
-
-newtype PCString = NewPCString
-    { getPCString :: Text
-    } deriving (Eq, Ord, Show)
-
-instance Binary PCString where
-    get = do
-        (NewInt32LE size) <- get
-        string <- if size < 0
-            then do
-                let actualSize = 2 * negate size
-                bytes <- getByteString (fromIntegral actualSize)
-                bytes & decodeUtf16LE & return
-            else do
-                bytes <- getByteString (fromIntegral size)
-                bytes & decodeLatin1 & return
-        string & Text.dropEnd 1 & NewPCString & return
-
-    put (NewPCString string) = do
-        let cString = Text.snoc string '\NUL'
-        let size = cString & Text.length & fromIntegral
-        if Text.all isLatin1 cString
-        then do
-            size & NewInt32LE & put
-            cString & encodeLatin1 & putByteString
-        else do
-            size & negate & NewInt32LE & put
-            cString & encodeUtf16LE & putByteString
-
-encodeLatin1 :: Text -> ByteString
-encodeLatin1 text = text & Text.unpack & BS8.pack
diff --git a/library/Octane/Types/Property.hs b/library/Octane/Types/Property.hs
deleted file mode 100644
--- a/library/Octane/Types/Property.hs
+++ /dev/null
@@ -1,96 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Types.Property (Property(..)) where
-
-import Octane.Core
-import Octane.Types.Boolean
-import Octane.Types.Float32LE
-import Octane.Types.Int32LE
-import Octane.Types.Int64LE
-import Octane.Types.List
-import Octane.Types.PCString
-import Octane.Types.Table
-
-data Property
-    = ArrayProperty Int64LE (List (Table Property))
-    | BoolProperty Int64LE Boolean
-    | ByteProperty Int64LE (PCString, PCString)
-    | FloatProperty Int64LE Float32LE
-    | IntProperty Int64LE Int32LE
-    | NameProperty Int64LE PCString
-    | QWordProperty Int64LE Int64LE
-    | StrProperty Int64LE PCString
-    deriving (Show)
-
-instance Binary Property where
-    get = do
-        kind <- get
-        size <- get
-        case getPCString kind of
-            "ArrayProperty" -> do
-                value <- get
-                ArrayProperty size value & return
-            "BoolProperty" -> do
-                value <- get
-                BoolProperty size value & return
-            "ByteProperty" -> do
-                key <- get
-                value <- get
-                ByteProperty size (key, value) & return
-            "FloatProperty" -> do
-                value <- case size of
-                    NewInt64LE 4 -> get
-                    _ -> fail ("unknown FloatProperty size " ++ show size)
-                FloatProperty size value & return
-            "IntProperty" -> do
-                value <- case size of
-                    NewInt64LE 4 -> get
-                    _ -> fail ("unknown IntProperty size " ++ show size)
-                IntProperty size value & return
-            "NameProperty" -> do
-                value <- get
-                NameProperty size value & return
-            "StrProperty" -> do
-                value <- get
-                StrProperty size value & return
-            "QWordProperty" -> do
-                value <- case size of
-                    NewInt64LE 8 -> get
-                    _ -> fail ("unknown QWordProperty size " ++ show size)
-                QWordProperty size value & return
-            _ -> fail ("unknown property type " ++ show kind)
-
-    put property = case property of
-        ArrayProperty size value -> do
-            "ArrayProperty" & NewPCString & put
-            size & put
-            value & put
-        BoolProperty size value -> do
-            "BoolProperty" & NewPCString & put
-            size & put
-            value & put
-        ByteProperty size (key, value) -> do
-            "ByteProperty" & NewPCString & put
-            size & put
-            key & put
-            value & put
-        FloatProperty size value -> do
-            "FloatProperty" & NewPCString & put
-            size & put
-            value & put
-        IntProperty size value -> do
-            "IntProperty" & NewPCString & put
-            size & put
-            value & put
-        NameProperty size value -> do
-            "NameProperty" & NewPCString & put
-            size & put
-            value & put
-        QWordProperty size value -> do
-            "QWordProperty" & NewPCString & put
-            size & put
-            value & put
-        StrProperty size value -> do
-            "StrProperty" & NewPCString & put
-            size & put
-            value & put
diff --git a/library/Octane/Types/Replay.hs b/library/Octane/Types/Replay.hs
deleted file mode 100644
--- a/library/Octane/Types/Replay.hs
+++ /dev/null
@@ -1,108 +0,0 @@
-module Octane.Types.Replay (Replay(..)) where
-
-import qualified Data.ByteString as BS
-import Octane.Core
-import Octane.Types.ActorMap
-import Octane.Types.CacheItem
-import Octane.Types.Int32LE
-import Octane.Types.KeyFrame
-import Octane.Types.List
-import Octane.Types.Mark
-import Octane.Types.Message
-import Octane.Types.ObjectMap
-import Octane.Types.PCString
-import Octane.Types.Property
-import Octane.Types.Table
-
-data Replay = NewReplay
-    { replaySize1 :: Int32LE
-    , replayCRC1 :: Int32LE
-    , replayVersion1 :: Int32LE
-    , replayVersion2 :: Int32LE
-    , replayLabel :: PCString
-    , replayProperties :: Table Property
-    , replaySize2 :: Int32LE
-    , replayCRC2 :: Int32LE
-    , replayEffects :: List PCString
-    , replayKeyFrames :: List KeyFrame
-    , replayFrames :: ByteString
-    , replayMessages :: List Message
-    , replayMarks :: List Mark
-    , replayPackages :: List PCString
-    , replayObjectMap :: ObjectMap
-    , replayNames :: List PCString
-    , replayActorMap :: ActorMap
-    , replayCacheItems :: List CacheItem
-    } deriving (Show)
-
-instance Binary Replay where
-    get = do
-        size1 <- get
-        crc1 <- get
-        version1 <- get
-        version2 <- get
-        label <- get
-        properties <- get
-        size2 <- get
-        crc2 <- get
-        effects <- get
-        keyFrames <- get
-        frames <- getFrameBytes
-        messages <- get
-        marks <- get
-        packages <- get
-        objectMap <- get
-        names <- get
-        actorMap <- get
-        cacheItems <- get
-        return NewReplay
-            { replaySize1 = size1
-            , replayCRC1 = crc1
-            , replayVersion1 = version1
-            , replayVersion2 = version2
-            , replayLabel = label
-            , replayProperties = properties
-            , replaySize2 = size2
-            , replayCRC2 = crc2
-            , replayEffects = effects
-            , replayKeyFrames = keyFrames
-            , replayFrames = frames
-            , replayMessages = messages
-            , replayMarks = marks
-            , replayPackages = packages
-            , replayObjectMap = objectMap
-            , replayNames = names
-            , replayActorMap = actorMap
-            , replayCacheItems = cacheItems
-            }
-
-    put replay = do
-        replay & replaySize1 & put
-        replay & replayCRC1 & put
-        replay & replayVersion1 & put
-        replay & replayVersion2 & put
-        replay & replayLabel & put
-        replay & replayProperties & put
-        replay & replaySize2 & put
-        replay & replayCRC2 & put
-        replay & replayEffects & put
-        replay & replayKeyFrames & put
-        replay & replayFrames & putFrameBytes
-        replay & replayMessages & put
-        replay & replayMarks & put
-        replay & replayPackages & put
-        replay & replayObjectMap & put
-        replay & replayNames & put
-        replay & replayActorMap & put
-        replay & replayCacheItems & put
-
-getFrameBytes :: Get ByteString
-getFrameBytes = do
-    NewInt32LE size <- get
-    frames <- getByteString (fromIntegral size)
-    return frames
-
-putFrameBytes :: ByteString -> Put
-putFrameBytes frames = do
-    frames & BS.length & fromIntegral & NewInt32LE & put
-    frames & putByteString
diff --git a/library/Octane/Types/Table.hs b/library/Octane/Types/Table.hs
deleted file mode 100644
--- a/library/Octane/Types/Table.hs
+++ /dev/null
@@ -1,41 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Types.Table (Table(..)) where
-
-import qualified Data.Map as Map
-import Octane.Core
-import Octane.Types.PCString
-
-newtype Table a = NewTable
-    { getTable :: Map PCString a
-    } deriving (Show)
-
-instance (Binary a) => Binary (Table a) where
-    get = do
-        row <- getRow
-        if Map.null row
-        then do
-            row & NewTable & return
-        else do
-            NewTable rows <- get
-            rows & Map.union row & NewTable & return
-
-    put (NewTable rows) = do
-        rows & Map.assocs & mapM_ putRow
-        "None" & NewPCString & put
-
-getRow :: (Binary a) => Get (Map PCString a)
-getRow = do
-    key <- get
-    if key == NewPCString "None"
-    then do
-        return Map.empty
-    else do
-        value <- get
-        return (Map.singleton key value)
-
-
-putRow :: (Binary a) => (PCString, a) -> Put
-putRow (key, value) = do
-    key & put
-    value & put
diff --git a/octane.cabal b/octane.cabal
--- a/octane.cabal
+++ b/octane.cabal
@@ -1,5 +1,5 @@
 name: octane
-version: 0.1.1
+version: 0.2.0
 cabal-version: >=1.10
 build-type: Simple
 license: MIT
@@ -23,24 +23,25 @@
     exposed-modules:
         Octane
         Octane.Main
-        Octane.Types
-        Octane.Types.Actor
-        Octane.Types.ActorMap
-        Octane.Types.Boolean
-        Octane.Types.CacheItem
-        Octane.Types.CacheProperty
-        Octane.Types.Float32LE
-        Octane.Types.Int32LE
-        Octane.Types.Int64LE
-        Octane.Types.KeyFrame
-        Octane.Types.List
-        Octane.Types.Mark
-        Octane.Types.Message
-        Octane.Types.ObjectMap
-        Octane.Types.PCString
-        Octane.Types.Property
-        Octane.Types.Replay
-        Octane.Types.Table
+        Octane.Type
+        Octane.Type.Actor
+        Octane.Type.ActorMap
+        Octane.Type.CacheItem
+        Octane.Type.CacheProperty
+        Octane.Type.KeyFrame
+        Octane.Type.Mark
+        Octane.Type.Message
+        Octane.Type.ObjectMap
+        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
     build-depends:
         base ==4.8.*,
         binary ==0.7.*,
@@ -78,22 +79,23 @@
     other-modules:
         OctaneSpec
         Octane.MainSpec
-        Octane.TypesSpec
-        Octane.Types.ActorSpec
-        Octane.Types.ActorMapSpec
-        Octane.Types.BooleanSpec
-        Octane.Types.CacheItemSpec
-        Octane.Types.CachePropertySpec
-        Octane.Types.Float32LESpec
-        Octane.Types.Int32LESpec
-        Octane.Types.Int64LESpec
-        Octane.Types.KeyFrameSpec
-        Octane.Types.ListSpec
-        Octane.Types.MarkSpec
-        Octane.Types.MessageSpec
-        Octane.Types.ObjectMapSpec
-        Octane.Types.PCStringSpec
-        Octane.Types.PropertySpec
-        Octane.Types.ReplaySpec
-        Octane.Types.TableSpec
+        Octane.TypeSpec
+        Octane.Type.ActorSpec
+        Octane.Type.ActorMapSpec
+        Octane.Type.CacheItemSpec
+        Octane.Type.CachePropertySpec
+        Octane.Type.KeyFrameSpec
+        Octane.Type.MarkSpec
+        Octane.Type.MessageSpec
+        Octane.Type.ObjectMapSpec
+        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
     ghc-options: -threaded -Wall
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,4 +1,4 @@
 install-ghc: true
 packages: [.]
 require-stack-version: '>=1.0'
-resolver: nightly-2016-01-30
+resolver: nightly-2016-01-31
diff --git a/test-suite/Octane/Type/ActorMapSpec.hs b/test-suite/Octane/Type/ActorMapSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/ActorMapSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.ActorMapSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "ActorMap" $ do
+    return ()
diff --git a/test-suite/Octane/Type/ActorSpec.hs b/test-suite/Octane/Type/ActorSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/ActorSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.ActorSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Actor" $ do
+    return ()
diff --git a/test-suite/Octane/Type/CacheItemSpec.hs b/test-suite/Octane/Type/CacheItemSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/CacheItemSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.CacheItemSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "CacheItem" $ do
+    return ()
diff --git a/test-suite/Octane/Type/CachePropertySpec.hs b/test-suite/Octane/Type/CachePropertySpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/CachePropertySpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.CachePropertySpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "CacheProperty" $ do
+    return ()
diff --git a/test-suite/Octane/Type/KeyFrameSpec.hs b/test-suite/Octane/Type/KeyFrameSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/KeyFrameSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.KeyFrameSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "KeyFrame" $ do
+    return ()
diff --git a/test-suite/Octane/Type/MarkSpec.hs b/test-suite/Octane/Type/MarkSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/MarkSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.MarkSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Mark" $ do
+    return ()
diff --git a/test-suite/Octane/Type/MessageSpec.hs b/test-suite/Octane/Type/MessageSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/MessageSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.MessageSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Message" $ do
+    return ()
diff --git a/test-suite/Octane/Type/ObjectMapSpec.hs b/test-suite/Octane/Type/ObjectMapSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/ObjectMapSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.ObjectMapSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "ObjectMap" $ do
+    return ()
diff --git a/test-suite/Octane/Type/Primitive/BooleanSpec.hs b/test-suite/Octane/Type/Primitive/BooleanSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/BooleanSpec.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.BooleanSpec (spec) where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import qualified Octane as Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Boolean" $ do
+    it "can be decoded" $ do
+        decodeBoolean "\0" `shouldBe` Right ("", 1, Octane.NewBoolean False)
+        decodeBoolean "\1" `shouldBe` Right ("", 1, Octane.NewBoolean True)
+    it "can be encoded" $ do
+        Binary.encode (Octane.NewBoolean False) `shouldBe` "\0"
+        Binary.encode (Octane.NewBoolean True) `shouldBe` "\1"
+    it "does not raise a runtime error when decoding garbage" $ do
+        decodeBoolean "garbage" `shouldBe` Left ("arbage", 1, "out of bounds")
+
+decodeBoolean :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Boolean)
+decodeBoolean = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/DictionarySpec.hs b/test-suite/Octane/Type/Primitive/DictionarySpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/DictionarySpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.Primitive.DictionarySpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Dictionary" $ do
+    return ()
diff --git a/test-suite/Octane/Type/Primitive/Float32LESpec.hs b/test-suite/Octane/Type/Primitive/Float32LESpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/Float32LESpec.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.Float32LESpec (spec) where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import qualified Octane as Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Float32LE" $ do
+    it "can be decoded" $ do
+        decodeFloat32LE "\0\0\0\0" `shouldBe` Right ("", 4, Octane.NewFloat32LE 0.0)
+        decodeFloat32LE "\0\0\128\63" `shouldBe` Right ("", 4, Octane.NewFloat32LE 1.0)
+    it "can be encoded" $ do
+        Binary.encode (Octane.NewFloat32LE 0.0) `shouldBe` "\0\0\0\0"
+        Binary.encode (Octane.NewFloat32LE 1.0) `shouldBe` "\0\0\128\63"
+
+decodeFloat32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Float32LE)
+decodeFloat32LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/Int32LESpec.hs b/test-suite/Octane/Type/Primitive/Int32LESpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/Int32LESpec.hs
@@ -0,0 +1,21 @@
+{-# 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 qualified Octane as Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Int32LE" $ do
+    it "can be decoded" $ do
+        decodeInt32LE "\0\0\0\0" `shouldBe` Right ("", 4, Octane.NewInt32LE 0)
+        decodeInt32LE "\1\0\0\0" `shouldBe` Right ("", 4, Octane.NewInt32LE 1)
+    it "can be encoded" $ do
+        Binary.encode (Octane.NewInt32LE 0) `shouldBe` "\0\0\0\0"
+        Binary.encode (Octane.NewInt32LE 1) `shouldBe` "\1\0\0\0"
+
+decodeInt32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Int32LE)
+decodeInt32LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/Int64LESpec.hs b/test-suite/Octane/Type/Primitive/Int64LESpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/Int64LESpec.hs
@@ -0,0 +1,21 @@
+{-# 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 qualified Octane as Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Int64LE" $ do
+    it "can be decoded" $ do
+        decodeInt64LE "\0\0\0\0\0\0\0\0" `shouldBe` Right ("", 8, Octane.NewInt64LE 0)
+        decodeInt64LE "\1\0\0\0\0\0\0\0" `shouldBe` Right ("", 8, Octane.NewInt64LE 1)
+    it "can be encoded" $ do
+        Binary.encode (Octane.NewInt64LE 0) `shouldBe` "\0\0\0\0\0\0\0\0"
+        Binary.encode (Octane.NewInt64LE 1) `shouldBe` "\1\0\0\0\0\0\0\0"
+
+decodeInt64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Int64LE)
+decodeInt64LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/Primitive/ListSpec.hs b/test-suite/Octane/Type/Primitive/ListSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/ListSpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.Primitive.ListSpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "ListSpec" $ do
+    return ()
diff --git a/test-suite/Octane/Type/Primitive/PCStringSpec.hs b/test-suite/Octane/Type/Primitive/PCStringSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/Primitive/PCStringSpec.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Octane.Type.Primitive.PCStringSpec (spec) where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Get as Binary
+import qualified Data.ByteString.Lazy as BSL
+import qualified Octane as Octane
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "PCString" $ do
+    it "can be decoded" $ do
+        decodePCString "\1\0\0\0\0" `shouldBe` Right ("", 5, Octane.NewPCString "")
+        decodePCString "\6\0\0\0ascii\0" `shouldBe` Right ("", 10, Octane.NewPCString "ascii")
+        decodePCString "\8\0\0\0\251\241\239\231\248d\233\0" `shouldBe` Right ("", 12, Octane.NewPCString "ûñïçødé")
+    it "can be encoded" $ do
+        Binary.encode (Octane.NewPCString "") `shouldBe` "\1\0\0\0\0"
+        Binary.encode (Octane.NewPCString "ascii") `shouldBe` "\6\0\0\0ascii\0"
+        Binary.encode (Octane.NewPCString "ûñïçødé") `shouldBe` "\8\0\0\0\251\241\239\231\248d\233\0"
+    it "does not decode strings of length 0" $ do
+        decodePCString "\0\0\0\0" `shouldBe` Left ("", 4, "invalid size")
+
+decodePCString :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.PCString)
+decodePCString = Binary.decodeOrFail
diff --git a/test-suite/Octane/Type/PrimitiveSpec.hs b/test-suite/Octane/Type/PrimitiveSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/PrimitiveSpec.hs
@@ -0,0 +1,20 @@
+module Octane.Type.PrimitiveSpec (spec) where
+
+import qualified Octane.Type.Primitive.BooleanSpec
+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.DictionarySpec
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Primitive" $ do
+    Octane.Type.Primitive.BooleanSpec.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.DictionarySpec.spec
diff --git a/test-suite/Octane/Type/PropertySpec.hs b/test-suite/Octane/Type/PropertySpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/PropertySpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.PropertySpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Property" $ do
+    return ()
diff --git a/test-suite/Octane/Type/ReplaySpec.hs b/test-suite/Octane/Type/ReplaySpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/Type/ReplaySpec.hs
@@ -0,0 +1,7 @@
+module Octane.Type.ReplaySpec (spec) where
+
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Replay" $ do
+    return ()
diff --git a/test-suite/Octane/TypeSpec.hs b/test-suite/Octane/TypeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Octane/TypeSpec.hs
@@ -0,0 +1,28 @@
+module Octane.TypeSpec (spec) where
+
+import qualified Octane.Type.ActorSpec
+import qualified Octane.Type.ActorMapSpec
+import qualified Octane.Type.CacheItemSpec
+import qualified Octane.Type.CachePropertySpec
+import qualified Octane.Type.KeyFrameSpec
+import qualified Octane.Type.MarkSpec
+import qualified Octane.Type.MessageSpec
+import qualified Octane.Type.ObjectMapSpec
+import qualified Octane.Type.PrimitiveSpec
+import qualified Octane.Type.PropertySpec
+import qualified Octane.Type.ReplaySpec
+import Test.Tasty.Hspec
+
+spec :: Spec
+spec = describe "Type" $ do
+    Octane.Type.ActorSpec.spec
+    Octane.Type.ActorMapSpec.spec
+    Octane.Type.CacheItemSpec.spec
+    Octane.Type.CachePropertySpec.spec
+    Octane.Type.KeyFrameSpec.spec
+    Octane.Type.MarkSpec.spec
+    Octane.Type.MessageSpec.spec
+    Octane.Type.ObjectMapSpec.spec
+    Octane.Type.PrimitiveSpec.spec
+    Octane.Type.PropertySpec.spec
+    Octane.Type.ReplaySpec.spec
diff --git a/test-suite/Octane/Types/ActorMapSpec.hs b/test-suite/Octane/Types/ActorMapSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/ActorMapSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.ActorMapSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "ActorMap" $ do
-    return ()
diff --git a/test-suite/Octane/Types/ActorSpec.hs b/test-suite/Octane/Types/ActorSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/ActorSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.ActorSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Actor" $ do
-    return ()
diff --git a/test-suite/Octane/Types/BooleanSpec.hs b/test-suite/Octane/Types/BooleanSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/BooleanSpec.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Types.BooleanSpec (spec) where
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import qualified Octane as Octane
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Boolean" $ do
-    it "can be decoded" $ do
-        decodeBoolean "\0" `shouldBe` Right ("", 1, Octane.NewBoolean False)
-        decodeBoolean "\1" `shouldBe` Right ("", 1, Octane.NewBoolean True)
-    it "can be encoded" $ do
-        Binary.encode (Octane.NewBoolean False) `shouldBe` "\0"
-        Binary.encode (Octane.NewBoolean True) `shouldBe` "\1"
-    it "does not raise a runtime error when decoding garbage" $ do
-        decodeBoolean "garbage" `shouldBe` Left ("arbage", 1, "out of bounds")
-
-decodeBoolean :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Boolean)
-decodeBoolean = Binary.decodeOrFail
diff --git a/test-suite/Octane/Types/CacheItemSpec.hs b/test-suite/Octane/Types/CacheItemSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/CacheItemSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.CacheItemSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "CacheItem" $ do
-    return ()
diff --git a/test-suite/Octane/Types/CachePropertySpec.hs b/test-suite/Octane/Types/CachePropertySpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/CachePropertySpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.CachePropertySpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "CacheProperty" $ do
-    return ()
diff --git a/test-suite/Octane/Types/Float32LESpec.hs b/test-suite/Octane/Types/Float32LESpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/Float32LESpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.Float32LESpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Float32LE" $ do
-    return ()
diff --git a/test-suite/Octane/Types/Int32LESpec.hs b/test-suite/Octane/Types/Int32LESpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/Int32LESpec.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Types.Int32LESpec (spec) where
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import qualified Octane as Octane
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Int32LE" $ do
-    it "can be decoded" $ do
-        decodeInt32LE "\0\0\0\0" `shouldBe` Right ("", 4, Octane.NewInt32LE 0)
-        decodeInt32LE "\1\0\0\0" `shouldBe` Right ("", 4, Octane.NewInt32LE 1)
-    it "can be encoded" $ do
-        Binary.encode (Octane.NewInt32LE 0) `shouldBe` "\0\0\0\0"
-        Binary.encode (Octane.NewInt32LE 1) `shouldBe` "\1\0\0\0"
-
-decodeInt32LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Int32LE)
-decodeInt32LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Types/Int64LESpec.hs b/test-suite/Octane/Types/Int64LESpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/Int64LESpec.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Octane.Types.Int64LESpec (spec) where
-
-import qualified Data.Binary as Binary
-import qualified Data.Binary.Get as Binary
-import qualified Data.ByteString.Lazy as BSL
-import qualified Octane as Octane
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Int64LE" $ do
-    it "can be decoded" $ do
-        decodeInt64LE "\0\0\0\0\0\0\0\0" `shouldBe` Right ("", 8, Octane.NewInt64LE 0)
-        decodeInt64LE "\1\0\0\0\0\0\0\0" `shouldBe` Right ("", 8, Octane.NewInt64LE 1)
-    it "can be encoded" $ do
-        Binary.encode (Octane.NewInt64LE 0) `shouldBe` "\0\0\0\0\0\0\0\0"
-        Binary.encode (Octane.NewInt64LE 1) `shouldBe` "\1\0\0\0\0\0\0\0"
-
-decodeInt64LE :: BSL.ByteString -> Either (BSL.ByteString, Binary.ByteOffset, String) (BSL.ByteString, Binary.ByteOffset, Octane.Int64LE)
-decodeInt64LE = Binary.decodeOrFail
diff --git a/test-suite/Octane/Types/KeyFrameSpec.hs b/test-suite/Octane/Types/KeyFrameSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/KeyFrameSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.KeyFrameSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "KeyFrame" $ do
-    return ()
diff --git a/test-suite/Octane/Types/ListSpec.hs b/test-suite/Octane/Types/ListSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/ListSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.ListSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "ListSpec" $ do
-    return ()
diff --git a/test-suite/Octane/Types/MarkSpec.hs b/test-suite/Octane/Types/MarkSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/MarkSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.MarkSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Mark" $ do
-    return ()
diff --git a/test-suite/Octane/Types/MessageSpec.hs b/test-suite/Octane/Types/MessageSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/MessageSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.MessageSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Message" $ do
-    return ()
diff --git a/test-suite/Octane/Types/ObjectMapSpec.hs b/test-suite/Octane/Types/ObjectMapSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/ObjectMapSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.ObjectMapSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "ObjectMap" $ do
-    return ()
diff --git a/test-suite/Octane/Types/PCStringSpec.hs b/test-suite/Octane/Types/PCStringSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/PCStringSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.PCStringSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "PCString" $ do
-    return ()
diff --git a/test-suite/Octane/Types/PropertySpec.hs b/test-suite/Octane/Types/PropertySpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/PropertySpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.PropertySpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Property" $ do
-    return ()
diff --git a/test-suite/Octane/Types/ReplaySpec.hs b/test-suite/Octane/Types/ReplaySpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/ReplaySpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.ReplaySpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Replay" $ do
-    return ()
diff --git a/test-suite/Octane/Types/TableSpec.hs b/test-suite/Octane/Types/TableSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/Types/TableSpec.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Octane.Types.TableSpec (spec) where
-
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Table" $ do
-    return ()
diff --git a/test-suite/Octane/TypesSpec.hs b/test-suite/Octane/TypesSpec.hs
deleted file mode 100644
--- a/test-suite/Octane/TypesSpec.hs
+++ /dev/null
@@ -1,40 +0,0 @@
-module Octane.TypesSpec (spec) where
-
-import qualified Octane.Types.ActorSpec as ActorSpec
-import qualified Octane.Types.ActorMapSpec as ActorMapSpec
-import qualified Octane.Types.BooleanSpec as BooleanSpec
-import qualified Octane.Types.CacheItemSpec as CacheItemSpec
-import qualified Octane.Types.CachePropertySpec as CachePropertySpec
-import qualified Octane.Types.Float32LESpec as Float32LESpec
-import qualified Octane.Types.Int32LESpec as Int32LESpec
-import qualified Octane.Types.Int64LESpec as Int64LESpec
-import qualified Octane.Types.KeyFrameSpec as KeyFrameSpec
-import qualified Octane.Types.ListSpec as ListSpec
-import qualified Octane.Types.MarkSpec as MarkSpec
-import qualified Octane.Types.MessageSpec as MessageSpec
-import qualified Octane.Types.ObjectMapSpec as ObjectMapSpec
-import qualified Octane.Types.PCStringSpec as PCStringSpec
-import qualified Octane.Types.PropertySpec as PropertySpec
-import qualified Octane.Types.ReplaySpec as ReplaySpec
-import qualified Octane.Types.TableSpec as TableSpec
-import Test.Tasty.Hspec
-
-spec :: Spec
-spec = describe "Types" $ do
-    ActorSpec.spec
-    ActorMapSpec.spec
-    BooleanSpec.spec
-    CacheItemSpec.spec
-    CachePropertySpec.spec
-    Float32LESpec.spec
-    Int32LESpec.spec
-    Int64LESpec.spec
-    KeyFrameSpec.spec
-    ListSpec.spec
-    MarkSpec.spec
-    MessageSpec.spec
-    ObjectMapSpec.spec
-    PCStringSpec.spec
-    PropertySpec.spec
-    ReplaySpec.spec
-    TableSpec.spec
diff --git a/test-suite/OctaneSpec.hs b/test-suite/OctaneSpec.hs
--- a/test-suite/OctaneSpec.hs
+++ b/test-suite/OctaneSpec.hs
@@ -1,10 +1,10 @@
 module OctaneSpec (spec) where
 
-import qualified Octane.MainSpec as MainSpec
-import qualified Octane.TypesSpec as TypesSpec
+import qualified Octane.MainSpec
+import qualified Octane.TypeSpec
 import Test.Tasty.Hspec
 
 spec :: Spec
 spec = describe "Octane" $ do
-    MainSpec.spec
-    TypesSpec.spec
+    Octane.MainSpec.spec
+    Octane.TypeSpec.spec
diff --git a/test-suite/TestSuite.hs b/test-suite/TestSuite.hs
--- a/test-suite/TestSuite.hs
+++ b/test-suite/TestSuite.hs
@@ -1,4 +1,4 @@
-import qualified OctaneSpec as OctaneSpec
+import qualified OctaneSpec
 import qualified Test.Tasty
 import Test.Tasty.Hspec
 
