stunts-0.1.0: src/Stunts/Unpack.hs
{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
module Stunts.Unpack where
import Control.Applicative
import Data.Bits
import Data.List
import Data.Vector ((!), (//))
import System.IO
import qualified Data.ByteString as SB
import qualified Data.ByteString.Lazy as LB
import qualified Data.Vector as V
data Dict = Empty | Leaf Int | Dict :|: Dict
loadResource :: FilePath -> IO [Int]
loadResource path = withBinaryFile path ReadMode $ \hdl ->
force =<< unpack . map fromEnum <$> hGetContents hdl
where
force xs = last xs `seq` return xs
unpackResource :: LB.ByteString -> LB.ByteString
unpackResource = LB.pack . map fromIntegral . unpack . map fromIntegral . LB.unpack
unpackResource' :: SB.ByteString -> SB.ByteString
unpackResource' = SB.pack . map fromIntegral . unpack . map fromIntegral . SB.unpack
unpack :: [Int] -> [Int]
unpack (n:xs)
| testBit n 7 = iterate pass (drop 3 xs) !! (n .&. 0x7f)
| otherwise = pass (n:xs)
where
pass (compType:b1:b2:b3:xs) = take l $ case compType of
1 -> unpackRLE (drop 4 xs)
2 -> unpackVLE xs
_ -> error "Unknown compression type!"
where
l = b1 + b2 `shiftL` 8 + b3 `shiftL` 16
unpackRLE :: [Int] -> [Int]
unpackRLE (escCount:xs) = decodeBytes $
if escCount > 0x7f then rest else decodeSeq (escCodes !! 1) rest
where
(escCodes,rest) = splitAt (escCount .&. 0x7f) xs
escLookup = V.replicate 0x100 0 // zip escCodes [1..]
decodeSeq code = concat . go
where
go [] = []
go (c:cs)
| c == code = replicate rep run ++ go rest
| otherwise = [c] : go cs
where
(run,_:rep:rest) = break (==code) cs
decodeBytes [] = []
decodeBytes (c:cs) = case escLookup ! c of
0 -> c : decodeBytes cs
1 -> let n:c:cs' = cs in replicate n c ++ decodeBytes cs'
3 -> let n1:n2:c:cs' = cs in replicate (n1 + n2 `shiftL` 8) c ++ decodeBytes cs'
n -> let c:cs' = cs in replicate (n-1) c ++ decodeBytes cs'
unpackVLE :: [Int] -> [Int]
unpackVLE (widthsCount:xs) = unfoldr (readDict dict) stream
where
widthsLen = widthsCount .&. 0x7f
(widths,xs') = splitAt widthsLen xs
(alphabet,xs'') = splitAt (sum widths) xs'
stream = [testBit x n | x <- xs'', n <- [7,6..0]]
dict = buildDict widths alphabet
buildDict ws as = go ws as 1 (0 :: Int) Empty
where
go [] _ _ _ d = d
go (w:ws) as l n d
| w == 0 = go ws as (l+1) (n `shiftL` 1) d
| otherwise = go (w-1:ws) as' l (n+1) (add l d a)
where
a:as' = as
add 0 _ a = Leaf a
add l' Empty a = add (l'-1) Empty a :|: Empty
add l' (d1 :|: d2) a
| testBit n (l'-1) = d1 :|: add (l'-1) d2 a
| otherwise = add (l'-1) d1 a :|: d2
readDict (Leaf x) bs = Just (x,bs)
readDict (d1 :|: d2) (b:bs) = readDict (if b then d2 else d1) bs
readDict _ _ = Nothing