module Binja.BinaryView
  ( Binja.BinaryView.load,
    Binja.BinaryView.close,
    Binja.BinaryView.save,
    Binja.BinaryView.hasFunctions,
    Binja.BinaryView.hasSymbols,
    Binja.BinaryView.hasDataVariables,
    Binja.BinaryView.updateAnalysis,
    Binja.BinaryView.updateAnalysisAndWait,
    Binja.BinaryView.abortAnalysis,
    Binja.BinaryView.functions,
    Binja.BinaryView.functionsContaining,
    Binja.BinaryView.functionsAt,
    Binja.BinaryView.functionsByName,
    Binja.BinaryView.symbols,
    Binja.BinaryView.symbolsByName,
    Binja.BinaryView.strings,
    Binja.BinaryView.read,
    Binja.BinaryView.symbolAt,
  )
where

import Binja.FFI
import Binja.Function
import Binja.Plugin
import Binja.Symbol
import Binja.Types
import Binja.Utils
import qualified Data.ByteString as BS
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TEE

-- It accepts a Haskell String for the filename and options and a Bool for updateAnalysis.
-- Here, we pass nullFunPtr and nullPtr for the progress callback and context.
loadFilename ::
  -- | Filename to load
  String ->
  -- | updateAnalysis flag
  Bool ->
  -- | Options string (JSON)
  String ->
  IO BNBinaryViewPtr
loadFilename :: String -> Bool -> String -> IO BNBinaryViewPtr
loadFilename String
filename Bool
updateAnalysisB String
options =
  String -> (Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr
forall a. String -> (Ptr CChar -> IO a) -> IO a
withCString String
filename ((Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr)
-> (Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr
forall a b. (a -> b) -> a -> b
$ \Ptr CChar
cFilename ->
    String -> (Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr
forall a. String -> (Ptr CChar -> IO a) -> IO a
withCString String
options ((Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr)
-> (Ptr CChar -> IO BNBinaryViewPtr) -> IO BNBinaryViewPtr
forall a b. (a -> b) -> a -> b
$ \Ptr CChar
cOptions ->
      Ptr CChar
-> CBool
-> Ptr CChar
-> BNProgressFunctionPtr
-> Ptr ()
-> IO BNBinaryViewPtr
c_BNLoadFilename
        Ptr CChar
cFilename
        (if Bool
updateAnalysisB then Word8 -> CBool
CBool Word8
1 else Word8 -> CBool
CBool Word8
0)
        Ptr CChar
cOptions
        BNProgressFunctionPtr
forall a. FunPtr a
nullFunPtr -- no progress callback
        Ptr ()
forall a. Ptr a
nullPtr -- no progress context

load :: String -> String -> IO BNBinaryViewPtr
load :: String -> String -> IO BNBinaryViewPtr
load String
filename String
options = do
  _ <- Bool -> IO Bool
initPlugins Bool
False
  viewPtr' <- loadFilename filename True options
  if viewPtr' == nullPtr
    then error $ "Failed to load binary view on file: " ++ filename
    else pure viewPtr'

close :: BNBinaryViewPtr -> IO ()
close :: BNBinaryViewPtr -> IO ()
close BNBinaryViewPtr
view' = do
  fileMetaDataPtr <- BNBinaryViewPtr -> IO BNFileMetaDataPtr
getFileForView BNBinaryViewPtr
view'
  closeFile fileMetaDataPtr

hasFunctions :: BNBinaryViewPtr -> IO Bool
hasFunctions :: BNBinaryViewPtr -> IO Bool
hasFunctions = (CBool -> Bool) -> IO CBool -> IO Bool
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap CBool -> Bool
Binja.Utils.toBool (IO CBool -> IO Bool)
-> (BNBinaryViewPtr -> IO CBool) -> BNBinaryViewPtr -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BNBinaryViewPtr -> IO CBool
c_BNHasFunctions

hasSymbols :: BNBinaryViewPtr -> Bool
hasSymbols :: BNBinaryViewPtr -> Bool
hasSymbols = CBool -> Bool
Binja.Utils.toBool (CBool -> Bool)
-> (BNBinaryViewPtr -> CBool) -> BNBinaryViewPtr -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BNBinaryViewPtr -> CBool
c_BNHasSymbols

hasDataVariables :: BNBinaryViewPtr -> Bool
hasDataVariables :: BNBinaryViewPtr -> Bool
hasDataVariables = CBool -> Bool
Binja.Utils.toBool (CBool -> Bool)
-> (BNBinaryViewPtr -> CBool) -> BNBinaryViewPtr -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BNBinaryViewPtr -> CBool
c_BNHasDataVariables

-- saves the original binary file to the (filename)
-- absolute filepath along with any modifications
save :: BNBinaryViewPtr -> String -> IO Bool
save :: BNBinaryViewPtr -> String -> IO Bool
save BNBinaryViewPtr
view String
filename =
  String -> (Ptr CChar -> IO Bool) -> IO Bool
forall a. String -> (Ptr CChar -> IO a) -> IO a
withCString String
filename ((Ptr CChar -> IO Bool) -> IO Bool)
-> (Ptr CChar -> IO Bool) -> IO Bool
forall a b. (a -> b) -> a -> b
$ \Ptr CChar
cFilename -> do
    result <- BNBinaryViewPtr -> Ptr CChar -> IO CBool
c_BNSaveToFilename BNBinaryViewPtr
view Ptr CChar
cFilename
    pure (Binja.Utils.toBool result)

updateAnalysis :: BNBinaryViewPtr -> IO ()
updateAnalysis :: BNBinaryViewPtr -> IO ()
updateAnalysis = BNBinaryViewPtr -> IO ()
c_BNUpdateAnalysis

-- updateAnalysisAndWait
-- starts the analysis process and blocks until it is complete. This method should be
-- used when it is necessary to ensure that analysis results are fully updated before
-- proceeding with further operations.
-- If an update is already in progress, this method chains a new update request to ensure that the update processes
-- all pending changes before the call was made.
updateAnalysisAndWait :: BNBinaryViewPtr -> IO ()
updateAnalysisAndWait :: BNBinaryViewPtr -> IO ()
updateAnalysisAndWait = BNBinaryViewPtr -> IO ()
c_BNUpdateAnalysisAndWait

abortAnalysis :: BNBinaryViewPtr -> IO ()
abortAnalysis :: BNBinaryViewPtr -> IO ()
abortAnalysis = BNBinaryViewPtr -> IO ()
c_BNAbortAnalysis

getFunctionList :: BNBinaryViewPtr -> IO FunctionList
getFunctionList :: BNBinaryViewPtr -> IO FunctionList
getFunctionList BNBinaryViewPtr
view =
  (Ptr CSize -> IO FunctionList) -> IO FunctionList
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CSize -> IO FunctionList) -> IO FunctionList)
-> (Ptr CSize -> IO FunctionList) -> IO FunctionList
forall a b. (a -> b) -> a -> b
$ \Ptr CSize
countPtr -> do
    rawPtr <- BNBinaryViewPtr -> Ptr CSize -> IO (Ptr BNFunctionPtr)
c_BNGetAnalysisFunctionList BNBinaryViewPtr
view Ptr CSize
countPtr
    count' <- fromIntegral <$> peek countPtr
    xs <-
      if rawPtr == nullPtr || count' == 0
        then pure []
        else peekArray count' rawPtr
    arrPtr <- newForeignPtr rawPtr (c_BNFreeFunctionList rawPtr $ fromIntegral count')
    pure
      FunctionList
        { flArrayPtr = arrPtr,
          flCount = count',
          flList = xs,
          flViewPtr = view
        }

functions :: BNBinaryViewPtr -> IO [BNFunctionPtr]
functions :: BNBinaryViewPtr -> IO [BNFunctionPtr]
functions = (FunctionList -> [BNFunctionPtr])
-> IO FunctionList -> IO [BNFunctionPtr]
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap FunctionList -> [BNFunctionPtr]
flList (IO FunctionList -> IO [BNFunctionPtr])
-> (BNBinaryViewPtr -> IO FunctionList)
-> BNBinaryViewPtr
-> IO [BNFunctionPtr]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BNBinaryViewPtr -> IO FunctionList
getFunctionList

functionsByName :: BNBinaryViewPtr -> String -> IO [BNFunctionPtr]
functionsByName :: BNBinaryViewPtr -> String -> IO [BNFunctionPtr]
functionsByName BNBinaryViewPtr
view String
name' = do
  syms <- BNBinaryViewPtr -> String -> IO [Symbol]
symbolsByName BNBinaryViewPtr
view String
name'
  let funcSyms = (Symbol -> Bool) -> [Symbol] -> [Symbol]
forall a. (a -> Bool) -> [a] -> [a]
filter Symbol -> Bool
Binja.Symbol.isFunction [Symbol]
syms
  xs <- mapM (functionsAt view . address) funcSyms
  pure $ concat xs

symbols :: BNBinaryViewPtr -> IO [Symbol]
symbols :: BNBinaryViewPtr -> IO [Symbol]
symbols BNBinaryViewPtr
view =
  (Ptr CSize -> IO [Symbol]) -> IO [Symbol]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CSize -> IO [Symbol]) -> IO [Symbol])
-> (Ptr CSize -> IO [Symbol]) -> IO [Symbol]
forall a b. (a -> b) -> a -> b
$ \Ptr CSize
countPtr -> do
    rawPtr <- BNBinaryViewPtr
-> Ptr CSize -> BNNameSpacePtr -> IO (Ptr BNSymbolPtr)
c_BNGetSymbols BNBinaryViewPtr
view Ptr CSize
countPtr BNNameSpacePtr
forall a. Ptr a
nullPtr
    count' <- fromIntegral <$> peek countPtr
    xs <-
      if rawPtr == nullPtr || count' == 0
        then pure []
        else peekArray count' rawPtr
    _ <- newForeignPtr rawPtr (c_BNFreeSymbolList rawPtr $ fromIntegral count')
    mapM Binja.Symbol.create xs

symbolsByName :: BNBinaryViewPtr -> String -> IO [Symbol]
symbolsByName :: BNBinaryViewPtr -> String -> IO [Symbol]
symbolsByName BNBinaryViewPtr
view String
name' = do
  syms <- BNBinaryViewPtr -> IO [Symbol]
Binja.BinaryView.symbols BNBinaryViewPtr
view
  pure $ filter (\Symbol
s -> Symbol -> String
name Symbol
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name') syms

functionsContaining :: BNBinaryViewPtr -> Word64 -> IO [BNFunctionPtr]
functionsContaining :: BNBinaryViewPtr -> Word64 -> IO [BNFunctionPtr]
functionsContaining BNBinaryViewPtr
view Word64
addr =
  (Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr])
-> (Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr]
forall a b. (a -> b) -> a -> b
$ \Ptr CSize
countPtr -> do
    arrPtr <- BNBinaryViewPtr -> Word64 -> Ptr CSize -> IO (Ptr BNFunctionPtr)
c_BNGetAnalysisFunctionsContainingAddress BNBinaryViewPtr
view Word64
addr Ptr CSize
countPtr
    count' <- peek countPtr
    if arrPtr == nullPtr || count' == 0
      then pure []
      else do
        refs <- peekArray (fromIntegral count') (castPtr arrPtr :: Ptr BNFunctionPtr)
        c_BNFreeFunctionList arrPtr count'
        pure refs

functionsAt :: BNBinaryViewPtr -> Word64 -> IO [BNFunctionPtr]
functionsAt :: BNBinaryViewPtr -> Word64 -> IO [BNFunctionPtr]
functionsAt BNBinaryViewPtr
view Word64
addr =
  (Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr])
-> (Ptr CSize -> IO [BNFunctionPtr]) -> IO [BNFunctionPtr]
forall a b. (a -> b) -> a -> b
$ \Ptr CSize
countPtr -> do
    arrPtr <- BNBinaryViewPtr -> Word64 -> Ptr CSize -> IO (Ptr BNFunctionPtr)
c_BNGetAnalysisFunctionsForAddress BNBinaryViewPtr
view Word64
addr Ptr CSize
countPtr
    count' <- peek countPtr
    if arrPtr == nullPtr || count' == 0
      then pure []
      else do
        refs <- peekArray (fromIntegral count') (castPtr arrPtr :: Ptr BNFunctionPtr)
        c_BNFreeFunctionList arrPtr count'
        pure refs

strings :: BNBinaryViewPtr -> IO [Maybe String]
strings :: BNBinaryViewPtr -> IO [Maybe String]
strings BNBinaryViewPtr
view =
  (Ptr CSize -> IO [Maybe String]) -> IO [Maybe String]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CSize -> IO [Maybe String]) -> IO [Maybe String])
-> (Ptr CSize -> IO [Maybe String]) -> IO [Maybe String]
forall a b. (a -> b) -> a -> b
$ \Ptr CSize
countPtr -> do
    arrPtr <- BNBinaryViewPtr -> Ptr CSize -> IO (Ptr BNStringRefPtr)
c_BNGetStrings BNBinaryViewPtr
view Ptr CSize
countPtr
    count' <- fromIntegral <$> peek countPtr
    if arrPtr == nullPtr || count' == 0
      then pure []
      else do
        refs <- peekArray count' (castPtr arrPtr :: Ptr BNStringRef)
        c_BNFreeStringReferenceList arrPtr
        forM refs $ \(BNStringRef BNStringType
t Word64
s CSize
l) -> do
          mbs <- BNBinaryViewPtr -> Word64 -> CSize -> IO (Maybe ByteString)
Binja.BinaryView.read BNBinaryViewPtr
view Word64
s CSize
l
          pure $ fmap (T.unpack . decodeByType t) mbs

decodeByType :: BNStringType -> BS.ByteString -> T.Text
decodeByType :: BNStringType -> ByteString -> Text
decodeByType BNStringType
ty' = ByteString -> Text
go
  where
    go :: ByteString -> Text
go = case BNStringType
ty' of
      BNStringType
AsciiString -> ByteString -> Text
TE.decodeLatin1
      BNStringType
Utf8String -> OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode
      BNStringType
Utf16String -> ByteString -> Text
decodeUtf16Auto
      BNStringType
Utf32String -> ByteString -> Text
decodeUtf32Auto

    -- UTF-16: detect BOM; otherwise assume LE
    decodeUtf16Auto :: ByteString -> Text
decodeUtf16Auto ByteString
bs
      | [Word8] -> ByteString -> Bool
hasPrefix [Word8
0xFF, Word8
0xFE] ByteString
bs = OnDecodeError -> ByteString -> Text
TE.decodeUtf16LEWith OnDecodeError
TEE.lenientDecode (Int -> ByteString -> ByteString
BS.drop Int
2 ByteString
bs)
      | [Word8] -> ByteString -> Bool
hasPrefix [Word8
0xFE, Word8
0xFF] ByteString
bs = OnDecodeError -> ByteString -> Text
TE.decodeUtf16BEWith OnDecodeError
TEE.lenientDecode (Int -> ByteString -> ByteString
BS.drop Int
2 ByteString
bs)
      | Bool
otherwise = OnDecodeError -> ByteString -> Text
TE.decodeUtf16LEWith OnDecodeError
TEE.lenientDecode ByteString
bs

    -- UTF-32: detect BOM; otherwise assume LE
    decodeUtf32Auto :: ByteString -> Text
decodeUtf32Auto ByteString
bs
      | [Word8] -> ByteString -> Bool
hasPrefix [Word8
0xFF, Word8
0xFE, Word8
0x00, Word8
0x00] ByteString
bs = OnDecodeError -> ByteString -> Text
TE.decodeUtf32LEWith OnDecodeError
TEE.lenientDecode (Int -> ByteString -> ByteString
BS.drop Int
4 ByteString
bs)
      | [Word8] -> ByteString -> Bool
hasPrefix [Word8
0x00, Word8
0x00, Word8
0xFE, Word8
0xFF] ByteString
bs = OnDecodeError -> ByteString -> Text
TE.decodeUtf32BEWith OnDecodeError
TEE.lenientDecode (Int -> ByteString -> ByteString
BS.drop Int
4 ByteString
bs)
      | Bool
otherwise = OnDecodeError -> ByteString -> Text
TE.decodeUtf32LEWith OnDecodeError
TEE.lenientDecode ByteString
bs

    hasPrefix :: [Word8] -> BS.ByteString -> Bool
    hasPrefix :: [Word8] -> ByteString -> Bool
hasPrefix [Word8]
pfx ByteString
bs = [Word8] -> ByteString
BS.pack [Word8]
pfx ByteString -> ByteString -> Bool
`BS.isPrefixOf` ByteString
bs

read :: BNBinaryViewPtr -> Word64 -> CSize -> IO (Maybe BS.ByteString)
read :: BNBinaryViewPtr -> Word64 -> CSize -> IO (Maybe ByteString)
read BNBinaryViewPtr
view Word64
addr CSize
len = do
  dataBuffer <- BNBinaryViewPtr -> Word64 -> CSize -> IO BNDataBufferPtr
c_BNReadViewBuffer BNBinaryViewPtr
view Word64
addr CSize
len
  if dataBuffer == nullPtr
    then pure Nothing
    else do
      dataPtr <- c_BNGetDataBufferContents dataBuffer
      if dataPtr == nullPtr
        then pure Nothing
        else do
          bs <- BS.packCStringLen (dataPtr, fromIntegral len)
          c_BNFreeDataBuffer dataBuffer
          pure $ Just bs

symbolAt :: BNBinaryViewPtr -> Word64 -> IO (Maybe Binja.Types.Symbol)
symbolAt :: BNBinaryViewPtr -> Word64 -> IO (Maybe Symbol)
symbolAt BNBinaryViewPtr
view Word64
addr = do
  symbolPtr <- BNBinaryViewPtr -> Word64 -> BNNameSpacePtr -> IO BNSymbolPtr
c_BNGetSymbolByAddress BNBinaryViewPtr
view Word64
addr BNNameSpacePtr
forall a. Ptr a
nullPtr
  if symbolPtr == nullPtr
    then do
      funcs <- functionsAt view addr
      if length funcs > 0
        then do
          sym <- Binja.Function.symbol $ head funcs
          pure $ Just sym
        else pure Nothing
    else do
      sym <- Binja.Symbol.create symbolPtr
      pure $ Just sym