diff --git a/Data/Array/Byte.hs b/Data/Array/Byte.hs
new file mode 100644
--- /dev/null
+++ b/Data/Array/Byte.hs
@@ -0,0 +1,353 @@
+-- |
+-- Module      : Data.Array.Byte
+-- Copyright   : (c) Roman Leshchinskiy 2009-2012
+-- License     : BSD-style
+--
+-- Compatibility layer for
+-- <https://hackage.haskell.org/package/base/docs/Data-Array-Byte.html Data.Array.Byte>,
+-- providing boxed wrappers for `ByteArray#` and `MutableByteArray#`
+-- and relevant instances for GHC < 9.4. Include it into your Cabal file:
+--
+-- > build-depends: base
+-- > if impl(ghc < 9.4)
+-- >   build-depends: data-array-byte
+--
+-- and then import "Data.Array.Byte" unconditionally.
+--
+-- Originally derived from @primitive@ package.
+
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE UnliftedFFITypes #-}
+
+module Data.Array.Byte (
+  ByteArray(..),
+  MutableByteArray(..),
+) where
+
+import Data.Bits ((.&.), unsafeShiftR)
+import Data.Data (mkNoRepType, Data(..), Typeable)
+import qualified Data.Foldable as F
+import Data.Semigroup (Semigroup(..))
+import GHC.Exts (ByteArray#, MutableByteArray#, sameMutableByteArray#, isTrue#, unsafeCoerce#, reallyUnsafePtrEquality#, copyByteArray#, writeWord8Array#, indexWord8Array#, sizeofByteArray#, unsafeFreezeByteArray#, newByteArray#, IsList(..), Int(..))
+import GHC.Show (intToDigit)
+import GHC.ST (ST(..), runST)
+import GHC.Word (Word8(..))
+
+#if MIN_VERSION_base(4,11,0)
+import GHC.Exts (compareByteArrays#)
+#else
+import Foreign.C.Types (CInt(..), CSize(..))
+import System.IO.Unsafe (unsafeDupablePerformIO)
+#endif
+
+import Control.DeepSeq (NFData(..))
+
+import GHC.Exts (Addr#, copyAddrToByteArray#)
+import Language.Haskell.TH.Syntax (Lift(..), Lit(..), Exp(..))
+
+#if MIN_VERSION_template_haskell(2,17,0)
+import Language.Haskell.TH.Syntax (unsafeCodeCoerce)
+#elif MIN_VERSION_template_haskell(2,16,0)
+import Language.Haskell.TH.Syntax (unsafeTExpCoerce)
+#endif
+
+#if MIN_VERSION_template_haskell(2,16,0)
+import GHC.ForeignPtr (ForeignPtr(..), ForeignPtrContents(..))
+import GHC.Exts (newPinnedByteArray#, isByteArrayPinned#, byteArrayContents#)
+import Language.Haskell.TH.Syntax (Bytes(..))
+#endif
+
+-- | Boxed wrapper for 'ByteArray#'.
+--
+-- Since 'ByteArray#' is an unlifted type and not a member of kind 'Data.Kind.Type',
+-- things like @[ByteArray#]@ or @IO ByteArray#@ are ill-typed. To work around this
+-- inconvenience this module provides a standard boxed wrapper, inhabiting 'Data.Kind.Type'.
+-- Clients are expected to use 'ByteArray' in higher-level APIs,
+-- but wrap and unwrap 'ByteArray' internally as they please
+-- and use functions from "GHC.Exts".
+data ByteArray = ByteArray ByteArray#
+
+-- | Boxed wrapper for 'MutableByteArray#'.
+--
+-- Since 'MutableByteArray#' is an unlifted type and not a member of kind 'Data.Kind.Type',
+-- things like @[MutableByteArray#]@ or @IO MutableByteArray#@ are ill-typed. To work around this
+-- inconvenience this module provides a standard boxed wrapper, inhabiting 'Data.Kind.Type'.
+-- Clients are expected to use 'MutableByteArray' in higher-level APIs,
+-- but wrap and unwrap 'MutableByteArray' internally as they please
+-- and use functions from "GHC.Exts".
+data MutableByteArray s = MutableByteArray (MutableByteArray# s)
+
+-- | Create a new mutable byte array of the specified size in bytes.
+--
+-- /Note:/ this function does not check if the input is non-negative.
+newByteArray :: Int -> ST s (MutableByteArray s)
+{-# INLINE newByteArray #-}
+newByteArray (I# n#) =
+  ST (\s# -> case newByteArray# n# s# of
+    (# s'#, arr# #) -> (# s'#, MutableByteArray arr# #))
+
+-- | Convert a mutable byte array to an immutable one without copying. The
+-- array should not be modified after the conversion.
+unsafeFreezeByteArray :: MutableByteArray s -> ST s ByteArray
+{-# INLINE unsafeFreezeByteArray #-}
+unsafeFreezeByteArray (MutableByteArray arr#) =
+  ST (\s# -> case unsafeFreezeByteArray# arr# s# of
+    (# s'#, arr'# #) -> (# s'#, ByteArray arr'# #))
+
+-- | Size of the byte array in bytes.
+sizeofByteArray :: ByteArray -> Int
+{-# INLINE sizeofByteArray #-}
+sizeofByteArray (ByteArray arr#) = I# (sizeofByteArray# arr#)
+
+-- | Read byte at specific index.
+indexByteArray :: ByteArray -> Int -> Word8
+{-# INLINE indexByteArray #-}
+indexByteArray (ByteArray arr#) (I# i#) = W8# (indexWord8Array# arr# i#)
+
+-- | Write byte at specific index.
+writeByteArray :: MutableByteArray s -> Int -> Word8 -> ST s ()
+{-# INLINE writeByteArray #-}
+writeByteArray (MutableByteArray arr#) (I# i#) (W8# x#) =
+  ST (\s# -> case writeWord8Array# arr# i# x# s# of
+    s'# -> (# s'#, () #))
+
+-- | Explode 'ByteArray' into a list of bytes.
+byteArrayToList :: ByteArray -> [Word8]
+{-# INLINE byteArrayToList #-}
+byteArrayToList arr = go 0
+  where
+    go i
+      | i < maxI  = indexByteArray arr i : go (i+1)
+      | otherwise = []
+    maxI = sizeofByteArray arr
+
+-- | Create a 'ByteArray' from a list of a known length. If the length
+--   of the list does not match the given length, this throws an exception.
+byteArrayFromListN :: Int -> [Word8] -> ByteArray
+byteArrayFromListN n ys = runST $ do
+    marr <- newByteArray n
+    let go !ix [] = if ix == n
+          then return ()
+          else error $ "Data.Array.Byte.byteArrayFromListN: list length less than specified size"
+        go !ix (x : xs) = if ix < n
+          then do
+            writeByteArray marr ix x
+            go (ix + 1) xs
+          else error $ "Data.Array.Byte.byteArrayFromListN: list length greater than specified size"
+    go 0 ys
+    unsafeFreezeByteArray marr
+
+-- | Copy a slice of an immutable byte array to a mutable byte array.
+--
+-- /Note:/ this function does not do bounds or overlap checking.
+copyByteArray
+  :: MutableByteArray s -- ^ destination array
+  -> Int                -- ^ offset into destination array
+  -> ByteArray          -- ^ source array
+  -> Int                -- ^ offset into source array
+  -> Int                -- ^ number of bytes to copy
+  -> ST s ()
+{-# INLINE copyByteArray #-}
+copyByteArray (MutableByteArray dst#) (I# doff#) (ByteArray src#) (I# soff#) (I# sz#) =
+  ST (\s# -> case copyByteArray# src# soff# dst# doff# sz# s# of
+    s'# -> (# s'#, () #))
+
+instance Data ByteArray where
+  toConstr _ = error "toConstr"
+  gunfold _ _ = error "gunfold"
+  dataTypeOf _ = mkNoRepType "Data.Array.Byte.ByteArray"
+
+instance Typeable s => Data (MutableByteArray s) where
+  toConstr _ = error "toConstr"
+  gunfold _ _ = error "gunfold"
+  dataTypeOf _ = mkNoRepType "Data.Array.Byte.MutableByteArray"
+
+instance Show ByteArray where
+  showsPrec _ ba =
+      showString "[" . go 0
+    where
+      showW8 :: Word8 -> String -> String
+      showW8 !w s =
+          '0'
+        : 'x'
+        : intToDigit (fromIntegral (unsafeShiftR w 4))
+        : intToDigit (fromIntegral (w .&. 0x0F))
+        : s
+      go i
+        | i < sizeofByteArray ba = comma . showW8 (indexByteArray ba i :: Word8) . go (i+1)
+        | otherwise              = showChar ']'
+        where
+          comma | i == 0    = id
+                | otherwise = showString ", "
+
+-- | Compare prefixes of given length.
+compareByteArraysFromBeginning :: ByteArray -> ByteArray -> Int -> Ordering
+{-# INLINE compareByteArraysFromBeginning #-}
+#if MIN_VERSION_base(4,11,0)
+compareByteArraysFromBeginning (ByteArray ba1#) (ByteArray ba2#) (I# n#)
+  = compare (I# (compareByteArrays# ba1# 0# ba2# 0# n#)) 0
+#else
+compareByteArraysFromBeginning (ByteArray ba1#) (ByteArray ba2#) (I# n#)
+  = compare (fromCInt (unsafeDupablePerformIO (memcmp ba1# ba2# n))) 0
+  where
+    n = fromIntegral (I# n#) :: CSize
+    fromCInt = fromIntegral :: CInt -> Int
+
+foreign import ccall unsafe "memcmp"
+  memcmp :: ByteArray# -> ByteArray# -> CSize -> IO CInt
+#endif
+
+
+-- | Do two byte arrays share the same pointer?
+sameByteArray :: ByteArray# -> ByteArray# -> Bool
+sameByteArray ba1 ba2 =
+    case reallyUnsafePtrEquality# (unsafeCoerce# ba1 :: ()) (unsafeCoerce# ba2 :: ()) of
+      r -> isTrue# r
+
+instance Eq ByteArray where
+  ba1@(ByteArray ba1#) == ba2@(ByteArray ba2#)
+    | sameByteArray ba1# ba2# = True
+    | n1 /= n2 = False
+    | otherwise = compareByteArraysFromBeginning ba1 ba2 n1 == EQ
+    where
+      n1 = sizeofByteArray ba1
+      n2 = sizeofByteArray ba2
+
+instance Eq (MutableByteArray s) where
+  (==) (MutableByteArray arr#) (MutableByteArray brr#)
+    = isTrue# (sameMutableByteArray# arr# brr#)
+
+-- | Non-lexicographic ordering. This compares the lengths of
+-- the byte arrays first and uses a lexicographic ordering if
+-- the lengths are equal. Subject to change between major versions.
+instance Ord ByteArray where
+  ba1@(ByteArray ba1#) `compare` ba2@(ByteArray ba2#)
+    | sameByteArray ba1# ba2# = EQ
+    | n1 /= n2 = n1 `compare` n2
+    | otherwise = compareByteArraysFromBeginning ba1 ba2 n1
+    where
+      n1 = sizeofByteArray ba1
+      n2 = sizeofByteArray ba2
+-- The primop compareByteArrays# (invoked from 'compareByteArraysFromBeginning')
+-- performs a check for pointer equality as well. However, it
+-- is included here because it is likely better to check for pointer equality
+-- before checking for length equality. Getting the length requires deferencing
+-- the pointers, which could cause accesses to memory that is not in the cache.
+-- By contrast, a pointer equality check is always extremely cheap.
+
+-- | Append two byte arrays.
+appendByteArray :: ByteArray -> ByteArray -> ByteArray
+appendByteArray a b = runST $ do
+  marr <- newByteArray (sizeofByteArray a + sizeofByteArray b)
+  copyByteArray marr 0 a 0 (sizeofByteArray a)
+  copyByteArray marr (sizeofByteArray a) b 0 (sizeofByteArray b)
+  unsafeFreezeByteArray marr
+
+-- | Concatenate a list of 'ByteArray's.
+concatByteArray :: [ByteArray] -> ByteArray
+concatByteArray arrs = runST $ do
+  let len = calcLength arrs 0
+  marr <- newByteArray len
+  pasteByteArrays marr 0 arrs
+  unsafeFreezeByteArray marr
+
+-- | Dump immutable 'ByteArray's into a mutable one, starting from a given offset.
+pasteByteArrays :: MutableByteArray s -> Int -> [ByteArray] -> ST s ()
+pasteByteArrays !_ !_ [] = return ()
+pasteByteArrays !marr !ix (x : xs) = do
+  copyByteArray marr ix x 0 (sizeofByteArray x)
+  pasteByteArrays marr (ix + sizeofByteArray x) xs
+
+-- | Compute total length of 'ByteArray's, increased by accumulator.
+calcLength :: [ByteArray] -> Int -> Int
+calcLength [] !n = n
+calcLength (x : xs) !n = calcLength xs (sizeofByteArray x + n)
+
+-- | An array of zero length.
+emptyByteArray :: ByteArray
+emptyByteArray = runST (newByteArray 0 >>= unsafeFreezeByteArray)
+
+-- | Replicate 'ByteArray' given number of times and concatenate all together.
+replicateByteArray :: Int -> ByteArray -> ByteArray
+replicateByteArray n arr = runST $ do
+  marr <- newByteArray (n * sizeofByteArray arr)
+  let go i = if i < n
+        then do
+          copyByteArray marr (i * sizeofByteArray arr) arr 0 (sizeofByteArray arr)
+          go (i + 1)
+        else return ()
+  go 0
+  unsafeFreezeByteArray marr
+
+instance Semigroup ByteArray where
+  (<>) = appendByteArray
+  sconcat = mconcat . F.toList
+  stimes i arr
+    | itgr < 1 = emptyByteArray
+    | itgr <= (fromIntegral (maxBound :: Int)) = replicateByteArray (fromIntegral itgr) arr
+    | otherwise = error "Data.Array.Byte#stimes: cannot allocate the requested amount of memory"
+    where itgr = toInteger i :: Integer
+
+instance Monoid ByteArray where
+  mempty = emptyByteArray
+  mappend = (<>)
+  mconcat = concatByteArray
+
+instance IsList ByteArray where
+  type Item ByteArray = Word8
+
+  toList = byteArrayToList
+  fromList xs = byteArrayFromListN (length xs) xs
+  fromListN = byteArrayFromListN
+
+instance NFData ByteArray where
+  rnf (ByteArray _) = ()
+
+instance NFData (MutableByteArray s) where
+  rnf (MutableByteArray _) = ()
+
+instance Lift ByteArray where
+#if MIN_VERSION_template_haskell(2,17,0)
+  liftTyped = unsafeCodeCoerce . lift
+#elif MIN_VERSION_template_haskell(2,16,0)
+  liftTyped = unsafeTExpCoerce . lift
+#endif
+
+#if MIN_VERSION_template_haskell(2,16,0)
+  lift (ByteArray b) = return
+    (AppE (AppE (VarE 'addrToByteArray) (LitE (IntegerL (fromIntegral len))))
+      (LitE (BytesPrimL (Bytes ptr 0 (fromIntegral len)))))
+    where
+      len# = sizeofByteArray# b
+      len = I# len#
+      pb :: ByteArray#
+      !(ByteArray pb)
+        | isTrue# (isByteArrayPinned# b) = ByteArray b
+        | otherwise = runST $ ST $
+          \s -> case newPinnedByteArray# len# s of
+            (# s', mb #) -> case copyByteArray# b 0# mb 0# len# s' of
+              s'' -> case unsafeFreezeByteArray# mb s'' of
+                (# s''', ret #) -> (# s''', ByteArray ret #)
+      ptr :: ForeignPtr Word8
+      ptr = ForeignPtr (byteArrayContents# pb) (PlainPtr (unsafeCoerce# pb))
+#else
+  lift (ByteArray b) = return
+    (AppE (AppE (VarE 'addrToByteArray) (LitE (IntegerL (fromIntegral len))))
+      (LitE (StringPrimL (toList (ByteArray b)))))
+    where
+      len# = sizeofByteArray# b
+      len = I# len#
+#endif
+
+addrToByteArray :: Int -> Addr# -> ByteArray
+addrToByteArray (I# len) addr = runST $ ST $
+  \s -> case newByteArray# len s of
+    (# s', mb #) -> case copyAddrToByteArray# addr mb 0# len s' of
+      s'' -> case unsafeFreezeByteArray# mb s'' of
+        (# s''', ret #) -> (# s''', ByteArray ret #)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2008-2009, Roman Leshchinskiy
+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 name of the University nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission. 
+
+THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
+GLASGOW AND THE 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
+UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE 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,11 @@
+# data-array-byte
+
+Compatibility layer for [`Data.Array.Byte`](https://hackage.haskell.org/package/base/docs/Data-Array-Byte.html), providing boxed wrappers for [`ByteArray#`](https://hackage.haskell.org/package/base/docs/GHC-Exts.html#t:ByteArray-35-) and [`MutableByteArray#`](https://hackage.haskell.org/package/base/docs/GHC-Exts.html#t:MutableByteArray-35-) and relevant instances for GHC < 9.4. Include it into your Cabal file:
+
+```cabal
+build-depends: base
+if impl(ghc < 9.4)
+  build-depends: data-array-byte
+```
+
+and then `import Data.Array.Byte` unconditionally.
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+# 0.1
+
+* Initial release, matching `Data.Array.Byte` in `base-4.17.0.0` and GHC 9.4.
diff --git a/data-array-byte.cabal b/data-array-byte.cabal
new file mode 100644
--- /dev/null
+++ b/data-array-byte.cabal
@@ -0,0 +1,44 @@
+cabal-version:      2.0
+name:               data-array-byte
+version:            0.1
+license:            BSD3
+license-file:       LICENSE
+copyright:          (c) Roman Leshchinskiy 2009-2012
+maintainer:         andrew.lelechenko@gmail.com
+author:             Roman Leshchinskiy <rl@cse.unsw.edu.au>
+tested-with:
+    ghc ==8.0.2 ghc ==8.2.2 ghc ==8.4.4 ghc ==8.6.5 ghc ==8.8.4
+    ghc ==8.10.7 ghc ==9.0.2 ghc ==9.2.4 ghc ==9.4.2
+
+homepage:           https://github.com/Bodigrim/data-array-byte
+bug-reports:        https://github.com/Bodigrim/data-array-byte/issues
+synopsis:           Compatibility layer for Data.Array.Byte
+description:
+    Compatibility layer for [Data.Array.Byte](https://hackage.haskell.org/package/base/docs/Data-Array-Byte.html), providing boxed wrappers for @ByteArray#@ and @MutableByteArray#@ and relevant instances for GHC < 9.4. Include it into your Cabal file:
+    .
+    > build-depends: base
+    > if impl(ghc < 9.4)
+    >   build-depends: data-array-byte
+    .
+    and then @import Data.Array.Byte@ unconditionally.
+
+category:           Compatibility
+build-type:         Simple
+extra-source-files:
+    changelog.md
+    README.md
+
+source-repository head
+    type:     git
+    location: https://github.com/Bodigrim/data-array-byte
+
+library
+    default-language: Haskell2010
+    ghc-options:      -Wall
+    build-depends:
+        base >=4.9 && <4.18,
+        deepseq >=1.4 && <1.5,
+        template-haskell >=2.11 && <2.20
+
+    if impl(ghc <9.4)
+        exposed-modules: Data.Array.Byte
