{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiWayIf #-}
module Hyrax.Abif.Read
( readAbif
, getAbif
, clear
, clearAbif
, getDebug
, getPString
, getCString
, getHeader
, getRoot
, getDirectories
, getDirectory
) where
import Protolude
import qualified Data.Text as Txt
import qualified Data.Text.Encoding as TxtE
import qualified Data.Binary as B
import qualified Data.Binary.Get as B
import qualified Data.ByteString.Lazy as BSL
import Control.Monad.Fail (fail)
import Hyrax.Abif
readAbif :: FilePath -> IO (Either Text Abif)
readAbif path = getAbif <$> BSL.readFile path
getAbif :: BSL.ByteString -> Either Text Abif
getAbif bs = do
(header, rootDir) <- case B.runGetOrFail (getRoot bs) bs of
Right (_, _, x) -> pure x
Left (_, _, e) -> Left ("Error reading root: " <> Txt.pack e)
let dirBytes = BSL.drop (fromIntegral $ dDataOffset rootDir) bs
ds <- case B.runGetOrFail (getDirectories bs [] $ dElemNum rootDir) dirBytes of
Right (_, _, x) -> pure x
Left (_, _, e) -> Left ("Error reading " <> show (dElemNum rootDir) <> " directories (at " <> show (dDataOffset rootDir) <> "): " <> Txt.pack e)
pure $ Abif header rootDir ds
clearAbif :: Abif -> Abif
clearAbif a = a { aRootDir = clear $ aRootDir a
, aDirs = clear <$> aDirs a
}
clear :: Directory -> Directory
clear d = d { dData = "" }
getDebug :: Directory -> Directory
getDebug d =
let bsAtOffset = dData d in
case dElemType d of
ElemPString ->
if dDataSize d <= 4
then d { dDataDebug = [TxtE.decodeUtf8 . BSL.toStrict . BSL.drop 1 . BSL.take (fromIntegral $ dDataSize d) $ dData d] }
else d { dDataDebug = [B.runGet (lbl getPString) bsAtOffset] }
ElemCString ->
if dDataSize d <= 4
then d { dDataDebug = [TxtE.decodeUtf8 . BSL.toStrict . BSL.take (fromIntegral $ dDataSize d - 1) $ dData d] }
else d { dDataDebug = [B.runGet (lbl . getCString $ dDataSize d) bsAtOffset] }
y ->
if dElemNum d == 1
then
case y of
ElemDate ->
flip B.runGet (dData d) $ lbl $ do
yy <- B.getInt16be
mt <- B.getInt8
dt <- B.getInt8
pure d { dDataDebug = [show yy <> "/" <> show mt <> "/" <> show dt]}
ElemTime ->
flip B.runGet (dData d) $ lbl $ do
hr <- B.getInt8
mn <- B.getInt8
sc <- B.getInt8
ss <- B.getInt8
pure $ d { dDataDebug = [show hr <> ":" <> show mn <> ":" <> show sc <> "." <> show ss] }
ElemLong ->
flip B.runGet (dData d) $ lbl $ do
x <- B.getInt32be
pure $ d { dDataDebug = [show x] }
ElemShort ->
flip B.runGet (dData d) $ lbl $ do
x <- B.getInt16be
pure $ d { dDataDebug = [show x] }
ElemFloat ->
flip B.runGet (dData d) $ lbl $ do
x <- B.getFloatbe
pure $ d { dDataDebug = [show x] }
ElemWord ->
flip B.runGet (dData d) $ lbl $ do
x <- B.getInt8
pure $ d { dDataDebug = [show x] }
ElemChar ->
flip B.runGet (dData d) $ lbl $ do
x <- B.getWord8
let c = BSL.pack [x]
pure $ d { dDataDebug = [TxtE.decodeUtf8 . BSL.toStrict $ c] }
_ -> d
else
case y of
ElemChar ->
flip B.runGet (dData d) $ lbl $ do
cs <- readArray B.getWord8
let c = BSL.pack cs
pure $ d { dDataDebug = [TxtE.decodeUtf8 . BSL.toStrict $ c] }
_ -> d
where
lbl = B.label $ "Reading " <> show (dElemTypeDesc d) <> " data size=" <> show (dDataSize d) <> " dir entry=" <> Txt.unpack (dTagName d) <> " cached data size=" <> (show . BSL.length $ dData d) <> ". "
readArray :: B.Get n -> B.Get [n]
readArray getFn = do
e <- B.isEmpty
if e then return []
else do
c <- getFn
cs <- readArray getFn
pure (c:cs)
getPString :: B.Get Text
getPString = do
sz <- fromIntegral <$> B.getInt8
TxtE.decodeUtf8 <$> B.label ("PString length=" <> show sz <> ".") (B.getByteString sz)
getCString :: Int -> B.Get Text
getCString sz =
TxtE.decodeUtf8 <$> B.getByteString (sz - 1)
getHeader :: B.Get Header
getHeader =
Header <$> (TxtE.decodeUtf8 <$> B.getByteString 4)
<*> (fromIntegral <$> B.getInt16be)
getRoot :: BSL.ByteString -> B.Get (Header, Directory)
getRoot bs = do
h <- getHeader
rd <- getDirectory bs
pure (h, rd)
getDirectory :: BSL.ByteString -> B.Get Directory
getDirectory bs = do
tagName <- TxtE.decodeUtf8 <$> B.getByteString 4
tagNum <- fromIntegral <$> B.getInt32be
typeCode <- fromIntegral <$> B.getInt16be
elemSize <- fromIntegral <$> B.getInt16be
elemNum <- fromIntegral <$> B.getInt32be
dataSize <- fromIntegral <$> B.getInt32be
offsetDataBytes <- B.lookAhead $ B.getLazyByteString 4
dataOffset <- fromIntegral <$> B.getInt32be
dataBytes <- if dataSize <= 4
then pure $ BSL.take (fromIntegral dataSize) offsetDataBytes
else case B.runGetOrFail (B.getLazyByteString $ fromIntegral dataSize) $ BSL.drop (fromIntegral dataOffset) bs of
Right (_, _, x) -> pure x
Left (_, _, e) -> fail $ "error reading data (" <> show dataSize <> " bytes starting at " <> show dataOffset <> ") for directory entry '" <> Txt.unpack tagName <> "': " <> e
let (elemType, elemCode) = describeElemType typeCode
pure Directory { dTagName = tagName
, dTagNum = tagNum
, dElemTypeCode = typeCode
, dElemTypeDesc = elemCode
, dElemType = elemType
, dElemSize = elemSize
, dElemNum = elemNum
, dDataSize = dataSize
, dDataOffset = dataOffset
, dData = dataBytes
, dDataDebug = []
}
getDirectories :: BSL.ByteString -> [Directory] -> Int -> B.Get [Directory]
getDirectories _ acc 0 = pure acc
getDirectories bs acc more = do
d <- getDirectory bs
B.skip 4
getDirectories bs (acc <> [d]) (more - 1)