diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Eric Conlon (c) 2022
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Eric Conlon nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# dahdit
+
+Binary parsing and serialization with integrated size.
+
+This differs from `binary` in a few ways:
+
+* It is required that all types implementing `Binary` must also implement `ByteSized`.
+* Known-statically-sized types can implement `StaticByteSized` for size speedups.
+* Examining `Put` can yield required byte size without serializing.
+* It uses `ShortByteString` to avoid pinned memory.
+* It supports parsing `PrimArray` for fast copies of repeated chunks of primitive data.
+* Derivation uses `via` and not `anyclass` for better control of derived instances.
+* Provides several additional types representing fixed-length strings or sequences (using `TypeLits`).
+
+This generally works but should be considered experimental.
+
+(dahs and dits are signal durations in Morse code)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dahdit.cabal b/dahdit.cabal
new file mode 100644
--- /dev/null
+++ b/dahdit.cabal
@@ -0,0 +1,130 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.35.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           dahdit
+version:        0.1.0
+synopsis:       Binary parsing and serialization with integrated size
+description:    Please see the README on GitHub at <https://github.com/ejconlon/dahdit#readme>
+category:       Data
+homepage:       https://github.com/ejconlon/dahdit#readme
+bug-reports:    https://github.com/ejconlon/dahdit/issues
+author:         Eric Conlon
+maintainer:     ejconlon@gmail.com
+copyright:      (c) 2022 Eric Conlon
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/ejconlon/dahdit
+
+library
+  exposed-modules:
+      Dahdit
+      Dahdit.Binary
+      Dahdit.BinaryRep
+      Dahdit.Fancy
+      Dahdit.Free
+      Dahdit.Funs
+      Dahdit.Generic
+      Dahdit.Internal
+      Dahdit.LiftedPrim
+      Dahdit.Nums
+      Dahdit.Proxy
+      Dahdit.Run
+      Dahdit.Sizes
+  other-modules:
+      Paths_dahdit
+  hs-source-dirs:
+      src
+  default-extensions:
+      BangPatterns
+      ConstraintKinds
+      DataKinds
+      DeriveFunctor
+      DeriveFoldable
+      DeriveGeneric
+      DeriveTraversable
+      DerivingStrategies
+      DerivingVia
+      FlexibleContexts
+      FlexibleInstances
+      FunctionalDependencies
+      GADTs
+      GeneralizedNewtypeDeriving
+      LambdaCase
+      KindSignatures
+      MultiParamTypeClasses
+      Rank2Types
+      ScopedTypeVariables
+      StandaloneDeriving
+      TupleSections
+      TypeApplications
+      TypeOperators
+      TypeFamilies
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -fwrite-ide-info -hiedir=.hie
+  build-depends:
+      base >=4.12 && <5
+    , bytestring ==0.10.*
+    , containers ==0.6.*
+    , data-default ==0.7.*
+    , data-sword ==0.2.*
+    , free ==5.1.*
+    , mtl ==2.2.*
+    , primitive ==0.7.*
+    , transformers ==0.5.*
+  default-language: Haskell2010
+
+test-suite dahdit-test
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  other-modules:
+      Paths_dahdit
+  hs-source-dirs:
+      test
+  default-extensions:
+      BangPatterns
+      ConstraintKinds
+      DataKinds
+      DeriveFunctor
+      DeriveFoldable
+      DeriveGeneric
+      DeriveTraversable
+      DerivingStrategies
+      DerivingVia
+      FlexibleContexts
+      FlexibleInstances
+      FunctionalDependencies
+      GADTs
+      GeneralizedNewtypeDeriving
+      LambdaCase
+      KindSignatures
+      MultiParamTypeClasses
+      Rank2Types
+      ScopedTypeVariables
+      StandaloneDeriving
+      TupleSections
+      TypeApplications
+      TypeOperators
+      TypeFamilies
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -fwrite-ide-info -hiedir=.hie -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.12 && <5
+    , bytestring ==0.10.*
+    , containers ==0.6.*
+    , dahdit
+    , data-default ==0.7.*
+    , data-sword ==0.2.*
+    , free ==5.1.*
+    , mtl ==2.2.*
+    , primitive ==0.7.*
+    , tasty
+    , tasty-hunit
+    , transformers ==0.5.*
+  default-language: Haskell2010
diff --git a/src/Dahdit.hs b/src/Dahdit.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit.hs
@@ -0,0 +1,45 @@
+module Dahdit
+  ( Word8
+  , Int8
+  , ByteString
+  , ShortByteString
+  , Seq (..)
+  , ByteArray
+  , PrimArray
+  , Generic
+  , Prim
+  , Proxy (..)
+  , module Dahdit.Binary
+  , module Dahdit.BinaryRep
+  , module Dahdit.Fancy
+  , module Dahdit.Free
+  , module Dahdit.Funs
+  , module Dahdit.Generic
+  , module Dahdit.LiftedPrim
+  , module Dahdit.Nums
+  , module Dahdit.Proxy
+  , module Dahdit.Run
+  , module Dahdit.Sizes
+   ) where
+
+import Dahdit.Binary
+import Dahdit.BinaryRep
+import Dahdit.Fancy
+import Dahdit.Free (Get, Put, PutM)
+import Dahdit.Funs hiding (unsafePutStaticArrayN, unsafePutStaticSeqN)
+import Dahdit.Generic
+import Dahdit.LiftedPrim
+import Dahdit.Nums
+import Dahdit.Proxy
+import Dahdit.Run
+import Dahdit.Sizes
+import Data.ByteString (ByteString)
+import Data.ByteString.Short (ShortByteString)
+import Data.Int (Int8)
+import Data.Primitive (Prim)
+import Data.Primitive.ByteArray (ByteArray)
+import Data.Primitive.PrimArray (PrimArray)
+import Data.Proxy (Proxy (..))
+import Data.Sequence (Seq (..))
+import Data.Word (Word8)
+import GHC.Generics (Generic)
diff --git a/src/Dahdit/Binary.hs b/src/Dahdit/Binary.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Binary.hs
@@ -0,0 +1,91 @@
+module Dahdit.Binary
+  ( Binary (..)
+  , byteSizeViaPut
+  ) where
+
+import Dahdit.Free (Get, Put)
+import Dahdit.Funs (getFloatBE, getFloatLE, getInt16BE, getInt16LE, getInt24BE, getInt24LE, getInt32BE, getInt32LE,
+                    getInt8, getWord16BE, getWord16LE, getWord24BE, getWord24LE, getWord32BE, getWord32LE, getWord8,
+                    putFloatBE, putFloatLE, putInt16BE, putInt16LE, putInt24BE, putInt24LE, putInt32BE, putInt32LE,
+                    putInt8, putWord16BE, putWord16LE, putWord24BE, putWord24LE, putWord32BE, putWord32LE, putWord8)
+import Dahdit.Nums (FloatBE (..), FloatLE, Int16BE (..), Int16LE, Int24BE (..), Int24LE, Int32BE (..), Int32LE,
+                    Word16BE (..), Word16LE, Word24BE (..), Word24LE, Word32BE (..), Word32LE)
+import Dahdit.Run (runCount)
+import Dahdit.Sizes (ByteCount, ByteSized)
+import Data.Int (Int8)
+import Data.Word (Word8)
+
+class ByteSized a => Binary a where
+  get :: Get a
+  put :: a -> Put
+
+byteSizeViaPut :: Binary a => a -> ByteCount
+byteSizeViaPut = runCount . put
+
+instance Binary () where
+  get = pure ()
+  put _ = pure ()
+
+instance Binary Word8 where
+  get = getWord8
+  put = putWord8
+
+instance Binary Int8 where
+  get = getInt8
+  put = putInt8
+
+instance Binary Word16LE where
+  get = getWord16LE
+  put = putWord16LE
+
+instance Binary Int16LE where
+  get = getInt16LE
+  put = putInt16LE
+
+instance Binary Word24LE where
+  get = getWord24LE
+  put = putWord24LE
+
+instance Binary Int24LE where
+  get = getInt24LE
+  put = putInt24LE
+
+instance Binary Word32LE where
+  get = getWord32LE
+  put = putWord32LE
+
+instance Binary Int32LE where
+  get = getInt32LE
+  put = putInt32LE
+
+instance Binary FloatLE where
+  get = getFloatLE
+  put = putFloatLE
+
+instance Binary Word16BE where
+  get = getWord16BE
+  put = putWord16BE
+
+instance Binary Int16BE where
+  get = getInt16BE
+  put = putInt16BE
+
+instance Binary Word24BE where
+  get = getWord24BE
+  put = putWord24BE
+
+instance Binary Int24BE where
+  get = getInt24BE
+  put = putInt24BE
+
+instance Binary Word32BE where
+  get = getWord32BE
+  put = putWord32BE
+
+instance Binary Int32BE where
+  get = getInt32BE
+  put = putInt32BE
+
+instance Binary FloatBE where
+  get = getFloatBE
+  put = putFloatBE
diff --git a/src/Dahdit/BinaryRep.hs b/src/Dahdit/BinaryRep.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/BinaryRep.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+module Dahdit.BinaryRep
+  ( BinaryRep (..)
+  , ViaBoundedEnum (..)
+  , ViaBinaryRep (..)
+  ) where
+
+import Dahdit.Binary (Binary (..))
+import Dahdit.Sizes (ByteSized (..), StaticByteSized (..))
+import Data.Proxy (Proxy (..))
+
+class Binary x => BinaryRep x a | a -> x where
+  fromBinaryRep :: x -> Either String a
+  toBinaryRep :: a -> x
+
+newtype ViaBoundedEnum x a = ViaBoundedEnum { unViaBoundedEnum :: a }
+
+instance (Binary x, Integral x, Bounded a, Enum a) => BinaryRep x (ViaBoundedEnum x a) where
+  fromBinaryRep x =
+    let i = fromIntegral x
+    in if i < fromEnum (minBound :: a) || i > fromEnum (maxBound :: a)
+      then Left ("Invalid enum value: " ++ show i)
+      else Right (ViaBoundedEnum (toEnum i))
+  toBinaryRep = fromIntegral . fromEnum . unViaBoundedEnum
+
+newtype ViaBinaryRep a = ViaBinaryRep { unViaBinaryRep :: a }
+
+instance (ByteSized x, BinaryRep x a) => ByteSized (ViaBinaryRep a) where
+  byteSize = byteSize . toBinaryRep . unViaBinaryRep
+
+instance (StaticByteSized x, BinaryRep x a) => StaticByteSized (ViaBinaryRep a) where
+  staticByteSize _ = staticByteSize (Proxy :: Proxy x)
+
+instance (BinaryRep x a, Binary x) => Binary (ViaBinaryRep a) where
+  get = get >>= either fail (pure . ViaBinaryRep) . fromBinaryRep
+  put = put . toBinaryRep . unViaBinaryRep
diff --git a/src/Dahdit/Fancy.hs b/src/Dahdit/Fancy.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Fancy.hs
@@ -0,0 +1,186 @@
+module Dahdit.Fancy
+  ( TermBytes (..)
+  , StaticBytes (..)
+  , mkStaticBytes
+  , normStaticBytes
+  , StaticSeq (..)
+  , StaticArray (..)
+  , BoolByte (..)
+  , ExactBytes (..)
+  ) where
+
+import Control.Monad (unless)
+import Dahdit.Binary (Binary (..))
+import Dahdit.Free (Get)
+import Dahdit.Funs (getByteString, getExpect, getStaticArray, getStaticSeq, getWord8, putByteString, putFixedString,
+                    putWord8, unsafePutStaticArrayN, unsafePutStaticSeqN)
+import Dahdit.Proxy (proxyForNatF)
+import Dahdit.Sizes (ByteSized (..), StaticByteSized (..), ViaStaticByteSized (..))
+import qualified Data.ByteString as BS
+import Data.ByteString.Internal (c2w)
+import qualified Data.ByteString.Short as BSS
+import Data.ByteString.Short.Internal (ShortByteString (..))
+import Data.Default (Default (..))
+import Data.Primitive (Prim)
+import Data.Primitive.ByteArray (ByteArray (..), byteArrayFromListN)
+import Data.Primitive.PrimArray (PrimArray, replicatePrimArray)
+import Data.Proxy (Proxy (..))
+import Data.Sequence (Seq)
+import qualified Data.Sequence as Seq
+import Data.String (IsString)
+import Data.Word (Word8)
+import GHC.TypeLits (KnownNat, KnownSymbol, Nat, Symbol, natVal, symbolVal)
+
+getUntilNull :: Get (Int, [Word8])
+getUntilNull = go 0 [] where
+  go !i !racc = do
+    w <- getWord8
+    if w == 0
+      then
+        let !acc = reverse racc
+        in pure (i, acc)
+      else go (i + 1) (w:racc)
+
+mkSBS :: Int -> [Word8] -> ShortByteString
+mkSBS n bs = let !(ByteArray ba) = byteArrayFromListN n bs in SBS ba
+
+-- | Bytes terminated with null byte.
+-- NOTE: Terminated with TWO null bytes if the string is even length
+-- to align to Word16 boundaries, as required for RIFF format, for example.
+newtype TermBytes = TermBytes { unTermBytes :: ShortByteString }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, IsString)
+
+instance Default TermBytes where
+  def = TermBytes BSS.empty
+
+instance ByteSized TermBytes where
+  byteSize (TermBytes sbs) =
+    let !bc = byteSize sbs + 1
+    in if even bc then bc else bc + 1
+
+instance Binary TermBytes where
+  get = do
+    (!i, acc) <- getUntilNull
+    unless (odd i) $ do
+      w <- getWord8
+      unless (w == 0) (fail "TermBytes missing word pad")
+    let !sbs = mkSBS i acc
+    pure $! TermBytes sbs
+
+  put (TermBytes sbs) = do
+    putByteString sbs
+    putWord8 0
+    unless (odd (BSS.length sbs)) (putWord8 0)
+
+-- | A fixed-length bytestring (truncated or zero-padded on put if length does not match).
+newtype StaticBytes (n :: Nat) = StaticBytes { unStaticBytes :: ShortByteString }
+  deriving stock (Show)
+  deriving newtype (IsString)
+  deriving (ByteSized) via (ViaStaticByteSized (StaticBytes n))
+
+mkStaticBytes :: KnownNat n => Proxy n -> ShortByteString -> StaticBytes n
+mkStaticBytes prox sbs =
+  -- TODO replace with the Short versions when the lib is updated to 0.11
+  let n = fromInteger (natVal prox)
+  in if BSS.length sbs == n
+    then StaticBytes sbs
+    else
+      let bs = BSS.fromShort sbs
+          x1 = BS.take n bs
+          l = BS.length x1
+      in StaticBytes $ BSS.toShort $ if l == n
+        then x1
+        else x1 <> BS.replicate (n - l) 0
+
+normStaticBytes :: KnownNat n => StaticBytes n -> StaticBytes n
+normStaticBytes sb@(StaticBytes sbs) = mkStaticBytes (proxyForNatF sb) sbs
+
+instance KnownNat n => Eq (StaticBytes n) where
+  x == y =
+    let StaticBytes x' = normStaticBytes x
+        StaticBytes y' = normStaticBytes y
+    in x' == y'
+
+instance KnownNat n => Ord (StaticBytes n) where
+  compare x y =
+    let StaticBytes x' = normStaticBytes x
+        StaticBytes y' = normStaticBytes y
+    in compare x' y'
+
+instance Default (StaticBytes n) where
+  def = StaticBytes BSS.empty
+
+instance KnownNat n => StaticByteSized (StaticBytes n) where
+  staticByteSize _ = fromInteger (natVal (Proxy :: Proxy n))
+
+instance KnownNat n => Binary (StaticBytes n) where
+  get = fmap StaticBytes (getByteString (fromInteger (natVal (Proxy :: Proxy n))))
+  put fb@(StaticBytes sbs) = putFixedString 0 (fromInteger (natVal fb)) sbs
+
+newtype StaticSeq (n :: Nat) a = StaticSeq { unStaticSeq :: Seq a }
+  deriving stock (Show)
+  deriving newtype (Eq, Functor, Foldable)
+  deriving (ByteSized) via (ViaStaticByteSized (StaticSeq n a))
+
+instance (KnownNat n, Default a) => Default (StaticSeq n a) where
+  def = StaticSeq (Seq.replicate (fromIntegral (natVal (Proxy :: Proxy n))) def)
+
+instance (KnownNat n, StaticByteSized a) => StaticByteSized (StaticSeq n a) where
+  staticByteSize _ = fromIntegral (natVal (Proxy :: Proxy n)) * staticByteSize (Proxy :: Proxy a)
+
+instance (KnownNat n, Binary a, StaticByteSized a, Default a) => Binary (StaticSeq n a) where
+  get = fmap StaticSeq (getStaticSeq (fromIntegral (natVal (Proxy :: Proxy n))) get)
+  put = unsafePutStaticSeqN (fromIntegral (natVal (Proxy :: Proxy n))) (Just def) put . unStaticSeq
+
+newtype StaticArray (n :: Nat) a = StaticArray { unStaticArray :: PrimArray a }
+  deriving stock (Show)
+  deriving newtype (Eq)
+  deriving (ByteSized) via (ViaStaticByteSized (StaticArray n a))
+
+instance (KnownNat n, Prim a, Default a) => Default (StaticArray n a) where
+  def = StaticArray (replicatePrimArray (fromIntegral (natVal (Proxy :: Proxy n))) def)
+
+instance (KnownNat n, StaticByteSized a) => StaticByteSized (StaticArray n a) where
+  staticByteSize _ = fromIntegral (natVal (Proxy :: Proxy n)) * staticByteSize (Proxy :: Proxy a)
+
+instance (KnownNat n, Prim a, StaticByteSized a, Default a) => Binary (StaticArray n a) where
+  get = fmap StaticArray (getStaticArray (fromIntegral (natVal (Proxy :: Proxy n))))
+  put = unsafePutStaticArrayN (fromIntegral (natVal (Proxy :: Proxy n))) (Just def) . unStaticArray
+
+newtype BoolByte = BoolByte { unBoolByte :: Bool }
+  deriving stock (Show)
+  deriving newtype (Eq)
+  deriving (ByteSized) via (ViaStaticByteSized BoolByte)
+
+instance Default BoolByte where
+  def = BoolByte False
+
+instance StaticByteSized BoolByte where
+  staticByteSize _ = 1
+
+instance Binary BoolByte where
+  get = fmap (BoolByte . (/= 0)) getWord8
+  put (BoolByte b) = putWord8 (if b then 1 else 0)
+
+newtype ExactBytes (s :: Symbol) = ExactBytes { unExactBytes :: () }
+  deriving stock (Show)
+  deriving newtype (Eq)
+  deriving (ByteSized) via (ViaStaticByteSized (ExactBytes s))
+
+instance Default (ExactBytes s) where
+  def = ExactBytes ()
+
+instance KnownSymbol s => StaticByteSized (ExactBytes s) where
+  staticByteSize _ = fromIntegral (length (symbolVal (Proxy :: Proxy s)))
+
+instance KnownSymbol s => Binary (ExactBytes s) where
+  get = do
+    let !s = symbolVal (Proxy :: Proxy s)
+        !bc = fromIntegral (length s)
+        !bs = BSS.pack (fmap c2w s)
+    getExpect s (getByteString bc) bs
+    pure $! ExactBytes ()
+  put _ = do
+    let !s = symbolVal (Proxy :: Proxy s)
+    putByteString (BSS.pack (fmap c2w s))
diff --git a/src/Dahdit/Free.hs b/src/Dahdit/Free.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Free.hs
@@ -0,0 +1,144 @@
+module Dahdit.Free
+  ( GetStaticSeqF (..)
+  , GetStaticArrayF (..)
+  , GetLookAheadF (..)
+  , GetScopeF (..)
+  , ScopeMode (..)
+  , GetF (..)
+  , Get (..)
+  , PutStaticSeqF (..)
+  , PutStaticArrayF (..)
+  , PutStaticHintF (..)
+  , PutF (..)
+  , PutM (..)
+  , Put
+  ) where
+
+import Control.Monad.Free.Church (F (..))
+import Dahdit.Nums (FloatBE, FloatLE, Int16BE, Int16LE, Int24BE, Int24LE, Int32BE, Int32LE, Word16BE, Word16LE,
+                    Word24BE, Word24LE, Word32BE, Word32LE)
+import Dahdit.Sizes (ByteCount, ElementCount, StaticByteSized (..))
+import Data.ByteString.Short (ShortByteString)
+import Data.Int (Int8)
+import Data.Primitive (ByteArray, Prim)
+import Data.Primitive.PrimArray (PrimArray)
+import Data.Proxy (Proxy (..))
+import Data.Sequence (Seq)
+import Data.Word (Word8)
+
+data GetStaticSeqF a where
+  GetStaticSeqF :: (StaticByteSized z) => !ElementCount -> Get z -> (Seq z -> a) -> GetStaticSeqF a
+
+instance Functor GetStaticSeqF where
+  fmap f (GetStaticSeqF n g k) = GetStaticSeqF n g (f . k)
+
+data GetStaticArrayF a where
+  GetStaticArrayF :: (StaticByteSized z, Prim z) => !ElementCount -> Proxy z -> (PrimArray z -> a) -> GetStaticArrayF a
+
+instance Functor GetStaticArrayF where
+  fmap f (GetStaticArrayF n p k) = GetStaticArrayF n p (f . k)
+
+data GetLookAheadF a where
+  GetLookAheadF :: Get z -> (z -> a) -> GetLookAheadF a
+
+instance Functor GetLookAheadF where
+  fmap f (GetLookAheadF g k) = GetLookAheadF g (f . k)
+
+data GetScopeF a where
+  GetScopeF :: !ScopeMode -> !ByteCount -> Get z -> (z -> a) -> GetScopeF a
+
+instance Functor GetScopeF where
+  fmap f (GetScopeF sm bc g k) = GetScopeF sm bc g (f . k)
+
+data ScopeMode =
+    ScopeModeExact
+  | ScopeModeWithin
+  deriving stock (Eq, Show)
+
+data GetF a =
+    GetFWord8 (Word8 -> a)
+  | GetFInt8 (Int8 -> a)
+  | GetFWord16LE (Word16LE -> a)
+  | GetFInt16LE (Int16LE -> a)
+  | GetFWord24LE (Word24LE -> a)
+  | GetFInt24LE (Int24LE -> a)
+  | GetFWord32LE (Word32LE -> a)
+  | GetFInt32LE (Int32LE -> a)
+  | GetFFloatLE (FloatLE -> a)
+  | GetFWord16BE (Word16BE -> a)
+  | GetFInt16BE (Int16BE -> a)
+  | GetFWord24BE (Word24BE -> a)
+  | GetFInt24BE (Int24BE -> a)
+  | GetFWord32BE (Word32BE -> a)
+  | GetFInt32BE (Int32BE -> a)
+  | GetFFloatBE (FloatBE -> a)
+  | GetFShortByteString !ByteCount (ShortByteString -> a)
+  | GetFStaticSeq !(GetStaticSeqF a)
+  | GetFStaticArray !(GetStaticArrayF a)
+  | GetFByteArray !ByteCount (ByteArray -> a)
+  | GetFScope !(GetScopeF a)
+  | GetFSkip !ByteCount a
+  | GetFLookAhead !(GetLookAheadF a)
+  | GetFRemainingSize (ByteCount -> a)
+  | GetFFail !String
+  deriving stock (Functor)
+
+newtype Get a = Get { unGet :: F GetF a }
+  deriving newtype (Functor, Applicative, Monad)
+
+instance MonadFail Get where
+  fail msg = Get (F (\_ y -> y (GetFFail msg)))
+
+data PutStaticSeqF a where
+  PutStaticSeqF :: StaticByteSized z => !ElementCount -> !(Maybe z) -> (z -> Put) -> !(Seq z) -> a -> PutStaticSeqF a
+
+instance Functor PutStaticSeqF where
+  fmap f (PutStaticSeqF n z p s k) = PutStaticSeqF n z p s (f k)
+
+data PutStaticArrayF a where
+  PutStaticArrayF :: (StaticByteSized z, Prim z) => !ElementCount -> !(Maybe z) -> !(PrimArray z) -> a -> PutStaticArrayF a
+
+instance Functor PutStaticArrayF where
+  fmap f (PutStaticArrayF n z a k) = PutStaticArrayF n z a (f k)
+
+data PutStaticHintF a where
+  PutStaticHintF :: !ByteCount -> Put -> a -> PutStaticHintF a
+
+instance Functor PutStaticHintF where
+  fmap f (PutStaticHintF n p k) = PutStaticHintF n p (f k)
+
+data PutF a =
+    PutFWord8 !Word8 a
+  | PutFInt8 !Int8 a
+  | PutFWord16LE !Word16LE a
+  | PutFInt16LE !Int16LE a
+  | PutFWord24LE !Word24LE a
+  | PutFInt24LE !Int24LE a
+  | PutFWord32LE !Word32LE a
+  | PutFInt32LE !Int32LE a
+  | PutFFloatLE !FloatLE a
+  | PutFWord16BE !Word16BE a
+  | PutFInt16BE !Int16BE a
+  | PutFWord24BE !Word24BE a
+  | PutFInt24BE !Int24BE a
+  | PutFWord32BE !Word32BE a
+  | PutFInt32BE !Int32BE a
+  | PutFFloatBE !FloatBE a
+  | PutFShortByteString !ByteCount !ShortByteString a
+  | PutFStaticSeq !(PutStaticSeqF a)
+  | PutFStaticArray !(PutStaticArrayF a)
+  | PutFByteArray !ByteCount !ByteArray a
+  | PutFStaticHint !(PutStaticHintF a)
+  deriving stock (Functor)
+
+newtype PutM a = PutM { unPutM :: F PutF a }
+  deriving newtype (Functor, Applicative, Monad)
+
+instance Semigroup (PutM ()) where
+  p <> q = p *> q
+
+instance Monoid (PutM ()) where
+  mappend = (<>)
+  mempty = pure ()
+
+type Put = PutM ()
diff --git a/src/Dahdit/Funs.hs b/src/Dahdit/Funs.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Funs.hs
@@ -0,0 +1,337 @@
+module Dahdit.Funs
+  ( getWord8
+  , getInt8
+  , getWord16LE
+  , getInt16LE
+  , getWord24LE
+  , getInt24LE
+  , getWord32LE
+  , getInt32LE
+  , getFloatLE
+  , getWord16BE
+  , getInt16BE
+  , getWord24BE
+  , getInt24BE
+  , getWord32BE
+  , getInt32BE
+  , getFloatBE
+  , getByteString
+  , getSkip
+  , getExact
+  , getWithin
+  , getSeq
+  , getStaticSeq
+  , getStaticArray
+  , getByteArray
+  , getLiftedPrimArray
+  , getExpect
+  , getLookAhead
+  , getRemainingSize
+  , getRemainingString
+  , getRemainingSeq
+  , getRemainingStaticSeq
+  , getRemainingStaticArray
+  , getRemainingByteArray
+  , getRemainingLiftedPrimArray
+  , getUnfold
+  , putWord8
+  , putInt8
+  , putWord16LE
+  , putInt16LE
+  , putWord24LE
+  , putInt24LE
+  , putWord32LE
+  , putInt32LE
+  , putFloatLE
+  , putWord16BE
+  , putInt16BE
+  , putWord24BE
+  , putInt24BE
+  , putWord32BE
+  , putInt32BE
+  , putFloatBE
+  , putByteString
+  , putFixedString
+  , putSeq
+  , putStaticSeq
+  , unsafePutStaticSeqN
+  , putStaticArray
+  , unsafePutStaticArrayN
+  , putByteArray
+  , putLiftedPrimArray
+  , putStaticHint
+  ) where
+
+import Control.Monad (replicateM_, unless)
+import Control.Monad.Free.Church (F (..))
+import Dahdit.Free (Get (..), GetF (..), GetLookAheadF (..), GetScopeF (..), GetStaticArrayF (..), GetStaticSeqF (..),
+                    Put, PutF (..), PutM (..), PutStaticArrayF (..), PutStaticHintF (..), PutStaticSeqF (..),
+                    ScopeMode (..))
+import Dahdit.LiftedPrim (LiftedPrim (..), LiftedPrimArray (..))
+import Dahdit.Nums (FloatBE, FloatLE, Int16BE, Int16LE, Int24BE, Int24LE, Int32BE, Int32LE, Word16BE, Word16LE,
+                    Word24BE, Word24LE, Word32BE, Word32LE)
+import Dahdit.Proxy (proxyForF, proxyForFun)
+import Dahdit.Sizes (ByteCount (..), ElementCount (..), StaticByteSized (..))
+import Data.ByteString.Short (ShortByteString)
+import qualified Data.ByteString.Short as BSS
+import Data.Foldable (traverse_)
+import Data.Int (Int8)
+import Data.Primitive (Prim, sizeofByteArray, sizeofPrimArray)
+import Data.Primitive.ByteArray (ByteArray)
+import Data.Primitive.PrimArray (PrimArray)
+import Data.Proxy (Proxy (..))
+import Data.Sequence (Seq (..))
+import qualified Data.Sequence as Seq
+import Data.Word (Word8)
+
+getWord8 :: Get Word8
+getWord8 = Get (F (\x y -> y (GetFWord8 x)))
+
+getInt8 :: Get Int8
+getInt8 = Get (F (\x y -> y (GetFInt8 x)))
+
+getWord16LE :: Get Word16LE
+getWord16LE = Get (F (\x y -> y (GetFWord16LE x)))
+
+getInt16LE :: Get Int16LE
+getInt16LE = Get (F (\x y -> y (GetFInt16LE x)))
+
+getWord24LE :: Get Word24LE
+getWord24LE = Get (F (\x y -> y (GetFWord24LE x)))
+
+getInt24LE :: Get Int24LE
+getInt24LE = Get (F (\x y -> y (GetFInt24LE x)))
+
+getWord32LE :: Get Word32LE
+getWord32LE = Get (F (\x y -> y (GetFWord32LE x)))
+
+getInt32LE :: Get Int32LE
+getInt32LE = Get (F (\x y -> y (GetFInt32LE x)))
+
+getFloatLE :: Get FloatLE
+getFloatLE = Get (F (\x y -> y (GetFFloatLE x)))
+
+getWord16BE :: Get Word16BE
+getWord16BE = Get (F (\x y -> y (GetFWord16BE x)))
+
+getInt16BE :: Get Int16BE
+getInt16BE = Get (F (\x y -> y (GetFInt16BE x)))
+
+getWord24BE :: Get Word24BE
+getWord24BE = Get (F (\x y -> y (GetFWord24BE x)))
+
+getInt24BE :: Get Int24BE
+getInt24BE = Get (F (\x y -> y (GetFInt24BE x)))
+
+getWord32BE :: Get Word32BE
+getWord32BE = Get (F (\x y -> y (GetFWord32BE x)))
+
+getInt32BE :: Get Int32BE
+getInt32BE = Get (F (\x y -> y (GetFInt32BE x)))
+
+getFloatBE :: Get FloatBE
+getFloatBE = Get (F (\x y -> y (GetFFloatBE x)))
+
+getByteString :: ByteCount -> Get ShortByteString
+getByteString bc = Get (F (\x y -> y (GetFShortByteString bc x)))
+
+getSkip :: ByteCount -> Get ()
+getSkip bc = Get (F (\x y -> y (GetFSkip bc (x ()))))
+
+getExact :: ByteCount -> Get a -> Get a
+getExact bc g = Get (F (\x y -> y (GetFScope (GetScopeF ScopeModeExact bc g x))))
+
+getWithin :: ByteCount -> Get a -> Get a
+getWithin bc g = Get (F (\x y -> y (GetFScope (GetScopeF ScopeModeWithin bc g x))))
+
+-- | Get Seq of dynamically-sized elements
+getSeq :: ElementCount -> Get a -> Get (Seq a)
+getSeq ec g = go Empty 0 where
+  go !acc i =
+    if i == ec
+      then pure acc
+      else do
+        x <- g
+        x `seq` go (acc :|> x) (i + 1)
+
+-- | Get Seq of statically-sized elements
+getStaticSeq :: (StaticByteSized a) => ElementCount -> Get a -> Get (Seq a)
+getStaticSeq n g = Get (F (\x y -> y (GetFStaticSeq (GetStaticSeqF n g x))))
+
+-- | Get PrimArray of statically-sized elements
+getStaticArray :: (StaticByteSized a, Prim a) => ElementCount -> Get (PrimArray a)
+getStaticArray n = Get (F (\x y -> y (GetFStaticArray (GetStaticArrayF n (Proxy :: Proxy a) x))))
+
+getByteArray :: ByteCount -> Get ByteArray
+getByteArray bc = Get (F (\x y -> y (GetFByteArray bc x)))
+
+getLiftedPrimArray :: LiftedPrim a => Proxy a -> ElementCount -> Get (LiftedPrimArray a)
+getLiftedPrimArray prox ec =
+  let !bc = fromIntegral (elemSizeLifted prox * fromIntegral ec)
+  in fmap LiftedPrimArray (getByteArray bc)
+
+getLookAhead :: Get a -> Get a
+getLookAhead g = Get (F (\x y -> y (GetFLookAhead (GetLookAheadF g x))))
+
+getRemainingSize :: Get ByteCount
+getRemainingSize = Get (F (\x y -> y (GetFRemainingSize x)))
+
+getRemainingString :: Get ShortByteString
+getRemainingString = getRemainingSize >>= getByteString
+
+getRemainingSeq :: Get a -> Get (Seq a)
+getRemainingSeq g = go Empty where
+  go !acc = do
+    bc <- getRemainingSize
+    if bc == 0
+      then pure acc
+      else do
+        x <- g
+        x `seq` go (acc :|> x)
+
+getRemainingStaticSeq :: (StaticByteSized a) => Get a -> Get (Seq a)
+getRemainingStaticSeq g = do
+  let !ebc = staticByteSize (proxyForF g)
+  bc <- getRemainingSize
+  let !left = rem bc ebc
+  if left == 0
+    then do
+      let !ec = fromIntegral (div bc ebc)
+      getStaticSeq ec g
+    else fail ("Leftover bytes for remaining static seq (have " ++ show (unByteCount left) ++ ", need " ++ show (unByteCount ebc) ++ ")")
+
+getRemainingStaticArray :: (StaticByteSized a, Prim a) => Proxy a -> Get (PrimArray a)
+getRemainingStaticArray prox = do
+  let !ebc = staticByteSize prox
+  bc <- getRemainingSize
+  let !left = rem bc ebc
+  if left == 0
+    then do
+      let !ec = fromIntegral (div bc ebc)
+      getStaticArray ec
+    else fail ("Leftover bytes for remaining static array (have " ++ show (unByteCount left) ++ ", need " ++ show (unByteCount ebc) ++ ")")
+
+getRemainingByteArray :: Get ByteArray
+getRemainingByteArray = getRemainingSize >>= getByteArray
+
+getRemainingLiftedPrimArray :: (LiftedPrim a) => Proxy a -> Get (LiftedPrimArray a)
+getRemainingLiftedPrimArray prox = do
+  let !ebc = fromIntegral (elemSizeLifted prox)
+  bc <- getRemainingSize
+  let !left = rem bc ebc
+  if left == 0
+    then do
+      let !ec = fromIntegral (div bc ebc)
+      getLiftedPrimArray prox ec
+    else fail ("Leftover bytes for remaining lifted prim array (have " ++ show (unByteCount left) ++ ", need " ++ show (unByteCount ebc) ++ ")")
+
+getExpect :: (Eq a, Show a) => String -> Get a -> a -> Get ()
+getExpect typ getter expec = do
+  actual <- getter
+  unless (expec == actual)
+    (fail ("Expected " ++ " " ++ typ ++  " " ++ show expec ++ " but found " ++ show actual))
+
+getUnfold :: b -> (b -> Get (Either b a)) -> Get a
+getUnfold b0 f = go b0 where
+  go !b = do
+    eba <- f b
+    either go pure eba
+
+putWord8 :: Word8 -> Put
+putWord8 d = PutM (F (\x y -> y (PutFWord8 d (x ()))))
+
+putInt8 :: Int8 -> Put
+putInt8 d = PutM (F (\x y -> y (PutFInt8 d (x ()))))
+
+putWord16LE :: Word16LE -> Put
+putWord16LE d = PutM (F (\x y -> y (PutFWord16LE d (x ()))))
+
+putInt16LE :: Int16LE -> Put
+putInt16LE d = PutM (F (\x y -> y (PutFInt16LE d (x ()))))
+
+putWord24LE :: Word24LE -> Put
+putWord24LE d = PutM (F (\x y -> y (PutFWord24LE d (x ()))))
+
+putInt24LE :: Int24LE -> Put
+putInt24LE d = PutM (F (\x y -> y (PutFInt24LE d (x ()))))
+
+putWord32LE :: Word32LE -> Put
+putWord32LE d = PutM (F (\x y -> y (PutFWord32LE d (x ()))))
+
+putInt32LE :: Int32LE -> Put
+putInt32LE d = PutM (F (\x y -> y (PutFInt32LE d (x ()))))
+
+putFloatLE :: FloatLE -> Put
+putFloatLE d = PutM (F (\x y -> y (PutFFloatLE d (x ()))))
+
+putWord16BE :: Word16BE -> Put
+putWord16BE d = PutM (F (\x y -> y (PutFWord16BE d (x ()))))
+
+putInt16BE :: Int16BE -> Put
+putInt16BE d = PutM (F (\x y -> y (PutFInt16BE d (x ()))))
+
+putWord24BE :: Word24BE -> Put
+putWord24BE d = PutM (F (\x y -> y (PutFWord24BE d (x ()))))
+
+putInt24BE :: Int24BE -> Put
+putInt24BE d = PutM (F (\x y -> y (PutFInt24BE d (x ()))))
+
+putWord32BE :: Word32BE -> Put
+putWord32BE d = PutM (F (\x y -> y (PutFWord32BE d (x ()))))
+
+putInt32BE :: Int32BE -> Put
+putInt32BE d = PutM (F (\x y -> y (PutFInt32BE d (x ()))))
+
+putFloatBE :: FloatBE -> Put
+putFloatBE d = PutM (F (\x y -> y (PutFFloatBE d (x ()))))
+
+putByteString :: ShortByteString -> Put
+putByteString sbs =
+  let !bc = fromIntegral (BSS.length sbs)
+  in PutM (F (\x y -> y (PutFShortByteString bc sbs (x ()))))
+
+putFixedString :: Word8 -> ByteCount -> ShortByteString -> Put
+putFixedString pad bc sbs = do
+  unless (bc == 0) $ do
+    let !len = fromIntegral bc
+        !lenSbs = BSS.length sbs
+        !mostLen = min len lenSbs
+        !mostBc = fromIntegral mostLen
+    PutM (F (\x y -> y (PutFShortByteString mostBc sbs (x ()))))
+    let !diff = len - lenSbs
+    unless (diff <= 0) (replicateM_ diff (putWord8 pad))
+
+-- | Put Seq of dynamically-sized elements
+putSeq :: (a -> Put) -> Seq a -> Put
+putSeq = traverse_
+
+-- | Put Seq of statically-sized elements
+putStaticSeq :: StaticByteSized a => (a -> Put) -> Seq a -> Put
+putStaticSeq p s =
+  let !n = fromIntegral (Seq.length s)
+  in unsafePutStaticSeqN n Nothing p s
+
+unsafePutStaticSeqN :: StaticByteSized a => ElementCount -> Maybe a -> (a -> Put) -> Seq a -> Put
+unsafePutStaticSeqN n mz p s = PutM (F (\x y -> y (PutFStaticSeq (PutStaticSeqF n mz p s (x ())))))
+
+-- | Put Array of statically-sized elements
+putStaticArray :: (StaticByteSized a, Prim a) => PrimArray a -> Put
+putStaticArray a =
+  let !n = fromIntegral (sizeofPrimArray a)
+  in unsafePutStaticArrayN n Nothing a
+
+unsafePutStaticArrayN :: (StaticByteSized a, Prim a) => ElementCount -> Maybe a -> PrimArray a -> Put
+unsafePutStaticArrayN n mz a = PutM (F (\x y -> y (PutFStaticArray (PutStaticArrayF n mz a (x ())))))
+
+putByteArray :: ByteArray -> Put
+putByteArray arr =
+  let !bc = fromIntegral (sizeofByteArray arr)
+  in PutM (F (\x y -> y (PutFByteArray bc arr (x ()))))
+
+putLiftedPrimArray :: LiftedPrimArray a -> Put
+putLiftedPrimArray = putByteArray . unLiftedPrimArray
+
+putStaticHint :: StaticByteSized a => (a -> Put) -> a -> Put
+putStaticHint p =
+  let !bc = staticByteSize (proxyForFun p)
+  in \a -> PutM (F (\x y -> y (PutFStaticHint (PutStaticHintF bc (p a) (x ())))))
diff --git a/src/Dahdit/Generic.hs b/src/Dahdit/Generic.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Generic.hs
@@ -0,0 +1,98 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+module Dahdit.Generic
+  ( ViaGeneric (..)
+  , ViaStaticGeneric (..)
+  ) where
+
+import Control.Applicative (liftA2)
+import Dahdit.Binary (Binary (..))
+import Dahdit.Free (Get, Put)
+import Dahdit.Funs (putStaticHint)
+import Dahdit.Sizes (ByteCount, ByteSized (..), StaticByteSized (..))
+import Data.Kind (Type)
+import Data.Proxy (Proxy (..))
+import GHC.Generics ((:*:) (..), Generic (..), K1 (..), M1 (..), U1 (..))
+
+-- | Use: deriving (ByteSized, Binary) via (ViaGeneric Foo)
+newtype ViaGeneric a = ViaGeneric { unViaGeneric :: a }
+
+-- | Use: deriving (ByteSized, StaticByteSized, Binary) via (ViaStaticGeneric Foo)
+newtype ViaStaticGeneric a = ViaStaticGeneric { unViaStaticGeneric :: a }
+
+-- ByteSized:
+
+class GByteSized f where
+  gbyteSize :: f a -> ByteCount
+
+-- Unit
+instance GByteSized U1 where
+  gbyteSize _ = 0
+
+-- Product
+instance (GByteSized a, GByteSized b) => GByteSized (a :*: b) where
+  gbyteSize (x :*: y) = gbyteSize x + gbyteSize y
+
+-- Metadata
+instance GByteSized a => GByteSized (M1 i c a) where
+  gbyteSize = gbyteSize . unM1
+
+-- Field
+instance ByteSized a => GByteSized (K1 i a) where
+  gbyteSize = byteSize . unK1
+
+instance (Generic t, GByteSized (Rep t)) => ByteSized (ViaGeneric t) where
+  byteSize = gbyteSize . from . unViaGeneric
+
+instance (Generic t, GByteSized (Rep t)) => ByteSized (ViaStaticGeneric t) where
+  byteSize = gbyteSize . from . unViaStaticGeneric
+
+-- StaticByteSized:
+
+class GByteSized f => GStaticByteSized (f :: Type -> Type) where
+  gstaticByteSize :: Proxy f -> ByteCount
+
+instance GStaticByteSized U1 where
+  gstaticByteSize _ = 0
+
+instance (GStaticByteSized a, GStaticByteSized b) => GStaticByteSized (a :*: b) where
+  gstaticByteSize _ = gstaticByteSize (Proxy :: Proxy a) + gstaticByteSize (Proxy :: Proxy b)
+
+instance GStaticByteSized a => GStaticByteSized (M1 i c a) where
+  gstaticByteSize _ = gstaticByteSize (Proxy :: Proxy a)
+
+instance StaticByteSized a => GStaticByteSized (K1 i a) where
+  gstaticByteSize _ = staticByteSize (Proxy :: Proxy a)
+
+instance (Generic t, GStaticByteSized (Rep t)) => StaticByteSized (ViaStaticGeneric t) where
+  staticByteSize _ = gstaticByteSize (Proxy :: Proxy (Rep t))
+
+-- Binary:
+
+class GByteSized f => GBinary (f :: Type -> Type) where
+  gget :: Get (f a)
+  gput :: f a -> Put
+
+instance GBinary U1 where
+  gget = pure U1
+  gput _ = pure ()
+
+instance (GBinary a, GBinary b) => GBinary (a :*: b) where
+  gget = liftA2 (:*:) gget gget
+  gput (x :*: y) = gput x *> gput y
+
+instance GBinary a => GBinary (M1 i c a) where
+  gget = fmap M1 gget
+  gput = gput . unM1
+
+instance Binary a => GBinary (K1 i a) where
+  gget = fmap K1 get
+  gput = put . unK1
+
+instance (Generic t, GBinary (Rep t)) => Binary (ViaGeneric t) where
+  get = fmap (ViaGeneric . to) gget
+  put = gput . from . unViaGeneric
+
+instance (Generic t, GStaticByteSized (Rep t), GBinary (Rep t)) => Binary (ViaStaticGeneric t) where
+  get = fmap (ViaStaticGeneric . to) gget
+  put = putStaticHint (gput . from . unViaStaticGeneric)
diff --git a/src/Dahdit/Internal.hs b/src/Dahdit/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Internal.hs
@@ -0,0 +1,86 @@
+module Dahdit.Internal where
+
+import Data.Bits (Bits (..))
+import Data.Int (Int16, Int32, Int8)
+import Data.ShortWord (Int24, Word24)
+import Data.Word (Word16, Word32, Word8)
+import GHC.Float (castFloatToWord32, castWord32ToFloat)
+
+newtype ViaFromIntegral x y = ViaFromIntegral { unViaFromIntegral :: y }
+  deriving newtype (Num)
+
+-- Types that can swap endianness - swapEndian is its own inverse
+class Num w => SwapEndian w where
+  swapEndian :: w -> w
+
+instance (SwapEndian x, Integral x, Integral y) => SwapEndian (ViaFromIntegral x y) where
+  swapEndian = ViaFromIntegral . fromIntegral @x @y . swapEndian . fromIntegral @y @x . unViaFromIntegral
+
+instance SwapEndian Word8 where
+  swapEndian = id
+
+instance SwapEndian Int8 where
+  swapEndian = id
+
+instance SwapEndian Word16 where
+  swapEndian w =
+    let (b0, b1) = unMkWord16LE w
+    in mkWord16LE b1 b0
+
+deriving via (ViaFromIntegral Word16 Int16) instance SwapEndian Int16
+
+instance SwapEndian Word24 where
+  swapEndian w =
+    let (b0, b1, b2) = unMkWord24LE w
+    in mkWord24LE b2 b1 b0
+
+deriving via (ViaFromIntegral Word24 Int24) instance SwapEndian Int24
+
+instance SwapEndian Word32 where
+  swapEndian w =
+    let (b0, b1, b2, b3) = unMkWord32LE w
+    in mkWord32LE b3 b2 b1 b0
+
+deriving via (ViaFromIntegral Word32 Int32) instance SwapEndian Int32
+
+instance SwapEndian Float where
+  swapEndian w =
+    let (b0, b1, b2, b3) = unMkFloatLE w
+    in mkFloatLE b3 b2 b1 b0
+
+mkWord16LE :: Word8 -> Word8 -> Word16
+mkWord16LE b0 b1 = (fromIntegral b1 `unsafeShiftL` 8) .|. fromIntegral b0
+
+unMkWord16LE :: Word16 -> (Word8, Word8)
+unMkWord16LE w =
+  let !b0 = fromIntegral w
+      !b1 = fromIntegral (w `shiftR` 8)
+  in (b0, b1)
+
+mkWord24LE :: Word8 -> Word8 -> Word8 -> Word24
+mkWord24LE b0 b1 b2 = fromIntegral (mkWord32LE b0 b1 b2 0)
+
+unMkWord24LE :: Word24 -> (Word8, Word8, Word8)
+unMkWord24LE w =
+  let !v = fromIntegral w
+      (b0, b1, b2, _) = unMkWord32LE v
+  in (b0, b1, b2)
+
+mkWord32LE :: Word8 -> Word8 -> Word8 -> Word8 -> Word32
+mkWord32LE b0 b1 b2 b3 =
+  (fromIntegral b3 `unsafeShiftL` 24) .|. (fromIntegral b2 `unsafeShiftL` 16) .|.
+    (fromIntegral b1 `unsafeShiftL` 8) .|. fromIntegral b0
+
+unMkWord32LE :: Word32 -> (Word8, Word8, Word8, Word8)
+unMkWord32LE w =
+  let !b0 = fromIntegral w
+      !b1 = fromIntegral (w `shiftR` 8)
+      !b2 = fromIntegral (w `shiftR` 16)
+      !b3 = fromIntegral (w `shiftR` 24)
+  in (b0, b1, b2, b3)
+
+mkFloatLE :: Word8 -> Word8 -> Word8 -> Word8 -> Float
+mkFloatLE b0 b1 b2 b3 = castWord32ToFloat (mkWord32LE b0 b1 b2 b3)
+
+unMkFloatLE :: Float -> (Word8, Word8, Word8, Word8)
+unMkFloatLE f = unMkWord32LE (castFloatToWord32 f)
diff --git a/src/Dahdit/LiftedPrim.hs b/src/Dahdit/LiftedPrim.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/LiftedPrim.hs
@@ -0,0 +1,126 @@
+module Dahdit.LiftedPrim
+  ( LiftedPrim (..)
+  , LiftedPrimArray (..)
+  , MutableLiftedPrimArray (..)
+  , emptyLiftedPrimArray
+  , indexLiftedPrimArray
+  , writeLiftedPrimArray
+  , freezeLiftedPrimArray
+  , thawLiftedPrimArray
+  , unsafeFreezeLiftedPrimArray
+  , unsafeThawLiftedPrimArray
+  , liftedPrimArrayFromListN
+  , liftedPrimArrayFromList
+  , generateLiftedPrimArray
+  , sizeofLiftedPrimArray
+  , cloneLiftedPrimArray
+  ) where
+
+import Control.Monad.Primitive (PrimMonad (..))
+import Dahdit.Internal (ViaFromIntegral (..))
+import Dahdit.Proxy (proxyForF)
+import Data.Default (Default (..))
+import Data.Foldable (for_)
+import Data.Int (Int8)
+import Data.Primitive.ByteArray (ByteArray, MutableByteArray, cloneByteArray, emptyByteArray, freezeByteArray,
+                                 indexByteArray, newByteArray, runByteArray, sizeofByteArray, thawByteArray,
+                                 unsafeFreezeByteArray, unsafeThawByteArray, writeByteArray)
+import Data.Proxy (Proxy (..))
+import Data.STRef (modifySTRef', newSTRef, readSTRef)
+import Data.Word (Word8)
+
+-- | This is a stripped-down version of 'Prim' that is possible for a human to implement.
+-- It's all about reading and writing structures from byte arrays.
+class LiftedPrim a where
+  elemSizeLifted :: Proxy a -> Int
+  indexByteArrayLiftedInBytes :: ByteArray -> Int -> a
+  indexByteArrayLiftedInElems :: ByteArray -> Int -> a
+  indexByteArrayLiftedInElems arr pos =
+    let !sz = elemSizeLifted (Proxy :: Proxy a)
+    in indexByteArrayLiftedInBytes arr (pos * sz)
+  writeByteArrayLiftedInBytes :: PrimMonad m => a -> MutableByteArray (PrimState m) -> Int -> m ()
+  writeByteArrayLiftedInElems :: PrimMonad m => a -> MutableByteArray (PrimState m) -> Int -> m ()
+  writeByteArrayLiftedInElems val arr pos =
+    let !sz = elemSizeLifted (Proxy :: Proxy a)
+    in writeByteArrayLiftedInBytes val arr (pos * sz)
+
+instance LiftedPrim Word8 where
+  elemSizeLifted _ = 1
+  indexByteArrayLiftedInBytes = indexByteArray
+  writeByteArrayLiftedInBytes val arr pos = writeByteArray arr pos val
+
+instance LiftedPrim Int8 where
+  elemSizeLifted _ = 1
+  indexByteArrayLiftedInBytes = indexByteArray
+  indexByteArrayLiftedInElems = indexByteArray
+  writeByteArrayLiftedInBytes val arr pos = writeByteArray arr pos val
+  writeByteArrayLiftedInElems val arr pos = writeByteArray arr pos val
+
+-- | NOTE: Relies on same byte width of both types!
+instance (Num x, Integral x, LiftedPrim x, Num y, Integral y) => LiftedPrim (ViaFromIntegral x y) where
+  elemSizeLifted _ = elemSizeLifted (Proxy :: Proxy x)
+  indexByteArrayLiftedInBytes arr pos = ViaFromIntegral (fromIntegral (indexByteArrayLiftedInBytes arr pos :: x))
+  writeByteArrayLiftedInBytes val arr pos = let !x = fromIntegral (unViaFromIntegral val) :: x in writeByteArrayLiftedInBytes x arr pos
+
+newtype LiftedPrimArray a = LiftedPrimArray { unLiftedPrimArray :: ByteArray }
+  deriving stock (Show)
+  deriving newtype (Eq, Semigroup, Monoid)
+
+instance Default (LiftedPrimArray a) where
+  def = emptyLiftedPrimArray
+
+newtype MutableLiftedPrimArray m a = MutableLiftedPrimArray { unMutableLiftedPrimArray :: MutableByteArray m }
+  deriving newtype (Eq)
+
+emptyLiftedPrimArray :: LiftedPrimArray a
+emptyLiftedPrimArray = LiftedPrimArray emptyByteArray
+
+indexLiftedPrimArray :: LiftedPrim a => LiftedPrimArray a -> Int -> a
+indexLiftedPrimArray (LiftedPrimArray arr) = indexByteArrayLiftedInElems arr
+
+writeLiftedPrimArray :: (LiftedPrim a, PrimMonad m) => a -> MutableLiftedPrimArray (PrimState m) a -> Int -> m ()
+writeLiftedPrimArray val (MutableLiftedPrimArray arr) = writeByteArrayLiftedInElems val arr
+
+freezeLiftedPrimArray :: PrimMonad m => MutableLiftedPrimArray (PrimState m) a -> Int -> Int -> m (LiftedPrimArray a)
+freezeLiftedPrimArray (MutableLiftedPrimArray arr) off len = fmap LiftedPrimArray (freezeByteArray arr off len)
+
+unsafeFreezeLiftedPrimArray :: PrimMonad m => MutableLiftedPrimArray (PrimState m) a -> m (LiftedPrimArray a)
+unsafeFreezeLiftedPrimArray (MutableLiftedPrimArray arr) = fmap LiftedPrimArray (unsafeFreezeByteArray arr)
+
+thawLiftedPrimArray :: PrimMonad m => LiftedPrimArray a -> Int -> Int -> m (MutableLiftedPrimArray (PrimState m) a)
+thawLiftedPrimArray (LiftedPrimArray arr) off len = fmap MutableLiftedPrimArray (thawByteArray arr off len)
+
+unsafeThawLiftedPrimArray :: PrimMonad m => LiftedPrimArray a -> m (MutableLiftedPrimArray (PrimState m) a)
+unsafeThawLiftedPrimArray (LiftedPrimArray arr) = fmap MutableLiftedPrimArray (unsafeThawByteArray arr)
+
+liftedPrimArrayFromListN :: LiftedPrim a => Int -> [a] -> LiftedPrimArray a
+liftedPrimArrayFromListN n xs = LiftedPrimArray $ runByteArray $ do
+  let !elemSize = elemSizeLifted (proxyForF xs)
+      !len = n * elemSize
+  arr <- newByteArray len
+  offRef <- newSTRef 0
+  for_ xs $ \x -> do
+    off <- readSTRef offRef
+    writeByteArrayLiftedInBytes x arr off
+    modifySTRef' offRef (elemSize+)
+  pure arr
+
+liftedPrimArrayFromList :: LiftedPrim a => [a] -> LiftedPrimArray a
+liftedPrimArrayFromList xs = liftedPrimArrayFromListN (length xs) xs
+
+generateLiftedPrimArray :: LiftedPrim a => Int -> (Int -> a) -> LiftedPrimArray a
+generateLiftedPrimArray n f = liftedPrimArrayFromListN n (fmap f [0 .. n - 1])
+
+sizeofLiftedPrimArray :: LiftedPrim a => LiftedPrimArray a -> Int
+sizeofLiftedPrimArray pa@(LiftedPrimArray arr) =
+  let !elemSize = elemSizeLifted (proxyForF pa)
+      !arrSize = sizeofByteArray arr
+  in div arrSize elemSize
+
+cloneLiftedPrimArray :: LiftedPrim a => LiftedPrimArray a -> Int -> Int -> LiftedPrimArray a
+cloneLiftedPrimArray pa@(LiftedPrimArray arr) off len =
+  let !elemSize = elemSizeLifted (proxyForF pa)
+      !byteOff = off * elemSize
+      !byteLen = len * elemSize
+      !arr' = cloneByteArray arr byteOff byteLen
+  in LiftedPrimArray arr'
diff --git a/src/Dahdit/Nums.hs b/src/Dahdit/Nums.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Nums.hs
@@ -0,0 +1,220 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | Derived instances rely on the host system being little-endian.
+-- If it's not, well... some CPP is in order.
+module Dahdit.Nums
+  ( EndianPair (..)
+  , Word16LE (..)
+  , Int16LE (..)
+  , Word24LE (..)
+  , Int24LE (..)
+  , Word32LE (..)
+  , Int32LE (..)
+  , FloatLE (..)
+  , Word16BE (..)
+  , Int16BE (..)
+  , Word24BE (..)
+  , Int24BE (..)
+  , Word32BE (..)
+  , Int32BE (..)
+  , FloatBE (..)
+  ) where
+
+import Dahdit.Internal (ViaFromIntegral (..), mkFloatLE, mkWord16LE, mkWord24LE, mkWord32LE, swapEndian, unMkFloatLE,
+                        unMkWord16LE, unMkWord24LE, unMkWord32LE)
+import Dahdit.LiftedPrim (LiftedPrim (..))
+import Data.Bits (Bits (..))
+import Data.Default (Default (..))
+import Data.Int (Int16, Int32, Int8)
+import Data.Primitive.ByteArray (indexByteArray, writeByteArray)
+import Data.Primitive.Types (Prim (..))
+import Data.Proxy (Proxy (..))
+import Data.ShortWord (Int24, Word24)
+import Data.Word (Word16, Word32, Word8)
+
+class (Num le, Num be) => EndianPair le be | le -> be, be -> le where
+  toLittleEndian :: be -> le
+  toBigEndian :: le -> be
+
+newtype Word16LE = Word16LE { unWord16LE :: Word16 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default, Prim)
+
+instance LiftedPrim Word16LE where
+  elemSizeLifted _ = 2
+
+  indexByteArrayLiftedInBytes arr pos =
+    let !b0 = indexByteArray arr pos
+        !b1 = indexByteArray arr (pos + 1)
+    in Word16LE (mkWord16LE b0 b1)
+
+  writeByteArrayLiftedInBytes w arr pos =
+    let !(b0, b1) = unMkWord16LE (unWord16LE w)
+    in writeByteArray arr pos b0 *> writeByteArray arr (pos + 1) b1
+
+newtype Int16LE = Int16LE { unInt16LE :: Int16 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default, Prim)
+  deriving (LiftedPrim) via (ViaFromIntegral Word16LE Int16LE)
+
+newtype Word24LE = Word24LE { unWord24LE :: Word24 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits)
+
+instance Default Word24LE where
+  def = 0
+
+instance LiftedPrim Word24LE where
+  elemSizeLifted _ = 3
+
+  indexByteArrayLiftedInBytes arr pos =
+    let !b0 = indexByteArray arr pos
+        !b1 = indexByteArray arr (pos + 1)
+        !b2 = indexByteArray arr (pos + 2)
+    in Word24LE (mkWord24LE b0 b1 b2)
+
+  writeByteArrayLiftedInBytes w arr pos = do
+    let !(b0, b1, b2) = unMkWord24LE (unWord24LE w)
+    writeByteArray arr pos b0
+    writeByteArray arr (pos + 1) b1
+    writeByteArray arr (pos + 2) b2
+
+newtype Int24LE = Int24LE { unInt24LE :: Int24 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits)
+  deriving (LiftedPrim) via (ViaFromIntegral Word24LE Int24LE)
+
+instance Default Int24LE where
+  def = 0
+
+newtype Word32LE = Word32LE { unWord32LE :: Word32 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default, Prim)
+
+instance LiftedPrim Word32LE where
+  elemSizeLifted _ = 4
+
+  indexByteArrayLiftedInBytes arr pos =
+    let !b0 = indexByteArray arr pos
+        !b1 = indexByteArray arr (pos + 1)
+        !b2 = indexByteArray arr (pos + 2)
+        !b3 = indexByteArray arr (pos + 3)
+    in Word32LE (mkWord32LE b0 b1 b2 b3)
+
+  writeByteArrayLiftedInBytes w arr pos = do
+    let !(b0, b1, b2, b3) = unMkWord32LE (unWord32LE w)
+    writeByteArray arr pos b0
+    writeByteArray arr (pos + 1) b1
+    writeByteArray arr (pos + 2) b2
+    writeByteArray arr (pos + 3) b3
+
+newtype Int32LE = Int32LE { unInt32LE :: Int32 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default, Prim)
+  deriving (LiftedPrim) via (ViaFromIntegral Word32LE Int32LE)
+
+newtype FloatLE = FloatLE { unFloatLE :: Float }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Real, Fractional, Floating, RealFrac, Default, Prim)
+
+instance LiftedPrim FloatLE where
+  elemSizeLifted _ = 4
+
+  indexByteArrayLiftedInBytes arr pos =
+    let !b0 = indexByteArray arr pos
+        !b1 = indexByteArray arr (pos + 1)
+        !b2 = indexByteArray arr (pos + 2)
+        !b3 = indexByteArray arr (pos + 3)
+    in FloatLE (mkFloatLE b0 b1 b2 b3)
+
+  writeByteArrayLiftedInBytes f arr pos = do
+    let !(b0, b1, b2, b3) = unMkFloatLE (unFloatLE f)
+    writeByteArray arr pos b0
+    writeByteArray arr (pos + 1) b1
+    writeByteArray arr (pos + 2) b2
+    writeByteArray arr (pos + 3) b3
+
+newtype Word16BE = Word16BE { unWord16BE :: Word16 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default)
+  deriving (LiftedPrim) via (ViaEndianPair Word16LE Word16BE)
+
+newtype Int16BE = Int16BE { unInt16BE :: Int16 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default)
+  deriving (LiftedPrim) via (ViaEndianPair Int16LE Int16BE)
+
+newtype Word24BE = Word24BE { unWord24BE :: Word24 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits)
+  deriving (LiftedPrim) via (ViaEndianPair Word24LE Word24BE)
+
+instance Default Word24BE where
+  def = 0
+
+newtype Int24BE = Int24BE { unInt24BE :: Int24 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits)
+  deriving (LiftedPrim) via (ViaEndianPair Int24LE Int24BE)
+
+instance Default Int24BE where
+  def = 0
+
+newtype Word32BE = Word32BE { unWord32BE :: Word32 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default)
+  deriving (LiftedPrim) via (ViaEndianPair Word32LE Word32BE)
+
+newtype Int32BE = Int32BE { unInt32BE :: Int32 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bits, Default)
+  deriving (LiftedPrim) via (ViaEndianPair Int32LE Int32BE)
+
+newtype FloatBE = FloatBE { unFloatBE :: Float }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Real, Fractional, Floating, RealFrac, Default, Prim)
+  deriving (LiftedPrim) via (ViaEndianPair FloatLE FloatBE)
+
+instance EndianPair Word8 Word8 where
+  toLittleEndian = id
+  toBigEndian = id
+
+instance EndianPair Int8 Int8 where
+  toLittleEndian = id
+  toBigEndian = id
+
+instance EndianPair Word16LE Word16BE where
+  toLittleEndian = Word16LE . swapEndian . unWord16BE
+  toBigEndian = Word16BE . swapEndian . unWord16LE
+
+instance EndianPair Int16LE Int16BE where
+  toLittleEndian = Int16LE . swapEndian . unInt16BE
+  toBigEndian = Int16BE . swapEndian . unInt16LE
+
+instance EndianPair Word24LE Word24BE where
+  toLittleEndian = Word24LE . swapEndian . unWord24BE
+  toBigEndian = Word24BE . swapEndian . unWord24LE
+
+instance EndianPair Int24LE Int24BE where
+  toLittleEndian = Int24LE . swapEndian . unInt24BE
+  toBigEndian = Int24BE . swapEndian . unInt24LE
+
+instance EndianPair Word32LE Word32BE where
+  toLittleEndian = Word32LE . swapEndian . unWord32BE
+  toBigEndian = Word32BE . swapEndian . unWord32LE
+
+instance EndianPair Int32LE Int32BE where
+  toLittleEndian = Int32LE . swapEndian . unInt32BE
+  toBigEndian = Int32BE . swapEndian . unInt32LE
+
+instance EndianPair FloatLE FloatBE where
+  toLittleEndian = FloatLE . swapEndian . unFloatBE
+  toBigEndian = FloatBE . swapEndian . unFloatLE
+
+newtype ViaEndianPair le be = ViaEndianPair { unViaEndianPair :: be }
+
+instance (LiftedPrim le, EndianPair le be) => LiftedPrim (ViaEndianPair le be) where
+  elemSizeLifted _ = elemSizeLifted (Proxy :: Proxy le)
+  indexByteArrayLiftedInBytes arr pos = ViaEndianPair (toBigEndian (indexByteArrayLiftedInBytes arr pos))
+  writeByteArrayLiftedInBytes (ViaEndianPair bval) = writeByteArrayLiftedInBytes (toLittleEndian bval)
diff --git a/src/Dahdit/Proxy.hs b/src/Dahdit/Proxy.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Proxy.hs
@@ -0,0 +1,21 @@
+module Dahdit.Proxy
+  ( proxyFor
+  , proxyForF
+  , proxyForFun
+  , proxyForNatF
+  ) where
+
+import Data.Proxy (Proxy (..))
+import GHC.TypeLits (Nat)
+
+proxyFor :: a -> Proxy a
+proxyFor _ = Proxy
+
+proxyForF :: f a -> Proxy a
+proxyForF _ = Proxy
+
+proxyForFun :: (a -> x) -> Proxy a
+proxyForFun _ = Proxy
+
+proxyForNatF :: forall (n :: Nat) f. f n -> Proxy n
+proxyForNatF _ = Proxy
diff --git a/src/Dahdit/Run.hs b/src/Dahdit/Run.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Run.hs
@@ -0,0 +1,432 @@
+module Dahdit.Run
+  ( GetError (..)
+  , prettyGetError
+  , runGet
+  , runGetIO
+  , runGetFile
+  , runCount
+  , runPut
+  , runPutFile
+  ) where
+
+import Control.Applicative (Alternative (..))
+import Control.Exception (Exception (..), throwIO)
+import Control.Monad (replicateM_, unless)
+import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)
+import Control.Monad.Free.Church (F (..))
+import Control.Monad.Reader (MonadReader (..), ReaderT (..), asks)
+import Control.Monad.ST.Strict (ST, runST)
+import Control.Monad.State.Strict (MonadState, State, runState)
+import qualified Control.Monad.State.Strict as State
+import Control.Monad.Trans (lift)
+import Control.Monad.Trans.Free (FreeT (..), iterT, wrap)
+import Control.Monad.Trans.Maybe (MaybeT (..))
+import Dahdit.Free (Get (..), GetF (..), GetLookAheadF (..), GetScopeF (..), GetStaticArrayF (..), GetStaticSeqF (..),
+                    Put, PutF (..), PutM (..), PutStaticArrayF (..), PutStaticHintF (..), PutStaticSeqF (..),
+                    ScopeMode (..))
+import Dahdit.LiftedPrim (LiftedPrim (..))
+import Dahdit.Nums (FloatBE, FloatLE, Int16BE, Int16LE (..), Int24BE, Int24LE, Int32BE, Int32LE, Word16BE,
+                    Word16LE (..), Word24BE, Word24LE, Word32BE, Word32LE)
+import Dahdit.Proxy (proxyForF)
+import Dahdit.Sizes (ByteCount (..), staticByteSize)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Short as BSS
+import Data.ByteString.Short.Internal (ShortByteString (..))
+import Data.Foldable (for_, toList)
+import Data.Int (Int8)
+import Data.Maybe (fromJust)
+import Data.Primitive.ByteArray (ByteArray (..), MutableByteArray, cloneByteArray, copyByteArray, indexByteArray,
+                                 newByteArray, setByteArray, unsafeFreezeByteArray, writeByteArray)
+import Data.Primitive.PrimArray (PrimArray (..), sizeofPrimArray)
+import qualified Data.Sequence as Seq
+import Data.STRef.Strict (STRef, newSTRef, readSTRef, writeSTRef)
+import Data.Word (Word8)
+
+-- Sizes:
+
+getStaticSeqSize :: GetStaticSeqF a -> Int
+getStaticSeqSize (GetStaticSeqF ec g _) =
+  let !z = fromIntegral (staticByteSize (proxyForF g))
+  in z * fromIntegral ec
+
+getStaticArraySize :: GetStaticArrayF a -> Int
+getStaticArraySize (GetStaticArrayF n prox _) =
+  let !z = fromIntegral (staticByteSize prox)
+  in z * fromIntegral n
+
+putStaticSeqSize :: PutStaticSeqF a -> Int
+putStaticSeqSize (PutStaticSeqF n _ _ s _) =
+  let !z = fromIntegral (staticByteSize (proxyForF s))
+  in z * fromIntegral n
+
+putStaticArrayElemSize :: PutStaticArrayF a -> Int
+putStaticArrayElemSize (PutStaticArrayF _ _ a _) =
+  fromIntegral (staticByteSize (proxyForF a))
+
+putStaticArraySize :: PutStaticArrayF a -> Int
+putStaticArraySize (PutStaticArrayF n _ a _) =
+  let !z = fromIntegral (staticByteSize (proxyForF a))
+  in z * fromIntegral n
+
+-- Get:
+
+data GetError =
+    GetErrorParseNeed !String !ByteCount !ByteCount
+  | GetErrorScopedMismatch !ByteCount !ByteCount
+  | GetErrorFail !String
+  deriving stock (Eq, Show)
+
+instance Exception GetError where
+  displayException = prettyGetError
+
+prettyGetError :: GetError -> String
+prettyGetError = \case
+  GetErrorParseNeed nm ac bc -> "End of input parsing " ++ nm ++ " (have " ++ show (unByteCount ac) ++ " bytes, need " ++ show (unByteCount bc) ++ ")"
+  GetErrorScopedMismatch ac bc -> "Did not parse enough scoped input (read " ++ show (unByteCount ac) ++ " bytes, expected " ++ show (unByteCount bc) ++ ")"
+  GetErrorFail msg -> "User error: " ++ msg
+
+data GetEnv s = GetEnv
+  { geLen :: !Int
+  , gePos :: !(STRef s Int)
+  , geArray :: !ByteArray
+  }
+
+newGetEnv :: ShortByteString -> ST s (GetEnv s)
+newGetEnv sbs@(SBS arr) = do
+  let !len = BSS.length sbs
+  pos <- newSTRef 0
+  pure $! GetEnv len pos (ByteArray arr)
+
+newtype GetEff s a = GetEff { unGetEff :: ReaderT (GetEnv s) (ExceptT GetError (ST s)) a }
+  deriving newtype (Functor, Applicative, Monad, MonadReader (GetEnv s), MonadError GetError)
+
+runGetEff :: GetEff s a -> GetEnv s -> ST s (Either GetError a)
+runGetEff m l = runExceptT (runReaderT (unGetEff m) l)
+
+instance MonadFail (GetEff s) where
+  fail = GetEff . throwError . GetErrorFail
+
+stGetEff :: ST s a -> GetEff s a
+stGetEff = GetEff . lift . lift
+
+newtype GetRun s a = GetRun { unGetRun :: FreeT GetF (GetEff s) a }
+  deriving newtype (Functor, Applicative, Monad)
+
+guardReadBytes :: String -> Int -> GetEff s Int
+guardReadBytes nm bc = do
+  GetEnv l posRef _ <- ask
+  pos <- stGetEff (readSTRef posRef)
+  let !ac = l - pos
+  if bc > ac
+    then throwError (GetErrorParseNeed nm (fromIntegral ac) (fromIntegral bc))
+    else pure pos
+
+readBytes :: String -> Int -> (ByteArray -> Int -> a) -> GetEff s a
+readBytes nm bc f = do
+  pos <- guardReadBytes nm bc
+  GetEnv _ posRef arr <- ask
+  stGetEff $ do
+    let !a = f arr pos
+        !newPos = pos + bc
+    writeSTRef posRef newPos
+    pure a
+
+readShortByteString :: Int -> ByteArray -> Int -> ShortByteString
+readShortByteString len arr pos = let !(ByteArray frozArr) = cloneByteArray arr pos len in SBS frozArr
+
+readScope :: GetScopeF (GetEff s a) -> GetEff s a
+readScope (GetScopeF sm bc g k) = do
+  let intBc = fromIntegral bc
+  GetEnv oldLen posRef _ <- ask
+  oldPos <- stGetEff (readSTRef posRef)
+  let !oldAvail = oldLen - oldPos
+  if intBc > oldAvail
+    then throwError (GetErrorParseNeed "scope" (fromIntegral oldAvail) bc)
+    else do
+      let !newLen = oldPos + intBc
+      a <- local (\ge -> ge { geLen = newLen }) (mkGetEff g)
+      case sm of
+        ScopeModeWithin -> k a
+        ScopeModeExact -> do
+          newPos <- stGetEff (readSTRef posRef)
+          let !actualBc = newPos - oldPos
+          if actualBc == intBc
+            then k a
+            else throwError (GetErrorScopedMismatch (fromIntegral actualBc) bc)
+
+readStaticSeq :: GetStaticSeqF (GetEff s a) -> GetEff s a
+readStaticSeq gss@(GetStaticSeqF ec g k) = do
+  let !bc = getStaticSeqSize gss
+  _ <- guardReadBytes "static sequence" bc
+  ss <- Seq.replicateA (fromIntegral ec) (mkGetEff g)
+  k ss
+
+readStaticArray :: GetStaticArrayF (GetEff s a) -> GetEff s a
+readStaticArray gsa@(GetStaticArrayF _ _ k) = do
+  let !bc = getStaticArraySize gsa
+  sa <- readBytes "static vector" bc (\arr pos -> let !(ByteArray frozArr) = cloneByteArray arr pos bc in PrimArray frozArr)
+  k sa
+
+readLookAhead :: GetLookAheadF (GetEff s a) -> GetEff s a
+readLookAhead (GetLookAheadF g k) = do
+  posRef <- asks gePos
+  startPos <- stGetEff (readSTRef posRef)
+  a <- mkGetEff g
+  stGetEff (writeSTRef posRef startPos)
+  k a
+
+execGetRun :: GetF (GetEff s a) -> GetEff s a
+execGetRun = \case
+  GetFWord8 k -> readBytes "Word8" 1 (indexByteArray @Word8) >>= k
+  GetFInt8 k -> readBytes "Int8" 1 (indexByteArray @Int8) >>= k
+  GetFWord16LE k -> readBytes "Word16LE" 2 (indexByteArrayLiftedInBytes @Word16LE) >>= k
+  GetFInt16LE k -> readBytes "Int16LE" 2 (indexByteArrayLiftedInBytes @Int16LE) >>= k
+  GetFWord24LE k -> readBytes "Word24LE" 3 (indexByteArrayLiftedInBytes @Word24LE) >>= k
+  GetFInt24LE k -> readBytes "Int24LE" 3 (indexByteArrayLiftedInBytes @Int24LE) >>= k
+  GetFWord32LE k -> readBytes "Word32LE" 4 (indexByteArrayLiftedInBytes @Word32LE) >>= k
+  GetFInt32LE k -> readBytes "Int32LE" 4 (indexByteArrayLiftedInBytes @Int32LE) >>= k
+  GetFFloatLE k -> readBytes "FloatLE" 4 (indexByteArrayLiftedInBytes @FloatLE) >>= k
+  GetFWord16BE k -> readBytes "Word16BE" 2 (indexByteArrayLiftedInBytes @Word16BE) >>= k
+  GetFInt16BE k -> readBytes "Int16BE" 2 (indexByteArrayLiftedInBytes @Int16BE) >>= k
+  GetFWord24BE k -> readBytes "Word24BE" 3 (indexByteArrayLiftedInBytes @Word24BE) >>= k
+  GetFInt24BE k -> readBytes "Int24BE" 3 (indexByteArrayLiftedInBytes @Int24BE) >>= k
+  GetFWord32BE k -> readBytes "Word32BE" 4 (indexByteArrayLiftedInBytes @Word32BE) >>= k
+  GetFInt32BE k -> readBytes "Int32BE" 4 (indexByteArrayLiftedInBytes @Int32BE) >>= k
+  GetFFloatBE k -> readBytes "FloatBE" 4 (indexByteArrayLiftedInBytes @FloatBE) >>= k
+  GetFShortByteString bc k ->
+    let !len = fromIntegral bc
+    in readBytes "ShortByteString" len (readShortByteString len) >>= k
+  GetFStaticSeq gss -> readStaticSeq gss
+  GetFStaticArray gsa -> readStaticArray gsa
+  GetFByteArray bc k ->
+    let !len = fromIntegral bc
+    in readBytes "ByteArray" len (\arr pos -> cloneByteArray arr pos len) >>= k
+  GetFScope gs -> readScope gs
+  GetFSkip bc k -> readBytes "skip" (fromIntegral bc) (\_ _ -> ()) *> k
+  GetFLookAhead gla -> readLookAhead gla
+  GetFRemainingSize k -> do
+    GetEnv len posRef _ <- ask
+    pos <- stGetEff (readSTRef posRef)
+    let !bc = fromIntegral (len - pos)
+    k bc
+  GetFFail msg -> fail msg
+
+runGetRun :: GetRun s a -> GetEnv s -> ST s (Either GetError a)
+runGetRun = runGetEff . iterGetRun
+
+iterGetRun :: GetRun s a -> GetEff s a
+iterGetRun m = iterT execGetRun (unGetRun m)
+
+mkGetRun :: Get a -> GetRun s a
+mkGetRun (Get (F w)) = GetRun (w pure wrap)
+
+mkGetEff :: Get a -> GetEff s a
+mkGetEff = iterGetRun . mkGetRun
+
+runGet :: Get a -> ShortByteString -> (Either GetError a, ByteCount)
+runGet m bs = runST $ do
+  let !n = mkGetEff m
+  env <- newGetEnv bs
+  ea <- runGetEff n env
+  bc <- readSTRef (gePos env)
+  pure (ea, fromIntegral bc)
+
+runGetIO :: Get a -> ShortByteString -> IO (a, ByteCount)
+runGetIO m bs =
+  let (!ea, !bc) = runGet m bs
+  in case ea of
+    Left e -> throwIO e
+    Right a -> pure (a, bc)
+
+runGetFile :: Get a -> FilePath -> IO (a, ByteCount)
+runGetFile m fp = do
+  bs <- BS.readFile fp
+  let !bss = BSS.toShort bs
+  runGetIO m bss
+
+-- Put unsafe:
+
+data PutEnv s = PutEnv
+  { peLen :: !Int
+  , pePos :: !(STRef s Int)
+  , peArray :: !(MutableByteArray s)
+  }
+
+newPutEnv :: Int -> ST s (PutEnv s)
+newPutEnv len = PutEnv len <$> newSTRef 0 <*> newByteArray len
+
+newtype PutEff s a = PutEff { unPutEff :: ReaderT (PutEnv s) (ST s) a }
+  deriving newtype (Functor, Applicative, Monad, MonadReader (PutEnv s))
+
+runPutEff :: PutEff s a -> PutEnv s -> ST s a
+runPutEff m = runReaderT (unPutEff m)
+
+stPutEff :: ST s a -> PutEff s a
+stPutEff = PutEff . lift
+
+newtype PutRun s a = PutRun { unPutRun :: FreeT PutF (PutEff s) a }
+  deriving newtype (Functor, Applicative, Monad)
+
+writeBytes :: Int -> (MutableByteArray s -> Int -> ST s ()) -> PutEff s ()
+writeBytes bc f = do
+  PutEnv _ posRef arr <- ask
+  stPutEff $ do
+    pos <- readSTRef posRef
+    f arr pos
+    let !newPos = pos + bc
+    writeSTRef posRef newPos
+
+writeShortByteString :: ShortByteString -> Int -> MutableByteArray s -> Int -> ST s ()
+writeShortByteString (SBS frozArr) len arr pos = copyByteArray arr pos (ByteArray frozArr) 0 len
+
+writeStaticSeq :: PutStaticSeqF (PutEff s a) -> PutEff s a
+writeStaticSeq (PutStaticSeqF n mz p s k) = do
+  let n' = fromIntegral n
+  for_ (take n' (toList s)) $ \a -> do
+    let !x = p a
+    mkPutEff x
+  let !e = Seq.length s
+  unless (n' <= e) $ do
+    let !q = mkPutEff (p (fromJust mz))
+    replicateM_ (n' - e) q
+  k
+
+writeStaticArray :: PutStaticArrayF (PutEff s a) -> PutEff s a
+writeStaticArray psa@(PutStaticArrayF needElems mz a@(PrimArray frozArr) k) = do
+  let !elemSize = putStaticArrayElemSize psa
+      !haveElems = sizeofPrimArray a
+      !useElems = min haveElems (fromIntegral needElems)
+      !useBc = elemSize * useElems
+  writeBytes useBc (\arr pos -> copyByteArray arr pos (ByteArray frozArr) 0 useBc)
+  let !needBc = putStaticArraySize psa
+  unless (useBc == needBc) $ do
+    let !extraBc = needBc - useBc
+    writeBytes extraBc (\arr pos -> setByteArray arr (pos + useBc) (pos + extraBc) (fromJust mz))
+  k
+
+execPutRun :: PutF (PutEff s a) -> PutEff s a
+execPutRun = \case
+  PutFWord8 x k -> writeBytes 1 (\arr pos -> writeByteArray arr pos x) *> k
+  PutFInt8 x k -> writeBytes 1 (\arr pos -> writeByteArray arr pos x) *> k
+  PutFWord16LE x k -> writeBytes 2 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt16LE x k -> writeBytes 2 (writeByteArrayLiftedInBytes x) *> k
+  PutFWord24LE x k -> writeBytes 3 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt24LE x k -> writeBytes 3 (writeByteArrayLiftedInBytes x) *> k
+  PutFWord32LE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt32LE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFFloatLE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFWord16BE x k -> writeBytes 2 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt16BE x k -> writeBytes 2 (writeByteArrayLiftedInBytes x) *> k
+  PutFWord24BE x k -> writeBytes 3 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt24BE x k -> writeBytes 3 (writeByteArrayLiftedInBytes x) *> k
+  PutFWord32BE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFInt32BE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFFloatBE x k -> writeBytes 4 (writeByteArrayLiftedInBytes x) *> k
+  PutFShortByteString bc sbs k ->
+    let !len = fromIntegral bc
+    in writeBytes len (writeShortByteString sbs len) *> k
+  PutFStaticSeq pss -> writeStaticSeq pss
+  PutFStaticArray psa -> writeStaticArray psa
+  PutFByteArray bc barr k ->
+    let !len = fromIntegral bc
+    in writeBytes len (\arr pos -> copyByteArray arr pos barr 0 len) *> k
+  PutFStaticHint (PutStaticHintF _ p k) -> mkPutEff p *> k
+
+runPutRun :: PutRun s a -> PutEnv s -> ST s a
+runPutRun = runPutEff . iterPutRun
+
+iterPutRun :: PutRun s a -> PutEff s a
+iterPutRun m = iterT execPutRun (unPutRun m)
+
+mkPutRun :: PutM a -> PutRun s a
+mkPutRun (PutM (F w)) = PutRun (w pure wrap)
+
+mkPutEff :: PutM a -> PutEff s a
+mkPutEff = iterPutRun . mkPutRun
+
+runPutUnsafe :: Put -> ByteCount -> ShortByteString
+runPutUnsafe m bc = runST $ do
+  let !len = fromIntegral bc
+      !n = mkPutRun m
+  st@(PutEnv _ posRef arr) <- newPutEnv len
+  runPutRun n st
+  pos <- readSTRef posRef
+  unless (pos == len) (error ("Invalid put length: (given " ++ show len ++ ", used " ++ show pos ++ ")"))
+  ByteArray frozArr <- unsafeFreezeByteArray arr
+  pure $! SBS frozArr
+
+-- Count:
+
+newtype CountEff a = CountEff { unCountEff :: MaybeT (State Int) a }
+  deriving newtype (Functor, Applicative, Monad, Alternative, MonadState Int)
+
+runCountEff :: CountEff a -> Int -> (Maybe a, Int)
+runCountEff m = runState (runMaybeT (unCountEff m))
+
+newtype CountRun a = CountRun { unCountRun :: FreeT PutF CountEff a }
+  deriving newtype (Functor, Applicative, Monad)
+
+execCountRun :: PutF (CountEff a) -> CountEff a
+execCountRun = \case
+  PutFWord8 _ k -> State.modify' (1+) *> k
+  PutFInt8 _ k -> State.modify' (1+) *> k
+  PutFWord16LE _ k -> State.modify' (2+) *> k
+  PutFInt16LE _ k -> State.modify' (2+) *> k
+  PutFWord24LE _ k -> State.modify' (3+) *> k
+  PutFInt24LE _ k -> State.modify' (3+) *> k
+  PutFWord32LE _ k -> State.modify' (4+) *> k
+  PutFInt32LE _ k -> State.modify' (4+) *> k
+  PutFFloatLE _ k -> State.modify' (4+) *> k
+  PutFWord16BE _ k -> State.modify' (2+) *> k
+  PutFInt16BE _ k -> State.modify' (2+) *> k
+  PutFWord24BE _ k -> State.modify' (3+) *> k
+  PutFInt24BE _ k -> State.modify' (3+) *> k
+  PutFWord32BE _ k -> State.modify' (4+) *> k
+  PutFInt32BE _ k -> State.modify' (4+) *> k
+  PutFFloatBE _ k -> State.modify' (4+) *> k
+  PutFShortByteString bc _ k ->
+    let !len = fromIntegral bc
+    in State.modify' (len+) *> k
+  PutFStaticSeq pss@(PutStaticSeqF _ _ _ _ k) ->
+    let !len = putStaticSeqSize pss
+    in State.modify' (len+) *> k
+  PutFStaticArray psv@(PutStaticArrayF _ _ _ k) ->
+    let !len = putStaticArraySize psv
+    in State.modify' (len+) *> k
+  PutFByteArray bc _ k ->
+    let !len = fromIntegral bc
+    in State.modify' (len+) *> k
+  PutFStaticHint (PutStaticHintF bc _ k) ->
+    let !len = fromIntegral bc
+    in State.modify' (len+) *> k
+
+runCountRun :: CountRun a -> Int -> (Maybe a, Int)
+runCountRun = runCountEff . iterCountRun
+
+iterCountRun :: CountRun a -> CountEff a
+iterCountRun m = iterT execCountRun (unCountRun m)
+
+mkCountRun :: PutM a -> CountRun a
+mkCountRun (PutM (F w)) = CountRun (w pure wrap)
+
+mkCountEff :: PutM a -> CountEff a
+mkCountEff = iterCountRun . mkCountRun
+
+runCount :: Put -> ByteCount
+runCount m =
+  let !n = mkCountRun m
+      (_, !bc) = runCountRun n 0
+  in fromIntegral bc
+
+-- Put safe:
+
+runPut :: Put -> ShortByteString
+runPut m = let !bc = runCount m in runPutUnsafe m bc
+
+-- Put file:
+
+runPutFile :: FilePath -> Put -> IO ()
+runPutFile fp m =
+  let !bs = runPut m
+      !bs' = BSS.fromShort bs
+  in BS.writeFile fp bs'
diff --git a/src/Dahdit/Sizes.hs b/src/Dahdit/Sizes.hs
new file mode 100644
--- /dev/null
+++ b/src/Dahdit/Sizes.hs
@@ -0,0 +1,214 @@
+module Dahdit.Sizes
+  ( ElementCount (..)
+  , ByteCount (..)
+  , StaticByteSized (..)
+  , byteSizeViaStatic
+  , ByteSized (..)
+  , ViaStaticByteSized (..)
+  , byteSizeFoldable
+  , staticByteSizeFoldable
+  ) where
+
+import Dahdit.LiftedPrim (LiftedPrim, LiftedPrimArray, sizeofLiftedPrimArray)
+import Dahdit.Nums (FloatBE, FloatLE, Int16BE, Int16LE, Int24BE, Int24LE, Int32BE, Int32LE, Word16BE, Word16LE,
+                    Word24BE, Word24LE, Word32BE, Word32LE)
+import Dahdit.Proxy (proxyFor, proxyForF)
+import Data.ByteString.Short (ShortByteString)
+import qualified Data.ByteString.Short as BSS
+import Data.Default (Default)
+import Data.Foldable (foldMap')
+import Data.Int (Int16, Int32, Int64, Int8)
+import Data.Primitive (Prim)
+import Data.Primitive.PrimArray (PrimArray, sizeofPrimArray)
+import Data.Proxy (Proxy (..))
+import Data.Semigroup (Sum (..))
+import Data.Sequence (Seq)
+import qualified Data.Sequence as Seq
+import Data.Word (Word16, Word32, Word64, Word8)
+
+newtype ElementCount = ElementCount { unElementCount :: Word64 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bounded, Default)
+
+newtype ByteCount = ByteCount { unByteCount :: Word64 }
+  deriving stock (Show)
+  deriving newtype (Eq, Ord, Num, Enum, Real, Integral, Bounded, Default)
+
+class ByteSized a where
+  byteSize :: a -> ByteCount
+
+instance ByteSized () where
+  byteSize _ = 0
+
+instance ByteSized Word8 where
+  byteSize _ = 1
+
+instance ByteSized Int8 where
+  byteSize _ = 1
+
+instance ByteSized Word16 where
+  byteSize _ = 2
+
+instance ByteSized Int16 where
+  byteSize _ = 2
+
+instance ByteSized Word32 where
+  byteSize _ = 4
+
+instance ByteSized Int32 where
+  byteSize _ = 4
+
+instance ByteSized Word64 where
+  byteSize _ = 8
+
+instance ByteSized Int64 where
+  byteSize _ = 8
+
+instance ByteSized Word16LE where
+  byteSize _ = 2
+
+instance ByteSized Int16LE where
+  byteSize _ = 2
+
+instance ByteSized Word24LE where
+  byteSize _ = 3
+
+instance ByteSized Int24LE where
+  byteSize _ = 3
+
+instance ByteSized Word32LE where
+  byteSize _ = 4
+
+instance ByteSized Int32LE where
+  byteSize _ = 4
+
+instance ByteSized FloatLE where
+  byteSize _ = 4
+
+instance ByteSized Word16BE where
+  byteSize _ = 2
+
+instance ByteSized Int16BE where
+  byteSize _ = 2
+
+instance ByteSized Word24BE where
+  byteSize _ = 3
+
+instance ByteSized Int24BE where
+  byteSize _ = 3
+
+instance ByteSized Word32BE where
+  byteSize _ = 4
+
+instance ByteSized Int32BE where
+  byteSize _ = 4
+
+instance ByteSized FloatBE where
+  byteSize _ = 4
+
+instance ByteSized ShortByteString where
+  byteSize = fromIntegral . BSS.length
+
+instance StaticByteSized a => ByteSized (Seq a) where
+  byteSize ss =
+    let !elen = staticByteSize (Proxy :: Proxy a)
+        !alen = fromIntegral (Seq.length ss)
+    in elen * alen
+
+instance (StaticByteSized a, Prim a) => ByteSized (PrimArray a) where
+  byteSize pa =
+    let !elen = staticByteSize (Proxy :: Proxy a)
+        !alen = fromIntegral (sizeofPrimArray pa)
+    in elen * alen
+
+instance (StaticByteSized a, LiftedPrim a) => ByteSized (LiftedPrimArray a) where
+  byteSize lpa =
+    let !elen = staticByteSize (Proxy :: Proxy a)
+        !alen = fromIntegral (sizeofLiftedPrimArray lpa)
+    in elen * alen
+
+class ByteSized a => StaticByteSized a where
+  staticByteSize :: Proxy a -> ByteCount
+
+byteSizeViaStatic :: StaticByteSized a => a -> ByteCount
+byteSizeViaStatic = staticByteSize . proxyFor
+
+instance StaticByteSized () where
+  staticByteSize _ = 0
+
+instance StaticByteSized Word8 where
+  staticByteSize _ = 1
+
+instance StaticByteSized Int8 where
+  staticByteSize _ = 1
+
+instance StaticByteSized Word16 where
+  staticByteSize _ = 2
+
+instance StaticByteSized Int16 where
+  staticByteSize _ = 2
+
+instance StaticByteSized Word32 where
+  staticByteSize _ = 4
+
+instance StaticByteSized Int32 where
+  staticByteSize _ = 4
+
+instance StaticByteSized Word64 where
+  staticByteSize _ = 8
+
+instance StaticByteSized Int64 where
+  staticByteSize _ = 8
+
+instance StaticByteSized Word16LE where
+  staticByteSize _ = 2
+
+instance StaticByteSized Int16LE where
+  staticByteSize _ = 2
+
+instance StaticByteSized Word24LE where
+  staticByteSize _ = 3
+
+instance StaticByteSized Int24LE where
+  staticByteSize _ = 3
+
+instance StaticByteSized Word32LE where
+  staticByteSize _ = 4
+
+instance StaticByteSized Int32LE where
+  staticByteSize _ = 4
+
+instance StaticByteSized FloatLE where
+  staticByteSize _ = 4
+
+instance StaticByteSized Word16BE where
+  staticByteSize _ = 2
+
+instance StaticByteSized Int16BE where
+  staticByteSize _ = 2
+
+instance StaticByteSized Word24BE where
+  staticByteSize _ = 3
+
+instance StaticByteSized Int24BE where
+  staticByteSize _ = 3
+
+instance StaticByteSized Word32BE where
+  staticByteSize _ = 4
+
+instance StaticByteSized Int32BE where
+  staticByteSize _ = 4
+
+instance StaticByteSized FloatBE where
+  staticByteSize _ = 4
+
+newtype ViaStaticByteSized a = ViaStaticByteSized { unViaStaticByteSized :: a }
+
+instance StaticByteSized a => ByteSized (ViaStaticByteSized a) where
+  byteSize _ = staticByteSize (Proxy :: Proxy a)
+
+byteSizeFoldable :: (Foldable f, ByteSized a) => f a -> ByteCount
+byteSizeFoldable = getSum . foldMap' (Sum . byteSize)
+
+staticByteSizeFoldable :: (Foldable f, StaticByteSized a) => f a -> ByteCount
+staticByteSizeFoldable fa = staticByteSize (proxyForF fa) * fromIntegral (length fa)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,162 @@
+module Main (main) where
+
+import Dahdit (Binary (..), BoolByte (BoolByte), ByteCount, ByteSized (..), FloatBE (..), FloatLE (..), Generic, Get,
+               Int16LE, Int32LE, Int8, Proxy (..), Put, ShortByteString, StaticByteSized (..), ViaGeneric (..),
+               ViaStaticGeneric (..), Word16LE, Word32LE, Word8, getByteArray, getByteString, getExact, getFloatBE,
+               getFloatLE, getInt16BE, getInt16LE, getInt24BE, getInt24LE, getInt32BE, getInt32LE, getInt8,
+               getLiftedPrimArray, getLookAhead, getRemainingSize, getSeq, getSkip, getStaticArray, getStaticSeq,
+               getWithin, getWord16BE, getWord16LE, getWord24BE, getWord24LE, getWord32BE, getWord32LE, getWord8,
+               liftedPrimArrayFromList, putByteArray, putByteString, putFloatBE, putFloatLE, putInt16BE, putInt16LE,
+               putInt24BE, putInt24LE, putInt32BE, putInt32LE, putInt8, putLiftedPrimArray, putSeq, putStaticArray,
+               putStaticSeq, putWord16BE, putWord16LE, putWord24BE, putWord24LE, putWord32BE, putWord32LE, putWord8,
+               runCount, runGet, runPut)
+import qualified Data.ByteString.Short as BSS
+import Data.Primitive.ByteArray (byteArrayFromList)
+import Data.Primitive.PrimArray (primArrayFromList)
+import qualified Data.Sequence as Seq
+import GHC.Float (castWord32ToFloat)
+import Test.Tasty (TestTree, defaultMain, testGroup)
+import Test.Tasty.HUnit (testCase, (@?=))
+
+data DynFoo = DynFoo !Word8 !Word16LE
+  deriving stock (Eq, Show, Generic)
+  deriving (ByteSized, Binary) via (ViaGeneric DynFoo)
+
+data StaFoo = StaFoo !Word8 !Word16LE
+  deriving stock (Eq, Show, Generic)
+  deriving (ByteSized, StaticByteSized, Binary) via (ViaStaticGeneric StaFoo)
+
+runGetCase :: (Show a, Eq a) => Get a -> Maybe (ByteCount, ByteCount, a) -> [Word8] -> IO ()
+runGetCase getter mayRes bsl = do
+  let bs = BSS.pack bsl
+      (result, actBc) = runGet getter bs
+  case (result, mayRes) of
+    (Left _, Nothing) -> pure ()
+    (Left err, Just (_, _, expecVal)) -> fail ("Got error <" ++ show err ++ ">, expected value <" ++ show expecVal ++ ">")
+    (Right actVal, Nothing) -> fail ("Got value <" ++ show actVal ++ ">, expected error")
+    (Right actVal, Just (expecBc, expecLeft, expecVal)) -> do
+      actVal @?= expecVal
+      actBc @?= expecBc
+      BSS.length bs - fromIntegral actBc @?= fromIntegral expecLeft
+
+runPutCase :: Put -> [Word8] -> IO ()
+runPutCase putter expecList = do
+  let expecBc = fromIntegral (length expecList)
+      expecBs = BSS.pack expecList
+      estBc = runCount putter
+  estBc @?= expecBc
+  let actBs = runPut putter
+      actBc = byteSize actBs
+  actBs @?= expecBs
+  actBc @?= expecBc
+
+testDahditByteSize :: TestTree
+testDahditByteSize = testGroup "byteSize"
+  [ testCase "Word8" (byteSize @Word8 0x5D @?= 1)
+  , testCase "Int8" (byteSize @Int8 0x5D @?= 1)
+  , testCase "Word16LE" (byteSize @Word16LE 0x5DEC @?= 2)
+  , testCase "Int16LE" (byteSize @Int16LE 0x5DEC @?= 2)
+  , testCase "Word32LE" (byteSize @Word32LE 0x5DEC6EFD @?= 4)
+  , testCase "Int32LE" (byteSize @Int32LE 0x5DEC6EFD @?= 4)
+  , testCase "FloatLE" (byteSize (FloatLE (castWord32ToFloat 0x5DEC6EFD)) @?= 4)
+  , testCase "ShortByteString" (byteSize @ShortByteString (BSS.pack [0xEC, 0x5D]) @?= 2)
+  , testCase "DynFoo" (byteSize (DynFoo 0xBB 0x5DEC) @?= 3)
+  , testCase "StaFoo" (byteSize (StaFoo 0xBB 0x5DEC) @?= 3)
+  ]
+
+testDahditStaticByteSize :: TestTree
+testDahditStaticByteSize = testGroup "staticByteSize"
+  [ testCase "Word8" (staticByteSize @Word8 Proxy @?= 1)
+  , testCase "Int8" (staticByteSize @Int8 Proxy @?= 1)
+  , testCase "Word16LE" (staticByteSize @Word16LE Proxy @?= 2)
+  , testCase "Int16LE" (staticByteSize @Int16LE Proxy @?= 2)
+  , testCase "Word32LE" (staticByteSize @Word32LE Proxy @?= 4)
+  , testCase "Int32LE" (staticByteSize @Int32LE Proxy @?= 4)
+  , testCase "FloatLE" (staticByteSize @FloatLE Proxy @?= 4)
+  , testCase "StaFoo" (staticByteSize @StaFoo Proxy @?= 3)
+  , testCase "BoolByte" (staticByteSize @BoolByte Proxy @?= 1)
+  ]
+
+testDahditGet :: TestTree
+testDahditGet = testGroup "get"
+  [ testCase "Word8 zero" (runGetCase getWord8 Nothing [])
+  , testCase "Word8 one" (runGetCase getWord8 (Just (1, 0, 0x5D)) [0x5D])
+  , testCase "Word8 two" (runGetCase getWord8 (Just (1, 1, 0x5D)) [0x5D, 0xBB])
+  , testCase "Int8" (runGetCase getInt8 (Just (1, 0, 0x5D)) [0x5D])
+  , testCase "Word16LE zero" (runGetCase getWord16LE Nothing [])
+  , testCase "Word16LE one" (runGetCase getWord16LE Nothing [0x5D])
+  , testCase "Word16LE two" (runGetCase getWord16LE (Just (2, 0, 0x5DEC)) [0xEC, 0x5D])
+  , testCase "Word16LE three" (runGetCase getWord16LE (Just (2, 1, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "Int16LE" (runGetCase getInt16LE (Just (2, 1, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "Word16BE" (runGetCase getWord16BE (Just (2, 1, 0x5DEC)) [0x5D, 0xEC, 0xBB])
+  , testCase "Int16BE" (runGetCase getInt16BE (Just (2, 1, 0x5DEC)) [0x5D, 0xEC, 0xBB])
+  , testCase "Word24LE" (runGetCase getWord24LE (Just (3, 1, 0xEC6EFD)) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Int24LE" (runGetCase getInt24LE (Just (3, 1, 0xEC6EFD)) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Word24BE" (runGetCase getWord24BE (Just (3, 1, 0xEC6EFD)) [0xEC, 0x6E, 0xFD, 0x5D])
+  , testCase "Int24BE" (runGetCase getInt24BE (Just (3, 1, 0xEC6EFD)) [0xEC, 0x6E, 0xFD, 0x5D])
+  , testCase "Word32LE" (runGetCase getWord32LE (Just (4, 0, 0x5DEC6EFD)) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Int32LE" (runGetCase getInt32LE (Just (4, 0, 0x5DEC6EFD)) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Word32BE" (runGetCase getWord32BE (Just (4, 0, 0x5DEC6EFD)) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "Int32BE" (runGetCase getInt32BE (Just (4, 0, 0x5DEC6EFD)) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "FloatLE" (runGetCase getFloatLE (Just (4, 0, FloatLE (castWord32ToFloat 0x5DEC6EFD))) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "FloatBE" (runGetCase getFloatBE (Just (4, 0, FloatBE (castWord32ToFloat 0x5DEC6EFD))) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "ShortByteString" (runGetCase (getByteString 2) (Just (2, 1, BSS.pack [0xEC, 0x5D])) [0xEC, 0x5D, 0xBB])
+  , testCase "Two Word8" (runGetCase ((,) <$> getWord8 <*> getWord8) (Just (2, 0, (0x5D, 0xBB))) [0x5D, 0xBB])
+  , testCase "Two Word16LE" (runGetCase ((,) <$> getWord16LE <*> getWord16LE) (Just (4, 0, (0x5DEC, 0x4020))) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "Seq" (runGetCase (getSeq 2 getWord16LE) (Just (4, 0, Seq.fromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "StaticSeq" (runGetCase (getStaticSeq 2 getWord16LE) (Just (4, 0, Seq.fromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "StaticArray" (runGetCase (getStaticArray @Word16LE 2) (Just (4, 0, primArrayFromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "DynFoo" (runGetCase (get @DynFoo) (Just (3, 0, DynFoo 0xBB 0x5DEC)) [0xBB, 0xEC, 0x5D])
+  , testCase "StaFoo" (runGetCase (get @StaFoo) (Just (3, 0, StaFoo 0xBB 0x5DEC)) [0xBB, 0xEC, 0x5D])
+  , testCase "getRemainingSize" (runGetCase getRemainingSize (Just (0, 3, 3)) [0xBB, 0xEC, 0x5D])
+  , testCase "getSkip" (runGetCase (getSkip 2) (Just (2, 1, ())) [0xBB, 0xEC, 0x5D])
+  , testCase "getLookAhead" (runGetCase (getLookAhead getWord16LE) (Just (0, 3, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "getExact eq" (runGetCase (getExact 2 getWord16LE) (Just (2, 1, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "getExact lt" (runGetCase (getExact 1 getWord16LE) Nothing [0xEC, 0x5D, 0xBB])
+  , testCase "getExact gt" (runGetCase (getExact 3 getWord16LE) Nothing [0xEC, 0x5D, 0xBB])
+  , testCase "getWithin eq" (runGetCase (getWithin 2 getWord16LE) (Just (2, 1, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "getWithin lt" (runGetCase (getWithin 1 getWord16LE) Nothing [0xEC, 0x5D, 0xBB])
+  , testCase "getWithin gt" (runGetCase (getWithin 3 getWord16LE) (Just (2, 1, 0x5DEC)) [0xEC, 0x5D, 0xBB])
+  , testCase "BoolByte True" (runGetCase (get @BoolByte) (Just (1, 0, BoolByte True)) [0x01])
+  , testCase "BoolByte False" (runGetCase (get @BoolByte) (Just (1, 0, BoolByte False)) [0x00])
+  , testCase "getByteArray" (runGetCase (getByteArray 3) (Just (3, 1, byteArrayFromList @Word8 [0xFD, 0x6E, 0xEC])) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "getLiftedPrimArray" (runGetCase (getLiftedPrimArray (Proxy :: Proxy Word16LE) 3) (Just (6, 1, liftedPrimArrayFromList @Word16LE [0xFD, 0x6E, 0xEC])) [0xFD, 0x00, 0x6E, 0x00, 0xEC, 0x00, 0x5D])
+  ]
+
+testDahditPut :: TestTree
+testDahditPut = testGroup "put"
+  [ testCase "Word8" (runPutCase (putWord8 0x5D) [0x5D])
+  , testCase "Int8" (runPutCase (putInt8 0x5D) [0x5D])
+  , testCase "Word16LE" (runPutCase (putWord16LE 0x5DEC) [0xEC, 0x5D])
+  , testCase "Int16LE" (runPutCase (putInt16LE 0x5DEC) [0xEC, 0x5D])
+  , testCase "Word16BE" (runPutCase (putWord16BE 0x5DEC) [0x5D, 0xEC])
+  , testCase "Int16BE" (runPutCase (putInt16BE 0x5DEC) [0x5D, 0xEC])
+  , testCase "Word24LE" (runPutCase (putWord24LE 0xEC6EFD) [0xFD, 0x6E, 0xEC])
+  , testCase "Int24LE" (runPutCase (putInt24LE 0xEC6EFD) [0xFD, 0x6E, 0xEC])
+  , testCase "Word24BE" (runPutCase (putWord24BE 0xEC6EFD) [0xEC, 0x6E, 0xFD])
+  , testCase "Int24BE" (runPutCase (putInt24BE 0xEC6EFD) [0xEC, 0x6E, 0xFD])
+  , testCase "Word32LE" (runPutCase (putWord32LE 0x5DEC6EFD) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Int32LE" (runPutCase (putInt32LE 0x5DEC6EFD) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "Word32BE" (runPutCase (putWord32BE 0x5DEC6EFD) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "Int32BE" (runPutCase (putInt32BE 0x5DEC6EFD) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "FloatLE" (runPutCase (putFloatLE (FloatLE (castWord32ToFloat 0x5DEC6EFD))) [0xFD, 0x6E, 0xEC, 0x5D])
+  , testCase "FloatBE" (runPutCase (putFloatBE (FloatBE (castWord32ToFloat 0x5DEC6EFD))) [0x5D, 0xEC, 0x6E, 0xFD])
+  , testCase "ShortByteString" (runPutCase (putByteString (BSS.pack [0xEC, 0x5D])) [0xEC, 0x5D])
+  , testCase "Two Word8" (runPutCase (putWord8 0x5D *> putWord8 0xBB) [0x5D, 0xBB])
+  , testCase "Two Word16LE" (runPutCase (putWord16LE 0x5DEC *> putWord16LE 0x4020) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "Seq" (runPutCase (putSeq putWord16LE (Seq.fromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "StaticSeq" (runPutCase (putStaticSeq putWord16LE (Seq.fromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "StaticArray" (runPutCase (putStaticArray @Word16LE (primArrayFromList [0x5DEC, 0x4020])) [0xEC, 0x5D, 0x20, 0x40])
+  , testCase "DynFoo" (runPutCase (put (DynFoo 0xBB 0x5DEC)) [0xBB, 0xEC, 0x5D])
+  , testCase "StaFoo" (runPutCase (put (StaFoo 0xBB 0x5DEC)) [0xBB, 0xEC, 0x5D])
+  , testCase "BoolByte True" (runPutCase (put (BoolByte True)) [0x01])
+  , testCase "BoolByte False" (runPutCase (put (BoolByte False)) [0x00])
+  , testCase "putByteArray" (runPutCase (putByteArray (byteArrayFromList @Word8 [0xFD, 0x6E, 0xEC])) [0xFD, 0x6E, 0xEC])
+  , testCase "putLiftedPrimArray" (runPutCase (putLiftedPrimArray (liftedPrimArrayFromList @Word16LE [0xFD, 0x6E, 0xEC])) [0xFD, 0x00, 0x6E, 0x00, 0xEC, 0x00])
+  ]
+
+testDahdit :: TestTree
+testDahdit = testGroup "Dahdit" [ testDahditByteSize, testDahditStaticByteSize, testDahditGet, testDahditPut ]
+
+main :: IO ()
+main = defaultMain testDahdit
