diff --git a/.gitignore b/.gitignore
index 733412c6f54fb97497b5847dbd3d1757545ddf82..82a9923d62811bb61b76c1fbaeb84f9af590a6ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 *~
 dist
+cabal.project.local
\ No newline at end of file
diff --git a/machines-zlib.cabal b/machines-zlib.cabal
index 7d0e637fe7706a3d1f2240d2795ed2cf499e16a5..d61c01b15390e72108e3023ff1ad9fae6f316282 100644
--- a/machines-zlib.cabal
+++ b/machines-zlib.cabal
@@ -3,7 +3,7 @@ cabal-version:       2.2
 name:                machines-zlib
 version:             0.2
 synopsis:            Decompression support for machines
--- description:         
+-- description:
 homepage:            https://github.com/lshift/machines-zlib
 license:             BSD-3-Clause
 license-file:        LICENSE
@@ -18,8 +18,12 @@ common deps
   build-depends:
       base >=4.11 && < 4.14
     , bytestring ^>= 0.10
+    , exceptions
     , machines >=0.4.1 && <0.8
+    , mtl ^>= 2.2
     , streaming-commons >=0.1.11 && <0.3
+    , primitive ^>= 0.7
+    , ghc-prim >= 0.3 && < 0.6
 
 library
   import: deps
diff --git a/src/Data/Machine/Zlib.hs b/src/Data/Machine/Zlib.hs
index 94145e641079e89809024a732eb50b55e0547e5d..77842af8f41f6fbb961323516b410f9d95445a2c 100644
--- a/src/Data/Machine/Zlib.hs
+++ b/src/Data/Machine/Zlib.hs
@@ -1,72 +1,54 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 module Data.Machine.Zlib
- ( gzipper
- , gunzipper
+ ( gunzipper
  , WindowBits(..)
  ) where
 
-import Control.Applicative
-import Control.Exception (throw)
-import Control.Monad
+import           Control.Applicative
+import           Control.Monad
+import           Control.Monad.Catch
+import           Control.Monad.Primitive
+import           Control.Monad.Trans
 import qualified Data.ByteString as BS
-import Data.Machine
-import Data.Machine.Stack
-import Data.Streaming.Zlib
-import System.IO.Unsafe
+import           Data.Machine
+import           Data.Machine.Stack
+import           Data.Streaming.Zlib
 
-unsafeM :: Monad n => IO a -> n a
-unsafeM ma = pure . unsafePerformIO $! ma
-
-handleRes :: Applicative m => PopperRes -> m (Maybe BS.ByteString)
-handleRes PRDone      = pure $ Nothing
-handleRes (PRNext bs) = pure $ Just bs
-handleRes (PRError e) = throw e
-
-gunzipper :: forall m . (Monad m) => MachineT m (Stack BS.ByteString) BS.ByteString
-gunzipper = construct $
-    go =<< (unsafeM $ initInflate (WindowBits 0)) -- Use what the header says.
+gunzipper :: forall m . (MonadThrow m, PrimMonad m, Monad m) => MachineT m (Stack BS.ByteString) BS.ByteString
+gunzipper = construct $ do
+  i <- doPrim $ initInflate (WindowBits 0)
+  start i
   where
-    go :: Inflate -> PlanT (Stack BS.ByteString) BS.ByteString m ()
-    go inflate = do
-        bs <- awaits Pop <|> stop
-        popper <- unsafeM <$> unsafeM (feedInflate inflate bs)
-        exhaust (handleRes =<< popper)
-        complete <- unsafeM $ isCompleteInflate inflate
-        if complete
-        then void $ finish
-        else go'
-       where
-        go' :: PlanT (Stack BS.ByteString) BS.ByteString m ()
-        go' = do
-          bs <- awaits Pop <|> finish
-          popper <- unsafeM <$> unsafeM (feedInflate inflate bs)
-          exhaust (handleRes =<< popper)
-          complete <- unsafeM $ isCompleteInflate inflate
-          if complete
-          then void $ finish
-          else go'
-        finish :: PlanT (Stack BS.ByteString) BS.ByteString m BS.ByteString
-        finish = do
-          r <- unsafeM $ finishInflate inflate
-          yield r
-          unused <- unsafeM $ getUnusedInflate inflate
-          awaits (Push unused)
-          stop
-
-gzipper :: forall m. Monad m => Int -> WindowBits -> ProcessT m BS.ByteString BS.ByteString
-gzipper lvl wb = construct $
-    go =<< (unsafeM $ initDeflate lvl wb)
-  where
-    go :: Deflate -> PlanT (Is BS.ByteString) BS.ByteString m ()
-    go deflate = go'
+    doPrim :: IO a -> PlanT (Stack BS.ByteString) BS.ByteString m a
+    doPrim = lift . unsafePrimToPrim
+    yield' :: BS.ByteString -> PlanT (Stack BS.ByteString) BS.ByteString m ()
+    yield' d = unless (BS.null d) $ yield d
+    push' :: BS.ByteString -> PlanT (Stack BS.ByteString) BS.ByteString m ()
+    push' d = unless (BS.null d) $ push d
+    -- This is all stupid. Its based on the exact behavior of the base API.
+    start :: Inflate -> PlanT (Stack BS.ByteString) BS.ByteString m ()
+    start i = go
       where
-        go' :: PlanT (Is BS.ByteString) BS.ByteString m ()
-        go' = do
-          bs <- await <|> finish
-          popper <- unsafeM <$> unsafeM (feedDeflate deflate bs)
-          exhaust (handleRes =<< popper)
-          go'
-        finish = do
-          let popper = unsafeM (finishDeflate deflate)
-          exhaust (handleRes =<< popper)
-          stop
+        drain :: Popper -> PlanT (Stack BS.ByteString) BS.ByteString m ()
+        drain popper =  do
+          md <- doPrim popper
+          case md of
+            PRNext d -> yield' d >> drain popper
+            PRDone -> do
+              -- This magic tries to detect if we've reached the end of a substream.
+              l <- doPrim (getUnusedInflate i)
+              if BS.null l
+                then go
+                else do
+                  yield' =<< doPrim (flushInflate i)
+                  push l
+            PRError e -> lift $ throwM e
+        getMore :: PlanT (Stack BS.ByteString) BS.ByteString m ()
+        getMore = drain =<< (doPrim . feedInflate i) =<< pop 
+        allDone :: PlanT (Stack BS.ByteString) BS.ByteString m ()
+        allDone = do
+          yield' =<< doPrim (flushInflate i)
+          push' =<< doPrim (getUnusedInflate i)
+        go :: PlanT (Stack BS.ByteString) BS.ByteString m ()
+        go = getMore <|> allDone
+
diff --git a/tests/Tests.hs b/tests/Tests.hs
index 44b9ae0cb61b3c305799e6a4271d74a067e75c5d..b29beeef543f82f9af7de4949d64e98e2af8ff09 100644
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -1,7 +1,10 @@
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 module Main where
 
-import qualified Codec.Compression.Zlib as ZLib
+import qualified "zlib" Codec.Compression.Zlib as ZLib
+import           Control.Monad.Trans
 import qualified Data.ByteString        as BS
 import qualified Data.ByteString.Lazy   as BL
 import           Data.Machine
@@ -10,52 +13,67 @@ import           Data.Machine.Zlib
 import           Data.Word
 import           Test.Tasty
 import           Test.Tasty.HUnit
-import           Test.Tasty.QuickCheck  as QC
+import           Test.Tasty.QuickCheck   as QC
+import           Test.QuickCheck.Monadic as QC
 
 main :: IO ()
 main = defaultMain tests
 
 tests :: TestTree
 tests = testGroup "machines-zlib"
-  [ properties
+  [ unzipTests
+--  , zipTests
+--  , inversionTest
   ]
 
-properties :: TestTree
-properties = testGroup "properties"
+runF :: Monoid a => MachineT IO k a -> IO (Maybe a)
+runF = fmap (Just . mconcat) . runT
+
+unzipTests :: TestTree
+unzipTests = testGroup "unzip"
   [ testCase "unzip empty stream doesn't error" $ do
-      mempty @=? (run $ (source []) `stack` gunzipper)
-      mempty @=? (run $ (source [mempty]) `stack` gunzipper)
-      mempty @=? (run $ (source [mempty, mempty]) `stack` gunzipper)
-  , testCase "zip on empty stream doesn't error" $ do
-      mempty @=? (run $ source [] ~> gzipper 1 (WindowBits 9))
-  , QC.testProperty "unzips" $ \(ds'::[Word8]) ->
-      let ds = BS.pack ds'
-      in  ds === (mconcat . run $ (source [ds]) `stack` gunzipper)
-  , QC.testProperty "zips" $ \(ds'::[Word8]) ->
+      ((Just mempty) @=?) =<< (runF $ (source []) `stack` gunzipper)
+      ((Just mempty) @=?) =<< (runF $ (source [mempty]) `stack` gunzipper)
+      ((Just mempty) @=?) =<< (runF $ (source [mempty, mempty]) `stack` gunzipper)
+  , QC.testProperty "unzips" $ \(ds'::[Word8]) -> monadicIO $ do
       let ds = BS.pack ds'
-      in  (BL.fromStrict ds) ===
-          (ZLib.decompress . BL.fromChunks . run $ source [ds] ~> (gzipper 1 (WindowBits 9)))
-  , QC.testProperty "zip/unzip" $ \(ds'::[[Word8]]) ->
-      let ds = map BS.pack ds'
-      in  (BL.fromChunks ds) ===
-          (BL.fromChunks $ run $
-             (source ds ~> gzipper 5 (WindowBits 9)) `stack`
-             gunzipper)
-  , testGroup "only first stream"
-    [ QC.testProperty "seperate chunks" $ \(ds0'::[Word8]) (ds1'::[Word8]) ->
-        let ds0 = BS.pack ds0'
-            ds1 = BS.pack ds1'
-        in  (ds0) ===
-            (mconcat $ run $
-              (source (map (BL.toStrict . ZLib.compress . BL.fromStrict) [ds0, ds1])) `stack`
-              gunzipper)
-    ]
+      QC.assert . ((Just ds) ==) =<< (lift . runF $ (source [BL.toStrict . ZLib.compress $ BL.fromStrict ds]) `stack` gunzipper)
 --  , testGroup "multiple streams"
 --    [ QC.testProperty "seperate chunks" $ \(ds0'::[Word8]) (ds1'::[Word8]) ->
 --        let ds0 = BS.pack ds0'
 --            ds1 = BS.pack ds1'
---        in  (ds0 `mappend` ds1) === (mconcat $ run $
+--        in  (ds0 `mappend` ds1) === (mconcat $ runF $
 --	      (source (map (BL.toStrict . ZLib.compress . BL.fromStrict) [ds0, ds1])) `stack`
 --	      (forever gunzipper))
 --    ]
+  , testGroup "only first stream"
+    [ QC.testProperty "seperate chunks" $ \(ds0'::[Word8]) (ds1'::[Word8]) -> monadicIO $ do
+        let ds0 = BS.pack ds0'
+            ds1 = BS.pack ds1'
+        QC.assert . ((Just ds0) ==) =<<
+            (lift . runF $
+              (source (map (BL.toStrict . ZLib.compress . BL.fromStrict) [ds0, ds1])) `stack`
+              gunzipper)
+    ]
+  ]
+{-
+zipTests :: TestTree
+zipTests = testGroup "properties"
+  [ testCase "zip on empty stream doesn't error" $ do
+      (Just mempty) @=? (runF $ (source []) `stack` (gzipper 1 (WindowBits 9)))
+  , QC.testProperty "zips" $ \(ds'::[Word8]) ->
+      let ds = BS.pack ds'
+      in  (Just $ BL.fromStrict ds) ===
+          (fmap (ZLib.decompress . BL.fromStrict) . runF $ (source [ds]) `stack` (gzipper 1 (WindowBits 9)))
+  ]
+
+inversionTest :: TestTree
+inversionTest = testGroup "inversion test"
+  [ QC.testProperty "zip/unzip" $ \(ds'::[[Word8]]) ->
+      let ds = map BS.pack ds'
+      in  (Just $ BL.fromChunks ds) ===
+          (fmap BL.fromStrict . runF $
+             (source ds `stack` gzipper 5 (WindowBits 9)) `stack`
+             gunzipper)
   ]
+-}
\ No newline at end of file