-- | This module defines messages from player to debugger module IMsg ( IMsg(..), AMF(..), AMFValue(..), amfUndecoratedName, nextIMessage ) where import Data.Word (Word8, Word16, Word32) import Data.ByteString (ByteString, pack) import qualified Data.ByteString.Char8 as BSChar import qualified Data.Iteratee as I import Data.Iteratee (Iteratee, Endian(LSB), endianRead4, endianRead2) import Control.Monad (replicateM, when) -- * Interface -- | Messages sent by player data IMsg -- | 00 or 00 = IMsgMenuState Word32 Word32 -- | 03 or 03 | IMsgCreateAnonymObject Word32 -- | 05 or 05 | IMsgTrace String -- | 0A or 10 | IMsgSetField Word32 ByteString [Word8] -- | 0B or 11 | IMsgDeleteField Word32 ByteString -- | 0C or 12 | IMsgMovieAttr ByteString ByteString -- | 0E or 14 | IMsgSwdFileEntry Word32 Word32 ByteString ByteString Word32 -- | 0F or 15 | IMsgAskBreakpoints -- | 10 or 16 | IMsgBreakHit Word16 Word16 Word32 ByteString -- | 11 or 17 | IMsgBreak -- | 12 or 18 | IMsgSetLocalVars Word32 -- | 13 or 19 | IMsgBreakpoints [(Word16, Word16)] -- | 14 or 20 | IMsgNumSwdFileEntry Word32 Word32 -- | 19 or 25 | IMsgProcessTag -- | 1A or 26 | IMsgVersion Word32 Word8 -- | 1B or 27 | IMsgBreakHitEx Word16 Word16 [(Word16, Word16, Word32, String)] -- | 1C or 28 | IMsgSetField2 Word32 ByteString [Word8] -- | 1E or 30 | IMsgGetField AMF [AMF] -- | 1F or 31 | IMsgFunctionFrame Word32 Word32 AMF [AMF] -- | 20 or 32 | IMsgDebuggerOption ByteString ByteString -- | 24 or 36 | IMsgException Word32 String [Word8] -- | All other | IMsgUnknown Word32 [Word8] deriving Show -- | Represents Action Message Format entry data AMF = AMF { amfParent :: Word32, amfName :: String, amfFlags :: Word32, amfValue :: AMFValue } deriving Show -- | Some objects (e.g. private members) could be decorated amfUndecoratedName :: AMF -> String amfUndecoratedName = reverse . takeWhile (/= ':') . reverse . amfName -- | Represents AMF value data AMFValue = AMFDouble Double | AMFBool Bool | AMFString String | AMFObject Word32 Word32 Word16 Word16 String | AMFNull | AMFUndefined | AMFTrails deriving Show -- | Read next message from player nextIMessage :: Monad m => Iteratee ByteString m IMsg nextIMessage = do len <- endianRead4 e_ idi <- endianRead4 e_ case idi of 00 -> iterMenuState len 03 -> iterCreateAnonymObject len 05 -> iterTrace len 10 -> iterSetField len 11 -> iterDeleteField len 12 -> iterMovieAttr len 14 -> iterSwdFileEntry len 15 -> iterAskBreakpoints len 16 -> iterBreakHit len 17 -> iterBreak len 18 -> iterSetLocalVars len 19 -> iterBreakpoints len 20 -> iterNumSwdFileEntry len 25 -> iterProcessTag len 26 -> iterVersion len 27 -> iterBreakHitEx len 28 -> iterSetField2 len 30 -> iterGetField len 31 -> iterFunctionFrame len 32 -> iterDebuggerOption len 36 -> iterException len _ -> iterUnknown idi len -- * Internals -- ** Iteratees to parse messages iterGetField :: Monad m => Word32 -> Iteratee ByteString m IMsg iterGetField len = do (amf, ln) <- takeAMF children <- takeChildren (fromIntegral len - ln) [] return $ IMsgGetField amf children iterDebuggerOption :: Monad m => Word32 -> Iteratee ByteString m IMsg iterDebuggerOption len = do (op, ol) <- takeStr (val, vl) <- takeStr when (fromIntegral len /= ol + vl) (fail "iterDebuggerOption: wrong size") return $ IMsgDebuggerOption op val iterFunctionFrame :: Monad m => Word32 -> Iteratee ByteString m IMsg iterFunctionFrame len = do depth <- endianRead4 e_ when (depth /= 0) (fail "iterFunctionFrame: depth != 0, not implemented") addr <- endianRead4 e_ (amf, ln) <- takeAMF children <- takeChildren (fromIntegral len - 4 - 4 - ln) [] return $ IMsgFunctionFrame depth addr amf children takeChildren :: Monad m => Int -> [AMF] -> Iteratee ByteString m [AMF] takeChildren 0 res = return $ reverse res takeChildren l res = do when (l < 0) (fail "iterFunctionFrame: wrong size") (amf, vl) <- takeAMF takeChildren (fromIntegral l - vl) (amf : res) iterException :: Monad m => Word32 -> Iteratee ByteString m IMsg iterException len = do arg1 <- endianRead4 e_ (ex, ln) <- takeStr arg3 <- replicateM (fromIntegral len - 4 - ln) I.head return $ IMsgException arg1 (bs2s ex) arg3 iterTrace :: Monad m => Word32 -> Iteratee ByteString m IMsg iterTrace len = do (msg, ln) <- takeStr when (len /= fromIntegral ln) (fail "iterTrace: wrong length") return $ IMsgTrace $ bs2s msg iterProcessTag :: Monad m => Word32 -> Iteratee ByteString m IMsg iterProcessTag len = do when (len /= 0) (fail "iterProcessTag: wrong length") return IMsgProcessTag iterDeleteField :: Monad m => Word32 -> Iteratee ByteString m IMsg iterDeleteField len = do addr <- endianRead4 e_ (name, ln) <- takeStr when (len /= fromIntegral ln + 4) (fail "iterDeleteField: wrong length") return $ IMsgDeleteField addr name iterSetField :: Monad m => Word32 -> Iteratee ByteString m IMsg iterSetField len = do addr <- endianRead4 e_ (name, ln) <- takeStr amf <- replicateM (fromIntegral len - 4 - ln) I.head return $ IMsgSetField addr name amf iterSetField2 :: Monad m => Word32 -> Iteratee ByteString m IMsg iterSetField2 len = do addr <- endianRead4 e_ (name, ln) <- takeStr amf <- replicateM (fromIntegral len - 4 - ln) I.head return $ IMsgSetField2 addr name amf iterMenuState :: Monad m => Word32 -> Iteratee ByteString m IMsg iterMenuState len = do when (len /= 8) (fail "iterMenuState: wrong length") arg1 <- endianRead4 e_ arg2 <- endianRead4 e_ return $ IMsgMenuState arg1 arg2 iterCreateAnonymObject :: Monad m => Word32 -> Iteratee ByteString m IMsg iterCreateAnonymObject len = do when (len /= 4) (fail "iterCreateAnonymObject: wrong length") addr <- endianRead4 e_ return $ IMsgCreateAnonymObject addr iterSetLocalVars :: Monad m => Word32 -> Iteratee ByteString m IMsg iterSetLocalVars len = do when (len /= 4) (fail "iterSetLocalVars: wrong length") addr <- endianRead4 e_ return $ IMsgSetLocalVars addr iterBreak :: Monad m => Word32 -> Iteratee ByteString m IMsg iterBreak len = do when (len /= 0) (fail "iterBreak: wrong length") return IMsgBreak -- XXX: Check length iterBreakHitEx :: Monad m => Word32 -> Iteratee ByteString m IMsg iterBreakHitEx _ = do fileId <- endianRead2 e_ line <- endianRead2 e_ depth <- endianRead4 e_ stack <- replicateM (fromIntegral depth) iterFrame return $ IMsgBreakHitEx fileId line stack where iterFrame = do fileId <- endianRead2 e_ line <- endianRead2 e_ addr <- endianRead4 e_ (entry, _) <- takeStr return (fileId, line, addr, bs2s entry) iterBreakHit :: Monad m => Word32 -> Iteratee ByteString m IMsg iterBreakHit len = do fileId <- endianRead2 e_ line <- endianRead2 e_ addr <- endianRead4 e_ (function, ln) <- takeStr when (len /= fromIntegral ln + 8) (fail "iterBreakHit: wrong length") return $ IMsgBreakHit fileId line addr function iterAskBreakpoints :: Monad m => Word32 -> Iteratee ByteString m IMsg iterAskBreakpoints len = do when (len /= 0) (fail "iterAskBreakpoints: wrong length") return IMsgAskBreakpoints -- XXX: Check length iterBreakpoints :: Monad m => Word32 -> Iteratee ByteString m IMsg iterBreakpoints _ = do count <- endianRead4 e_ l <- replicateM (fromIntegral count) iter' return $ IMsgBreakpoints l where iter' = do fileId <- endianRead2 e_ line <- endianRead2 e_ return (fileId, line) iterSwdFileEntry :: Monad m => Word32 -> Iteratee ByteString m IMsg iterSwdFileEntry len = do fileId <- endianRead4 e_ unIndex <- endianRead4 e_ (name, ln1) <- takeStr (source, ln2) <- takeStr swfIndex <- endianRead4 e_ when (len /= fromIntegral (ln1 + ln2) + 12) (fail "iterSwdFileEntry: wrong length") return $ IMsgSwdFileEntry fileId unIndex name source swfIndex iterUnknown :: Monad m => Word32 -> Word32 -> Iteratee ByteString m IMsg iterUnknown idi len = do dat <- replicateM (fromIntegral len) I.head return $ IMsgUnknown idi dat iterVersion :: Monad m => Word32 -> Iteratee ByteString m IMsg iterVersion len = do when (len /= 5) (fail "iterVersion: wrong length") major <- endianRead4 e_ minor <- I.head return $ IMsgVersion major minor iterMovieAttr :: Monad m => Word32 -> Iteratee ByteString m IMsg iterMovieAttr len = do (name, ln1) <- takeStr (value, ln2) <- takeStr when (len /= fromIntegral (ln1 + ln2)) (fail "iterMovieAttr: wrong length") return $ IMsgMovieAttr name value iterNumSwdFileEntry :: Monad m => Word32 -> Iteratee ByteString m IMsg iterNumSwdFileEntry len = do when (len /= 8) (fail "iterNumSwdFileEntry: wrong length") num <- endianRead4 e_ index <- endianRead4 e_ return $ IMsgNumSwdFileEntry num index -- ** Utilities e_ :: Endian e_ = LSB -- | Read zero terminated string -- returns string and number of bytes read takeStr :: Monad m => Iteratee ByteString m (ByteString, Int) takeStr = takeStr' [] 0 where takeStr' :: Monad m => [Word8] -> Int -> Iteratee ByteString m (ByteString, Int) takeStr' cs len = do c <- I.head if c == 0 then return . flip (,) (len + 1) . pack . reverse $ cs else takeStr' (c:cs) (len + 1) -- | Read AMF takeAMF :: Monad m => Iteratee ByteString m (AMF, Int) takeAMF = do parent <- endianRead4 e_ (name, nl) <- takeStr vtype <- endianRead2 e_ flags <- endianRead4 e_ (value, vl) <- takeAMFValue vtype return (AMF parent (bs2s name) flags value, 4 + nl + 2 + 4 + vl) -- | Read AMF value takeAMFValue :: Monad m => Word16 -> Iteratee ByteString m (AMFValue, Int) takeAMFValue 0 = do (str, ln) <- takeStr return (AMFDouble . read . bs2s $ str, ln) takeAMFValue 1 = do v <- I.head return (AMFBool (v /= 0), 1) takeAMFValue 2 = do (str, ln) <- takeStr return (AMFString (bs2s str), ln) takeAMFValue 3 = do oid <- endianRead4 e_ tp <- endianRead4 e_ isF <- endianRead2 e_ r <- endianRead2 e_ (typeName, tl) <- takeStr return (AMFObject oid tp isF r (bs2s typeName), 4 + 4 + 2 + 2 + tl) takeAMFValue 5 = return (AMFNull, 0) takeAMFValue 6 = return (AMFUndefined, 0) takeAMFValue 19 = return (AMFTrails, 0) takeAMFValue tp = fail $ "takeAMFValue: not implemented: " ++ show tp -- | ByteString to String bs2s :: ByteString -> String bs2s = BSChar.unpack