bytepatch 0.2.0 → 0.2.1
raw patch · 10 files changed
+266/−208 lines, 10 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- BytePatch.Core: Overwrite :: a -> OverwriteMeta a -> Overwrite a
- BytePatch.Core: OverwriteMeta :: Maybe Int -> Maybe a -> OverwriteMeta a
- BytePatch.Core: [omExpected] :: OverwriteMeta a -> Maybe a
- BytePatch.Core: [omNullTerminates] :: OverwriteMeta a -> Maybe Int
- BytePatch.Core: data Overwrite a
- BytePatch.Core: data OverwriteMeta a
- BytePatch.Core: instance Data.Foldable.Foldable BytePatch.Core.OverwriteMeta
- BytePatch.Core: instance Data.Traversable.Traversable BytePatch.Core.OverwriteMeta
- BytePatch.Core: instance GHC.Base.Functor BytePatch.Core.OverwriteMeta
- BytePatch.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Core.Overwrite a)
- BytePatch.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Core.OverwriteMeta a)
- BytePatch.Core: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Core.Overwrite a)
- BytePatch.Core: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Core.OverwriteMeta a)
- BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.Overwrite a)
- BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.OverwriteMeta a)
- BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.MultiPatch a)
- BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.MultiPatches a)
- BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.Offset a)
- BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.Overwrite a)
- BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.OverwriteMeta a)
- BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.MultiPatch a)
- BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.MultiPatches a)
- BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.Offset a)
- BytePatch.JSON: instance GHC.Generics.Generic (BytePatch.Core.Overwrite a)
- BytePatch.JSON: instance GHC.Generics.Generic (BytePatch.Core.OverwriteMeta a)
- BytePatch.Linear.Core: Patch :: a -> Int -> OverwriteMeta a -> Patch a
- BytePatch.Linear.Core: [patchContents] :: Patch a -> a
- BytePatch.Linear.Core: [patchMeta] :: Patch a -> OverwriteMeta a
- BytePatch.Linear.Core: [patchOffset] :: Patch a -> Int
- BytePatch.Linear.Core: data Patch a
- BytePatch.Linear.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Linear.Core.Patch a)
- BytePatch.Linear.Core: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Linear.Core.Patch a)
- BytePatch.Linear.Core: type Patchscript a = [(Int, Overwrite a)]
- BytePatch.Linear.Gen: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Linear.Gen.Error a)
- BytePatch.Linear.Gen: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Linear.Gen.Error a)
- BytePatch.Pretty: MultiPatch :: a -> [Offset a] -> MultiPatch a
- BytePatch.Pretty: MultiPatches :: Maybe Int -> [MultiPatch a] -> MultiPatches a
- BytePatch.Pretty: Offset :: Int -> Maybe Int -> Maybe Int -> Maybe (OverwriteMeta a) -> Offset a
- BytePatch.Pretty: [mpContents] :: MultiPatch a -> a
- BytePatch.Pretty: [mpOffsets] :: MultiPatch a -> [Offset a]
- BytePatch.Pretty: [mpsBaseOffset] :: MultiPatches a -> Maybe Int
- BytePatch.Pretty: [mpsPatches] :: MultiPatches a -> [MultiPatch a]
- BytePatch.Pretty: [oAbsoluteOffset] :: Offset a -> Maybe Int
- BytePatch.Pretty: [oMaxLength] :: Offset a -> Maybe Int
- BytePatch.Pretty: [oOffset] :: Offset a -> Int
- BytePatch.Pretty: [oPatchMeta] :: Offset a -> Maybe (OverwriteMeta a)
- BytePatch.Pretty: data MultiPatch a
- BytePatch.Pretty: data MultiPatches a
- BytePatch.Pretty: data Offset a
- BytePatch.Pretty: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Pretty.MultiPatch a)
- BytePatch.Pretty: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Pretty.MultiPatches a)
- BytePatch.Pretty: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Pretty.Offset a)
- BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.MultiPatch a)
- BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.MultiPatches a)
- BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.Offset a)
- BytePatch.Pretty: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Pretty.MultiPatch a)
- BytePatch.Pretty: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Pretty.MultiPatches a)
- BytePatch.Pretty: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Pretty.Offset a)
+ BytePatch.Core: AbsSeek :: SeekKind
+ BytePatch.Core: CursorSeek :: SeekKind
+ BytePatch.Core: Edit :: a -> EditMeta a -> Edit a
+ BytePatch.Core: EditMeta :: Maybe Int -> Maybe a -> EditMeta a
+ BytePatch.Core: FwdSeek :: SeekKind
+ BytePatch.Core: Patch :: SeekRep s -> Edit a -> Patch (s :: SeekKind) a
+ BytePatch.Core: [editData] :: Edit a -> a
+ BytePatch.Core: [editMeta] :: Edit a -> EditMeta a
+ BytePatch.Core: [emExpected] :: EditMeta a -> Maybe a
+ BytePatch.Core: [emNullTerminates] :: EditMeta a -> Maybe Int
+ BytePatch.Core: data Edit a
+ BytePatch.Core: data EditMeta a
+ BytePatch.Core: data Patch (s :: SeekKind) a
+ BytePatch.Core: data SeekKind
+ BytePatch.Core: instance (GHC.Classes.Eq (BytePatch.Core.SeekRep s), GHC.Classes.Eq a) => GHC.Classes.Eq (BytePatch.Core.Patch s a)
+ BytePatch.Core: instance (GHC.Show.Show (BytePatch.Core.SeekRep s), GHC.Show.Show a) => GHC.Show.Show (BytePatch.Core.Patch s a)
+ BytePatch.Core: instance Data.Foldable.Foldable (BytePatch.Core.Patch s)
+ BytePatch.Core: instance Data.Foldable.Foldable BytePatch.Core.Edit
+ BytePatch.Core: instance Data.Foldable.Foldable BytePatch.Core.EditMeta
+ BytePatch.Core: instance Data.Traversable.Traversable (BytePatch.Core.Patch s)
+ BytePatch.Core: instance Data.Traversable.Traversable BytePatch.Core.Edit
+ BytePatch.Core: instance Data.Traversable.Traversable BytePatch.Core.EditMeta
+ BytePatch.Core: instance GHC.Base.Functor (BytePatch.Core.Patch s)
+ BytePatch.Core: instance GHC.Base.Functor BytePatch.Core.Edit
+ BytePatch.Core: instance GHC.Base.Functor BytePatch.Core.EditMeta
+ BytePatch.Core: instance GHC.Classes.Eq BytePatch.Core.SeekKind
+ BytePatch.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Core.Edit a)
+ BytePatch.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Core.EditMeta a)
+ BytePatch.Core: instance GHC.Generics.Generic (BytePatch.Core.Edit a)
+ BytePatch.Core: instance GHC.Generics.Generic (BytePatch.Core.EditMeta a)
+ BytePatch.Core: instance GHC.Generics.Generic (BytePatch.Core.Patch s a)
+ BytePatch.Core: instance GHC.Generics.Generic BytePatch.Core.SeekKind
+ BytePatch.Core: instance GHC.Show.Show BytePatch.Core.SeekKind
+ BytePatch.Core: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Core.Edit a)
+ BytePatch.Core: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Core.EditMeta a)
+ BytePatch.Core: type family SeekRep (s :: SeekKind)
+ BytePatch.JSON: instance (Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.SeekRep s), Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.EditOffset s a)
+ BytePatch.JSON: instance (Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.SeekRep s), Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.MultiEdit s a)
+ BytePatch.JSON: instance (Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.SeekRep s), Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.EditOffset s a)
+ BytePatch.JSON: instance (Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.SeekRep s), Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.MultiEdit s a)
+ BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.Edit a)
+ BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Core.EditMeta a)
+ BytePatch.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (BytePatch.Pretty.CommonMultiEdits a)
+ BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.Edit a)
+ BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Core.EditMeta a)
+ BytePatch.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (BytePatch.Pretty.CommonMultiEdits a)
+ BytePatch.Linear.Gen: instance (GHC.Classes.Eq (BytePatch.Core.SeekRep s), GHC.Classes.Eq a) => GHC.Classes.Eq (BytePatch.Linear.Gen.Error s a)
+ BytePatch.Linear.Gen: instance (GHC.Show.Show (BytePatch.Core.SeekRep s), GHC.Show.Show a) => GHC.Show.Show (BytePatch.Linear.Gen.Error s a)
+ BytePatch.Linear.Patch: patchPure :: Cfg -> [Patch 'FwdSeek Bytes] -> ByteString -> Either Error ByteString
+ BytePatch.Pretty: CommonMultiEdits :: Maybe (SeekRep 'CursorSeek) -> [MultiEdit 'CursorSeek a] -> CommonMultiEdits a
+ BytePatch.Pretty: EditOffset :: SeekRep s -> Maybe (SeekRep 'AbsSeek) -> Maybe Natural -> Maybe (EditMeta a) -> EditOffset (s :: SeekKind) a
+ BytePatch.Pretty: MultiEdit :: a -> [EditOffset s a] -> MultiEdit (s :: SeekKind) a
+ BytePatch.Pretty: [cmesBaseOffset] :: CommonMultiEdits a -> Maybe (SeekRep 'CursorSeek)
+ BytePatch.Pretty: [cmesEdits] :: CommonMultiEdits a -> [MultiEdit 'CursorSeek a]
+ BytePatch.Pretty: [eoAbsOffset] :: EditOffset (s :: SeekKind) a -> Maybe (SeekRep 'AbsSeek)
+ BytePatch.Pretty: [eoEditMeta] :: EditOffset (s :: SeekKind) a -> Maybe (EditMeta a)
+ BytePatch.Pretty: [eoMaxLength] :: EditOffset (s :: SeekKind) a -> Maybe Natural
+ BytePatch.Pretty: [eoOffset] :: EditOffset (s :: SeekKind) a -> SeekRep s
+ BytePatch.Pretty: [meAt] :: MultiEdit (s :: SeekKind) a -> [EditOffset s a]
+ BytePatch.Pretty: [meData] :: MultiEdit (s :: SeekKind) a -> a
+ BytePatch.Pretty: data CommonMultiEdits a
+ BytePatch.Pretty: data EditOffset (s :: SeekKind) a
+ BytePatch.Pretty: data MultiEdit (s :: SeekKind) a
+ BytePatch.Pretty: instance (GHC.Classes.Eq (BytePatch.Core.SeekRep s), GHC.Classes.Eq a) => GHC.Classes.Eq (BytePatch.Pretty.EditOffset s a)
+ BytePatch.Pretty: instance (GHC.Classes.Eq (BytePatch.Core.SeekRep s), GHC.Classes.Eq a) => GHC.Classes.Eq (BytePatch.Pretty.MultiEdit s a)
+ BytePatch.Pretty: instance (GHC.Show.Show (BytePatch.Core.SeekRep s), GHC.Show.Show a) => GHC.Show.Show (BytePatch.Pretty.EditOffset s a)
+ BytePatch.Pretty: instance (GHC.Show.Show (BytePatch.Core.SeekRep s), GHC.Show.Show a) => GHC.Show.Show (BytePatch.Pretty.MultiEdit s a)
+ BytePatch.Pretty: instance Data.Foldable.Foldable (BytePatch.Pretty.EditOffset s)
+ BytePatch.Pretty: instance Data.Foldable.Foldable (BytePatch.Pretty.MultiEdit s)
+ BytePatch.Pretty: instance Data.Foldable.Foldable BytePatch.Pretty.CommonMultiEdits
+ BytePatch.Pretty: instance Data.Traversable.Traversable (BytePatch.Pretty.EditOffset s)
+ BytePatch.Pretty: instance Data.Traversable.Traversable (BytePatch.Pretty.MultiEdit s)
+ BytePatch.Pretty: instance Data.Traversable.Traversable BytePatch.Pretty.CommonMultiEdits
+ BytePatch.Pretty: instance GHC.Base.Functor (BytePatch.Pretty.EditOffset s)
+ BytePatch.Pretty: instance GHC.Base.Functor (BytePatch.Pretty.MultiEdit s)
+ BytePatch.Pretty: instance GHC.Base.Functor BytePatch.Pretty.CommonMultiEdits
+ BytePatch.Pretty: instance GHC.Classes.Eq a => GHC.Classes.Eq (BytePatch.Pretty.CommonMultiEdits a)
+ BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.CommonMultiEdits a)
+ BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.EditOffset s a)
+ BytePatch.Pretty: instance GHC.Generics.Generic (BytePatch.Pretty.MultiEdit s a)
+ BytePatch.Pretty: instance GHC.Show.Show a => GHC.Show.Show (BytePatch.Pretty.CommonMultiEdits a)
- BytePatch.Linear.Gen: ErrorOverlap :: Patch a -> Patch a -> Error a
+ BytePatch.Linear.Gen: ErrorOverlap :: Patch s a -> Patch s a -> Error s a
- BytePatch.Linear.Gen: data Error a
+ BytePatch.Linear.Gen: data Error s a
- BytePatch.Linear.Gen: gen :: [Patch Bytes] -> (Patchscript Bytes, [Error Bytes])
+ BytePatch.Linear.Gen: gen :: [Patch 'AbsSeek Bytes] -> ([Patch 'FwdSeek Bytes], [Error 'AbsSeek Bytes])
- BytePatch.Linear.Patch: advance :: MonadFwdByteStream m => Int -> m ()
+ BytePatch.Linear.Patch: advance :: MonadFwdByteStream m => Natural -> m ()
- BytePatch.Linear.Patch: patch :: MonadFwdByteStream m => Cfg -> Patchscript Bytes -> m (Maybe Error)
+ BytePatch.Linear.Patch: patch :: MonadFwdByteStream m => Cfg -> [Patch 'FwdSeek Bytes] -> m (Maybe Error)
- BytePatch.Linear.Patch: readahead :: MonadFwdByteStream m => Int -> m Bytes
+ BytePatch.Linear.Patch: readahead :: MonadFwdByteStream m => Natural -> m Bytes
- BytePatch.Pretty: applyBaseOffset :: MultiPatches a -> (Int, [(MultiPatch a, [Offset a])])
+ BytePatch.Pretty: applyBaseOffset :: CommonMultiEdits a -> (Integer, [(MultiEdit 'AbsSeek a, [EditOffset 'CursorSeek a])])
- BytePatch.Pretty: normalize :: PatchRep a => [MultiPatch a] -> Maybe [Patch Bytes]
+ BytePatch.Pretty: normalize :: PatchRep a => [MultiEdit 'AbsSeek a] -> Maybe [Patch 'AbsSeek Bytes]
- BytePatch.Pretty: normalizeSimple :: PatchRep a => [MultiPatches a] -> Maybe [Patch Bytes]
+ BytePatch.Pretty: normalizeSimple :: PatchRep a => [CommonMultiEdits a] -> Maybe [Patch 'AbsSeek Bytes]
Files
- CHANGELOG.md +5/−0
- README.md +8/−6
- app/Main.hs +37/−7
- bytepatch.cabal +7/−2
- src/BytePatch/Core.hs +32/−11
- src/BytePatch/JSON.hs +24/−26
- src/BytePatch/Linear/Core.hs +0/−13
- src/BytePatch/Linear/Gen.hs +24/−17
- src/BytePatch/Linear/Patch.hs +36/−29
- src/BytePatch/Pretty.hs +93/−97
CHANGELOG.md view
@@ -1,3 +1,8 @@+## 0.2.1 (2021-12-03)+ * large internal refactoring (still more to come!)+ * schema refactoring (patch -> edit, offsets -> at)+ * Hackage metadata improvements (initial release was a bit wonky)+ ## 0.2.0 (2021-12-03) Initial release.
README.md view
@@ -1,10 +1,12 @@ # bytepatch-A CLI tool for patching byte-representable data in a bytestream. Write-**patchscripts** (in-place edits to a file) using a convenient YAML format, and-apply them with a safe patching algorithm. You choose how explicit to be: edits-may be written as a simple `(offset, content)` tuple, or you can provide-metadata regarding expected original data, so you know you're patching the right-file.+A Haskell library and CLI tool for patching byte-representable data in a+bytestream. Write **patchscripts** (in-place edits to a file) using a convenient+YAML format, and apply them with a safe patching algorithm. You choose how+explicit to be: edits may be written as a simple `(offset, content)` tuple, or+you can provide metadata regarding expected original data, so you know you're+patching the right file.++Intended as a general tool for making simple binary editing more manageable. ## What? If you're modifying binaries, you often end up needing to make edits in a hex
app/Main.hs view
@@ -7,17 +7,18 @@ import BytePatch.Pretty import BytePatch.Pretty.PatchRep-import BytePatch.JSON+import BytePatch.JSON() import qualified BytePatch.Linear.Patch as Linear import qualified BytePatch.Linear.Gen as Linear import Control.Monad.IO.Class import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL import qualified Data.Yaml as Yaml import Data.Aeson ( FromJSON )-import Data.Text ( Text ) -type Bytes = BS.ByteString+import Data.Text ( Text )+import BytePatch.Pretty.HexByteString main :: IO () main = Options.parse >>= run@@ -25,20 +26,49 @@ run :: MonadIO m => Config -> m () run cfg = do return ()- tryReadPatchscript @Text (cfgPatchscript cfg) >>= \case+ tryReadPatchscript @HexByteString (cfgPatchscript cfg) >>= \case Nothing -> quit "couldn't parse patchscript" Just ps -> case normalizeSimple ps of Nothing -> quit "failed while normalizing patchscript" Just p -> case Linear.gen p of- (_, _:_) -> quit "failed while generating patchscript"- (ps', []) -> quit "TODO actually patch"+ (_, genErrs@(_:_)) -> quit' "patchscript generation failed" genErrs+ (ps', []) -> do+ bs <- readStream' io+ case Linear.patchPure (cfgPatchCfg cfg) ps' bs of+ Left patchErr -> quit' "patching failed" patchErr+ Right bs' -> writeStream' io (BL.toStrict bs')+ where+ io = cfgStreamInOut cfg +readStream :: MonadIO m => CStream -> m BS.ByteString+readStream stream = liftIO $+ case stream of+ CStreamStd -> BS.getContents+ CStreamFile fp -> BS.readFile fp++readStream' :: MonadIO m => CStreamInOut -> m BS.ByteString+readStream' = readStream . fst . unCStreamInOut++writeStream :: MonadIO m => CStream -> BS.ByteString -> m ()+writeStream stream bs = liftIO $+ case stream of+ CStreamStd -> BS.putStr bs+ CStreamFile fp -> BS.writeFile fp bs++writeStream' :: MonadIO m => CStreamInOut -> BS.ByteString -> m ()+writeStream' stream = writeStream (snd (unCStreamInOut stream))++quit' :: (MonadIO m, Show a) => String -> a -> m ()+quit' msg a = liftIO $ do+ putStrLn $ "bytepatch: error: " <> msg+ print a+ quit :: MonadIO m => String -> m () quit = liftIO . putStrLn -tryReadPatchscript :: forall a m. (PatchRep a, FromJSON a, MonadIO m) => FilePath -> m (Maybe [MultiPatches a])+tryReadPatchscript :: forall a m. (PatchRep a, FromJSON a, MonadIO m) => FilePath -> m (Maybe [CommonMultiEdits a]) tryReadPatchscript = tryDecodeYaml tryDecodeYaml :: (FromJSON a, MonadIO m) => FilePath -> m (Maybe a)
bytepatch.cabal view
@@ -5,13 +5,19 @@ -- see: https://github.com/sol/hpack name: bytepatch-version: 0.2.0+version: 0.2.1 synopsis: Patch byte-representable data in a bytestream.+description: Please see README.md.+category: CLI homepage: https://github.com/raehik/bytepatch#readme bug-reports: https://github.com/raehik/bytepatch/issues+author: Ben Orchard+maintainer: Ben Orchard <thefirstmuffinman@gmail.com> license: MIT license-file: LICENSE build-type: Simple+tested-with:+ GHC >= 8.6 && < 9.2 extra-source-files: README.md CHANGELOG.md@@ -24,7 +30,6 @@ exposed-modules: BytePatch.Core BytePatch.JSON- BytePatch.Linear.Core BytePatch.Linear.Gen BytePatch.Linear.Patch BytePatch.Pretty
src/BytePatch/Core.hs view
@@ -1,17 +1,38 @@+{-# LANGUAGE DataKinds, TypeFamilies, UndecidableInstances #-}+ module BytePatch.Core where --- | A single in-place edit.------ Overwrites may store extra metadata that can be used at patch time to--- validate input data (i.e. check we're patching the expected file).-data Overwrite a = Overwrite a (OverwriteMeta a)- deriving (Eq, Show)+import GHC.Generics ( Generic )+import GHC.Natural --- | Optional patch time data for an overwrite.-data OverwriteMeta a = OverwriteMeta- { omNullTerminates :: Maybe Int+data Patch (s :: SeekKind) a = Patch (SeekRep s) (Edit a)+ deriving (Generic, Functor, Foldable, Traversable)++deriving instance (Eq (SeekRep s), Eq a) => Eq (Patch s a)+deriving instance (Show (SeekRep s), Show a) => Show (Patch s a)++data SeekKind+ = FwdSeek -- ^ seeks only move cursor forward+ | CursorSeek -- ^ seeks move cursor forward or backward+ | AbsSeek -- ^ seeks specify an exact offset in stream+ deriving (Eq, Show, Generic)++type family SeekRep (s :: SeekKind) where+ SeekRep 'FwdSeek = Natural+ SeekRep 'CursorSeek = Integer+ SeekRep 'AbsSeek = Natural++-- | Data to add to a stream.+data Edit a = Edit+ { editData :: a+ , editMeta :: EditMeta a+ } deriving (Eq, Show, Generic, Functor, Foldable, Traversable)++-- | Various optional metadata defining expected existing data for an 'Edit'.+data EditMeta a = EditMeta+ { emNullTerminates :: Maybe Int -- ^ Stream segment should be null bytes (0x00) only from this index onwards. - , omExpected :: Maybe a+ , emExpected :: Maybe a -- ^ Stream segment should be this.- } deriving (Eq, Show, Functor, Foldable, Traversable)+ } deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
src/BytePatch/JSON.hs view
@@ -1,13 +1,13 @@ {-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE UndecidableInstances #-} -- | Aeson instances for various types. module BytePatch.JSON where import BytePatch.Core-import BytePatch.Pretty+import qualified BytePatch.Pretty as Pretty import BytePatch.Pretty.HexByteString import Data.Aeson-import GHC.Generics (Generic) import Text.Megaparsec import Data.Void @@ -24,34 +24,32 @@ { fieldLabelModifier = camelTo2 '_' . drop x , rejectUnknownFields = True } -instance ToJSON a => ToJSON (MultiPatches a) where- toJSON = genericToJSON (jsonCfgCamelDrop 3)- toEncoding = genericToEncoding (jsonCfgCamelDrop 3)-instance FromJSON a => FromJSON (MultiPatches a) where- parseJSON = genericParseJSON (jsonCfgCamelDrop 3)+instance ToJSON a => ToJSON (Pretty.CommonMultiEdits a) where+ toJSON = genericToJSON $ jsonCfgCamelDrop 4+ toEncoding = genericToEncoding $ jsonCfgCamelDrop 4+instance FromJSON a => FromJSON (Pretty.CommonMultiEdits a) where+ parseJSON = genericParseJSON $ jsonCfgCamelDrop 4 -instance ToJSON a => ToJSON (MultiPatch a) where- toJSON = genericToJSON (jsonCfgCamelDrop 2)- toEncoding = genericToEncoding (jsonCfgCamelDrop 2)-instance FromJSON a => FromJSON (MultiPatch a) where- parseJSON = genericParseJSON (jsonCfgCamelDrop 2)+instance (ToJSON (SeekRep s), ToJSON a) => ToJSON (Pretty.MultiEdit s a) where+ toJSON = genericToJSON $ jsonCfgCamelDrop 2+ toEncoding = genericToEncoding $ jsonCfgCamelDrop 2+instance (FromJSON (SeekRep s), FromJSON a) => FromJSON (Pretty.MultiEdit s a) where+ parseJSON = genericParseJSON $ jsonCfgCamelDrop 2 -instance ToJSON a => ToJSON (Offset a) where- toJSON = genericToJSON (jsonCfgCamelDrop 1)- toEncoding = genericToEncoding (jsonCfgCamelDrop 1)-instance FromJSON a => FromJSON (Offset a) where- parseJSON = genericParseJSON (jsonCfgCamelDrop 1)+instance (ToJSON (SeekRep s), ToJSON a) => ToJSON (Pretty.EditOffset s a) where+ toJSON = genericToJSON $ jsonCfgCamelDrop 2+ toEncoding = genericToEncoding $ jsonCfgCamelDrop 2+instance (FromJSON (SeekRep s), FromJSON a) => FromJSON (Pretty.EditOffset s a) where+ parseJSON = genericParseJSON $ jsonCfgCamelDrop 2 -deriving instance Generic (OverwriteMeta a)-instance ToJSON a => ToJSON (OverwriteMeta a) where- toJSON = genericToJSON (jsonCfgCamelDrop 2)- toEncoding = genericToEncoding (jsonCfgCamelDrop 2)-instance FromJSON a => FromJSON (OverwriteMeta a) where- parseJSON = genericParseJSON (jsonCfgCamelDrop 2)+instance ToJSON a => ToJSON (EditMeta a) where+ toJSON = genericToJSON $ jsonCfgCamelDrop 2+ toEncoding = genericToEncoding $ jsonCfgCamelDrop 2+instance FromJSON a => FromJSON (EditMeta a) where+ parseJSON = genericParseJSON $ jsonCfgCamelDrop 2 -deriving instance Generic (Overwrite a)-instance ToJSON a => ToJSON (Overwrite a) where+instance ToJSON a => ToJSON (Edit a) where toJSON = genericToJSON defaultOptions toEncoding = genericToEncoding defaultOptions-instance FromJSON a => FromJSON (Overwrite a) where+instance FromJSON a => FromJSON (Edit a) where parseJSON = genericParseJSON defaultOptions
− src/BytePatch/Linear/Core.hs
@@ -1,13 +0,0 @@-module BytePatch.Linear.Core where--import BytePatch.Core---- | A list of "keep n bytes, write data in-place" actions.-type Patchscript a = [(Int, Overwrite a)]---- | Write the given data into the given offset.-data Patch a = Patch- { patchContents :: a- , patchOffset :: Int- , patchMeta :: OverwriteMeta a- } deriving (Eq, Show)
src/BytePatch/Linear/Gen.hs view
@@ -1,28 +1,34 @@+{-# LANGUAGE DataKinds, TypeFamilies, UndecidableInstances #-}+ module BytePatch.Linear.Gen (gen, Error(..)) where import BytePatch.Core-import BytePatch.Linear.Core import qualified Data.ByteString as BS import Control.Monad.State import qualified Data.List as List+import GHC.Natural type Bytes = BS.ByteString -- | Error encountered during linear patchscript generation.-data Error a- = ErrorOverlap (Patch a) (Patch a)+data Error s a+ = ErrorOverlap (Patch s a) (Patch s a) -- ^ Two edits wrote to the same offset.- deriving (Eq, Show) --- | Process an offset patchscript into a linear patchscript.+deriving instance (Eq (SeekRep s), Eq a) => Eq (Error s a)+deriving instance (Show (SeekRep s), Show a) => Show (Error s a)++-- | Process a list of patches into a linear patch script. -- -- Errors are reported, but do not interrupt patch generation. The user could -- discard patchscripts that errored, or perhaps attempt to recover them. This -- is what we do for errors: -- -- * overlapping edit: later edit is skipped & overlapping edits reported-gen :: [Patch Bytes] -> (Patchscript Bytes, [Error Bytes])+gen+ :: [Patch 'AbsSeek Bytes]+ -> ([Patch 'FwdSeek Bytes], [Error 'AbsSeek Bytes]) gen pList = let pList' = List.sortBy comparePatchOffsets pList (_, script, errors, _) = execState (go pList') (0, [], [], undefined)@@ -30,23 +36,24 @@ -- a non-negative offset (negative offsets are forbidden) in (reverse script, reverse errors) where- comparePatchOffsets (Patch _ o1 _) (Patch _ o2 _) = compare o1 o2- go :: (MonadState (Int, Patchscript Bytes, [Error Bytes], Patch Bytes) m) => [Patch Bytes] -> m ()+ comparePatchOffsets (Patch o1 _) (Patch o2 _) = compare o1 o2+ go+ :: (MonadState (Natural, [Patch 'FwdSeek Bytes], [Error 'AbsSeek Bytes], Patch 'AbsSeek Bytes) m)+ => [Patch 'AbsSeek Bytes]+ -> m () go [] = return ()- go (p@(Patch bs offset meta):ps) = do+ go (p@(Patch offset edit) : ps) = do (cursor, script, errors, prevPatch) <- get- case trySkipTo offset cursor of+ case offset `minusNaturalMaybe` cursor of -- next offset is behind current cursor: overlapping patches -- record error, recover via dropping patch- Left _ -> do+ Nothing -> do let e = ErrorOverlap p prevPatch let errors' = e : errors put (cursor, script, errors', p) go ps- Right skip -> do- let cursor' = cursor + skip + BS.length bs- o = Overwrite bs meta- put (cursor', (skip, o):script, errors, p)+ Just skip -> do+ let dataLen = fromIntegral $ BS.length $ editData edit+ let cursor' = cursor + skip + dataLen+ put (cursor', Patch skip edit : script, errors, p) go ps- trySkipTo to from =- let diff = to - from in if diff >= 0 then Right diff else Left (-diff)
src/BytePatch/Linear/Patch.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DataKinds, TypeFamilies #-}+ {- | Low-level patchscript processing and application. Patchscripts are applied as a list of @(skip x, write in-place y)@ commands. An@@ -13,44 +15,50 @@ -} module BytePatch.Linear.Patch- ( patch- , MonadFwdByteStream(..)+ (+ -- * Patch interface+ MonadFwdByteStream(..) , Cfg(..) , Error(..)++ -- * Prepared patchers+ , patchPure++ -- * General patcher+ , patch+ ) where import BytePatch.Core-import BytePatch.Linear.Core +import GHC.Natural import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Builder as BB import Control.Monad.State import Control.Monad.Reader import System.IO ( Handle, SeekMode(..), hSeek )+import Optics type Bytes = BS.ByteString -- TODO also require reporting cursor position (for error reporting) class Monad m => MonadFwdByteStream m where -- | Read a number of bytes without advancing the cursor.- readahead :: Int -> m Bytes+ readahead :: Natural -> m Bytes -- | Advance cursor without reading.- advance :: Int -> m ()+ advance :: Natural -> m () -- | Insert bytes into the stream at the cursor position, overwriting -- existing bytes. overwrite :: Bytes -> m () instance Monad m => MonadFwdByteStream (StateT (Bytes, BB.Builder) m) where- readahead n = do- (src, out) <- get- let (bs, src') = BS.splitAt n src- put (src', out)- return bs+ readahead n = BS.take (fromIntegral n) <$> gets fst advance n = do (src, out) <- get- let (bs, src') = BS.splitAt n src+ let (bs, src') = BS.splitAt (fromIntegral n) src put (src', out <> BB.byteString bs) overwrite bs = do (src, out) <- get@@ -60,7 +68,7 @@ instance MonadIO m => MonadFwdByteStream (ReaderT Handle m) where readahead n = do hdl <- ask- bs <- liftIO $ BS.hGet hdl n+ bs <- liftIO $ BS.hGet hdl (fromIntegral n) liftIO $ hSeek hdl RelativeSeek (- fromIntegral n) return bs advance n = do@@ -88,21 +96,24 @@ | ErrorPatchDidNotMatchExpected Bytes Bytes deriving (Eq, Show) -patch :: MonadFwdByteStream m => Cfg -> Patchscript Bytes -> m (Maybe Error)+patch+ :: MonadFwdByteStream m+ => Cfg -> [Patch 'FwdSeek Bytes]+ -> m (Maybe Error) patch cfg = go where go [] = return Nothing- go ((n, Overwrite bs meta):es) = do+ go (Patch n (Edit bs meta):es) = do advance n- bsStream <- readahead $ BS.length bs -- TODO catch overlong error+ bsStream <- readahead $ fromIntegral $ BS.length bs -- TODO catch overlong error -- if provided, strip trailing nulls from to-overwrite bytestring- case tryStripNulls bsStream (omNullTerminates meta) of+ case tryStripNulls bsStream (emNullTerminates meta) of Nothing -> return $ Just ErrorPatchUnexpectedNonnull Just bsStream' -> do -- if provided, check the to-overwrite bytestring matches expected- case checkExpected bsStream' (omExpected meta) of+ case checkExpected bsStream' (emExpected meta) of Just (bsa, bse) -> return $ Just $ ErrorPatchDidNotMatchExpected bsa bse Nothing -> overwrite bs >> go es @@ -125,15 +136,11 @@ then Nothing else Just (bs, bsExpected) -{--finishPurePatch :: (BS.ByteString, BB.Builder) -> BL.ByteString-finishPurePatch (src, out) = BB.toLazyByteString $ out <> BB.byteString src--tmpExPatchscript :: Patchscript-tmpExPatchscript =- [ (1, "ABC")- , (2, "DEFG") ]--tmpExInitialState :: (BS.ByteString, BB.Builder)-tmpExInitialState = ("abcdefghijklmnopqrstuvwxyz", mempty)--}+-- | Attempt to apply a patchscript to a 'Data.ByteString.ByteString'.+patchPure :: Cfg -> [Patch 'FwdSeek Bytes] -> BS.ByteString -> Either Error BL.ByteString+patchPure cfg ps bs =+ let (mErr, (bsRemaining, bbPatched)) = runState (patch cfg ps) (bs, mempty)+ bbPatched' = bbPatched <> BB.byteString bsRemaining+ in case mErr of+ Just err -> Left err+ Nothing -> Right $ BB.toLazyByteString bbPatched'
src/BytePatch/Pretty.hs view
@@ -1,12 +1,22 @@--- | Convenience interface to enable defining edits at offsets with some--- optional safety checks.+{-# LANGUAGE DataKinds, TypeFamilies, UndecidableInstances #-} +{-|+Convenience interface to enable defining edits at offsets with some optional+safety checks.++Redefines some types to enable us to easily leverage Aeson's generic JSON schema+deriving. That sadly means we can't use some of the interesting offset plumbing.++TODO I should definitely bite the bullet and use the plumbing now that it's so+cool.+-}+ module BytePatch.Pretty ( -- * Core types- MultiPatches(..)- , MultiPatch(..)- , Offset(..)+ CommonMultiEdits(..)+ , MultiEdit(..)+ , EditOffset(..) -- * Convenience functions , normalizeSimple@@ -18,96 +28,70 @@ ) where import BytePatch.Core-import BytePatch.Linear.Core import BytePatch.Pretty.PatchRep import qualified Data.ByteString as BS import Data.Maybe ( fromMaybe ) import GHC.Generics ( Generic )+import GHC.Natural type Bytes = BS.ByteString --- | Normalize a set of 'MultiPatches', discarding everything on error.-normalizeSimple :: PatchRep a => [MultiPatches a] -> Maybe [Patch Bytes]-normalizeSimple mps =- let (p, errs) = listAlgebraConcatEtc . map applyBaseOffset $ mps- in case errs of- _:_ -> Nothing- [] -> normalize p+-- | A list of 'MultiEdit's with some common configuration.+data CommonMultiEdits a = CommonMultiEdits+ { cmesBaseOffset :: Maybe (SeekRep 'CursorSeek)+ -- ^+ -- The base offset from which all offsets are located. An actual offset is+ -- calculated by adding the base offset to an offset. Actual offsets below 0+ -- are invalid, meaning for an offset @o@ with base offset @bo@, the actual+ -- offset is only valid when @o >= bo@. Negative base offsets are allowed. --- | A list of patches sharing a configuration, each applied at a list of--- offsets, abstracted over patch type.-data MultiPatches a = MultiPatches- { mpsBaseOffset :: Maybe Int- -- ^ The base offset from which all offsets are located. Subtracted from each- -- offset value to obtain the actual offset. Any offset located before the- -- base offset (x where x < base) is discarded as erroneous.- --- -- This feature enables us to allow negative offsets. For example, say you set- -- the base offset to @-10@. This is equivalent to stating that every offset- -- in the list is to be shifted +10 bytes. Thus, all offsets x where x >= -10- -- are now valid.- --- -- The original rationale behind this feature was to ease assembly patches on- -- ELFs. Decompilers focus on virtual addresses, and apparently (in my- -- experience) don't like to divulge physical file offsets. However, we can- -- recover the physical offset of any virtual address via the following steps:- --- -- 1. subtract the containing ELF segment's virtual address- -- 2. add the containing ELF segment's physical offset- --- -- So we can prepare a base offset @elf_vaddr - elf_phys_offset@, which we can- -- subtract from any virtual address inside that segment to retrieve its- -- related byte offset in the ELF file. Thus, you need do that calculation- -- manually once for every segment you patch, then you can use your- -- decompiler's virtual addresses!- --- -- You can even specify absolute offsets, which are compared to the calculated- -- actual offsets. So you get the best of both worlds!- --- -- Absolute offsets are only used for asserting correctness of calculated- -- actual offsets. If you want to mix absolute and base-relative offsets...- -- don't. I'm loath to support that, because I believe it would serve only to- -- confuse the patch file interface. Instead, group patches into absolute- -- (base offset = 0) and base-relative lists.+ , cmesEdits :: [MultiEdit 'CursorSeek a]+ } deriving (Eq, Show, Generic, Functor, Foldable, Traversable) - , mpsPatches :: [MultiPatch a]- } deriving (Eq, Show, Generic)+-- | A single edit to be applied at a list of offsets.+data MultiEdit (s :: SeekKind) a = MultiEdit+ { meData :: a -- ^ The value (e.g. bytes, text) to add.+ , meAt :: [EditOffset s a] -- ^ Offsets to apply edit at.+ } deriving (Generic, Functor, Foldable, Traversable) --- | A single patch applied at a list of offsets, parameterized by patch type.-data MultiPatch a = MultiPatch- { mpContents :: a- -- ^ The value to patch in. Likely a bytestring or text for simple uses.- , mpOffsets :: [Offset a]- } deriving (Eq, Show, Generic)+deriving instance (Eq (SeekRep s), Eq a) => Eq (MultiEdit s a)+deriving instance (Show (SeekRep s), Show a) => Show (MultiEdit s a) --- | An offset in a stream, with metadata about it to use when preparing the--- patch and at patch time.-data Offset a = Offset- { oOffset :: Int- -- ^ Stream offset to patch at.+-- | An edit offset, with metadata to use for preparing and applying the edit.+data EditOffset (s :: SeekKind) a = EditOffset+ { eoOffset :: SeekRep s+ -- ^ Stream offset for edit. - , oAbsoluteOffset :: Maybe Int- -- ^ Absolute stream offset to patch at. Compared with actual offset- -- (calculated from offset and base offset).+ , eoAbsOffset :: Maybe (SeekRep 'AbsSeek)+ -- ^ Absolute stream offset for edit. Used for checking against actual offset. - , oMaxLength :: Maybe Int- -- ^ Maximum bytestring length allowed to patch in at this offset.- -- TODO: use single range/span instead (default 0->x, also allow y->x)+ , eoMaxLength :: Maybe Natural+ -- ^ Maximum number of bytes allowed to write at this offset. - , oPatchMeta :: Maybe (OverwriteMeta a)- -- ^ Patch-time info for the overwrite at this offset.- --- -- Named "patch meta" instead of the more correct "overwrite meta" for more- -- friendly JSON field naming. We wrap it in a 'Maybe' for similar reasons,- -- plus it means the default can be inserted later on.- } deriving (Eq, Show, Generic)+ , eoEditMeta :: Maybe (EditMeta a)+ -- ^ Optional apply time metadata for the edit at this offset. + } deriving (Generic, Functor, Foldable, Traversable)++deriving instance (Eq (SeekRep s), Eq a) => Eq (EditOffset s a)+deriving instance (Show (SeekRep s), Show a) => Show (EditOffset s a)++-- | Normalize a list of 'CommonMultiEdits's, discarding everything on error.+normalizeSimple :: PatchRep a => [CommonMultiEdits a] -> Maybe [Patch 'AbsSeek Bytes]+normalizeSimple cmess =+ let (p, errs) = listAlgebraConcatEtc . map applyBaseOffset $ cmess+ in case errs of+ _:_ -> Nothing+ [] -> normalize p+ -- Drops no info, not easy to consume.-applyBaseOffset :: MultiPatches a -> (Int, [(MultiPatch a, [Offset a])])-applyBaseOffset mps =- (baseOffset, recalculateMultiPatchOffsets baseOffset (mpsPatches mps))- where baseOffset = fromMaybe 0 (mpsBaseOffset mps)+applyBaseOffset+ :: CommonMultiEdits a+ -> (Integer, [(MultiEdit 'AbsSeek a, [EditOffset 'CursorSeek a])])+applyBaseOffset cmes =+ (baseOffset, recalculateMultiPatchOffsets baseOffset (cmesEdits cmes))+ where baseOffset = fromMaybe 0 (cmesBaseOffset cmes) -- lmao this sucks. generalisation bad listAlgebraConcatEtc :: [(a, [(b, [c])])] -> ([b], [(c, a)])@@ -117,42 +101,54 @@ go' x (mp, offs) = (mp, map (\o -> (o, x)) offs) tuplemconcat = foldr (\(a, bs) (as, bs') -> (a:as, bs <> bs')) ([], mempty) -recalculateMultiPatchOffsets :: Int -> [MultiPatch a] -> [(MultiPatch a, [Offset a])]+recalculateMultiPatchOffsets+ :: Integer+ -> [MultiEdit 'CursorSeek a]+ -> [(MultiEdit 'AbsSeek a, [EditOffset 'CursorSeek a])] recalculateMultiPatchOffsets baseOffset = map go where- go :: MultiPatch a -> (MultiPatch a, [Offset a])- go mp =- let (osRecalculated, osInvalid) = recalculateOffsets baseOffset (mpOffsets mp)- in (mp { mpOffsets = osRecalculated }, osInvalid)+ go :: MultiEdit 'CursorSeek a -> (MultiEdit 'AbsSeek a, [EditOffset 'CursorSeek a])+ go me =+ let (osRecalculated, osInvalid) = recalculateOffsets baseOffset (meAt me)+ in (me { meAt = osRecalculated }, osInvalid) -recalculateOffsets :: Int -> [Offset a] -> ([Offset a], [Offset a])+recalculateOffsets+ :: Integer+ -> [EditOffset 'CursorSeek a]+ -> ([EditOffset 'AbsSeek a], [EditOffset 'CursorSeek a]) recalculateOffsets baseOffset = partitionMaybe go where- go o = if actualOffset >= 0 then Just (o { oOffset = actualOffset }) else Nothing- where actualOffset = oOffset o - baseOffset+ go o = let actualOffset = baseOffset + eoOffset o+ in case tryIntegerToNatural actualOffset of+ Nothing -> Nothing+ Just actualOffset' -> Just $ o { eoOffset = actualOffset' } -normalize :: PatchRep a => [MultiPatch a] -> Maybe [Patch Bytes]+tryIntegerToNatural :: Integer -> Maybe Natural+tryIntegerToNatural n | n < 0 = Nothing+ | otherwise = Just $ naturalFromInteger n++normalize :: PatchRep a => [MultiEdit 'AbsSeek a] -> Maybe [Patch 'AbsSeek Bytes] normalize xs = concat <$> mapM go xs- where go (MultiPatch contents os) = mapM (tryMakeSingleReplace contents) os+ where go (MultiEdit contents os) = mapM (tryMakeSingleReplace contents) os -- TODO now can error with "[expected] content has no valid patch rep"-tryMakeSingleReplace :: PatchRep a => a -> Offset a -> Maybe (Patch Bytes)-tryMakeSingleReplace contents (Offset os maos maxLen mMeta) =+tryMakeSingleReplace :: PatchRep a => a -> EditOffset 'AbsSeek a -> Maybe (Patch 'AbsSeek Bytes)+tryMakeSingleReplace contents (EditOffset os maos mMaxLen mMeta) = case toPatchRep contents of Left errStr -> error errStr -- TODO Right bs -> if offsetIsCorrect- then case maxLen of- Just len -> if BS.length bs > len then Nothing else overwrite bs- Nothing -> overwrite bs+ then case mMaxLen of+ Just maxLen -> if BS.length bs > fromIntegral maxLen then Nothing else overwrite bs+ Nothing -> overwrite bs else Nothing where overwrite bs = case traverse toPatchRep meta of Left errStr -> error errStr -- TODO- Right meta' -> Just $ Patch { patchContents = bs- , patchOffset = os- , patchMeta = meta' }- meta = fromMaybe (OverwriteMeta Nothing Nothing) mMeta+ Right meta' ->+ Just $ Patch os $ Edit { editData = bs+ , editMeta = meta' }+ meta = fromMaybe (EditMeta Nothing Nothing) mMeta offsetIsCorrect = case maos of Nothing -> True Just aos -> os == aos