IPSパッチ
なんかIPSというフォーマットのパッチがあるらしい。
てきとーにググるとWinIPSとかいうソフトが有名らしい。
MacだとIPSPatcherとかが有名らしい。
配布形式がみんなsitなのはなんか理由あるのだろうかとか考えつつ、sitの使い方が分からない。
分からないからググろう。
IPS形式について。
なぜここでsitについて調べずにipsについて調べたかというと、「なんとなく」だ。
まぁ、そんな日もあるさ。
で、IPSについて。
適当に転がってたIPSファイルを、かるくhexdump -Cあたりで読んでみるとPATCHから始まってEOFで終わることが分かる。
ふむ。
間のデータに関してはちと分からない。
分からなければググるのみ。
id:GOCHA氏のhatenaに仕様発見*1。
よく分からなかったのはビッグエンディアンだったからか。
多分最初にアドレスが来るだろうとか思っていたのだが、無意識にリトルエンディアンで読んでたから気がつかなかったらしい。
軽くまとめると
Magic (3 bytes, "PATCH") Clusters = ( Address (3 bytes big endian) Size (2 bytes big endian) Data (size byte/bytes) | Address (3 bytes big endian) (Size) (2 bytes, 00 00) Size (2bytes big endian) Data (1 byte) )* EOF (3 bytes, "EOF")
前者は単純なデータの上書き。後者はDataを使ってデータ範囲を同じ値で埋める。らしい。
むぅ。too much simple。
Address = "EOF" = 0x454f46
は正しいアドレスになり得るから、"EOF"を読んだからといってそこで終了にできないという。
まぁ、Size読もうとした段階でファイル終端に達したら、Address == "EOF"かチェックして、一致しなかったら異常終了、ってことになるのかな?
AddressとEOFはともに3 bytesだし。
...EOFの存在意義がよく分からない。最後までちゃんとダウンロードできました、ってか?
ヘッダにクラスタの個数書いたほうが...とか思うのは素人考えなんだろうか。
で、なんか単純そうなのでHaskellで書いてみた。
名前はまんまhsips。
import Prelude ( (*), (+), (-), Char, IO, Int, error ) import Array ( (//), elems, listArray ) import Char ( ord ) import Data.Eq ( (==), Eq ) import Data.Function ( ($), id ) import List ( (++), concat, foldl, length, replicate, splitAt, zip ) import Maybe ( Maybe( Just, Nothing ) ) import Monad ( MonadPlus, guard, mzero, return ) import System ( getArgs, getProgName ) import System.IO ( Handle, IOMode( ReadMode, WriteMode ), hClose, hGetContents, hPutStr, openBinaryFile, putStrLn, stdin, stdout ) main :: IO () main = do args <- getArgs case args of [ipsFile] -> do hIps <- openBinaryFile ipsFile ReadMode patch hIps stdin stdout hClose hIps [ipsFile, inFile] -> do hIps <- openBinaryFile ipsFile ReadMode hInput <- openBinaryFile inFile ReadMode patch hIps hInput stdout hClose hInput hClose hIps [ipsFile, inFile, outFile] -> do hIps <- openBinaryFile ipsFile ReadMode hInput <- openBinaryFile inFile ReadMode hOutput <- openBinaryFile outFile WriteMode patch hIps hInput hOutput hClose hOutput hClose hInput hClose hIps _ -> do progName <- getProgName putStrLn $ "usage: " ++ progName ++ " ips [input] [output]" unfoldr2 :: (b -> Maybe (a, b)) -> b -> ([a], b) unfoldr2 f x = case f x of Just (y, x') -> let (ys,xs) = unfoldr2 f x' in (y:ys,xs) Nothing -> ([], x) patch :: Handle -> Handle -> Handle -> IO () patch hIps hInput hOutput = do ips <- hGetContents hIps input <- hGetContents hInput let inputArray = listArray (0, length input - 1) input case readIPS ips of Just (patches, []) -> hPutStr hOutput $ elems $ inputArray // patches _ -> error "IPS format error" readIPS :: (MonadPlus m) => [Char] -> m ([(Int, Char)], [Char]) readIPS source = do (_, x1) <- readMagic source (patches, x2) <- return $ unfoldr2 readCluster x1 (_, x3) <- readEOF x2 return (concat patches, x3) readCluster :: (MonadPlus m) => [Char] -> m ([(Int, Char)], [Char]) readCluster source = do (a, x1) <- readAddr source (s, x2) <- readSize x1 if s == 0 then do (s2, x3) <- readSize x2 (d, x4) <- readData 1 x3 let patches = zip [a..] $ concat $ replicate s2 d return $ (patches, x4) else do (d, x3) <- readData s x2 let patches = zip [a..] $ d return $ (patches, x3) readMagic :: (MonadPlus m) => [Char] -> m ((), [Char]) readMagic source = do (x,rest) <- readData 5 source guard (x == "PATCH") return ((), rest) readEOF :: (MonadPlus m) => [Char] -> m ((), [Char]) readEOF source = do (x,rest) <- readData 3 source guard (x == "EOF") return ((), rest) readAddr :: (MonadPlus m) => [Char] -> m (Int, [Char]) readAddr = fetchData readBigendian 3 readSize :: (MonadPlus m) => [Char] -> m (Int, [Char]) readSize = fetchData readBigendian 2 readData :: (MonadPlus m) => Int -> [Char] -> m ([Char], [Char]) readData = fetchData id fetchData :: (MonadPlus m) => ([Char] -> a) -> Int -> [Char] -> m (a, [Char]) fetchData f n xxs = if n == length x then return (f x, xs) else mzero where (x,xs) = splitAt n xxs readBigendian :: [Char] -> Int readBigendian = foldl (\x y -> x * 0x100 + ord y) 0
とりあえずdoを使いたかったです、的な実装です。
いや、Maybe Monadを使いたかったんですよ。はい。
そんなわけで、最初read*系は"String -> Maybe (a, String)"型にしてて Maybe a"だった。Address == "EOF"問題に気がついて『残り』も返すようにした。">*2、途中でこれもっと一般化できるかも、とか思って"(MonadPlus m) => "に変えてみたのだが...。
見れば見るほどReadS [(a, String)]"。リスト([])もMonadかつMonadPlus。">*3です。本当にありがとうございました。
よくよく考えればやってることはパースだし、最初からReadSでやればよかったとかね...。
まぁ、動いてるのでよしとします。
実装自体はなんども手直ししたのでそこそこきれいになった気がする。と自画自賛。
doでMaybe Monadを使いまくってるので、ぱっとみカオス。
と、いうことで、なんとなくIPSパッチャー作ってみたけど...、そういやなんでIPSパッチャー必要だったんだっけ?
とかいうオチ。
*1:http://d.hatena.ne.jp/GOCHA/20060206/ips
*2:もっと正確に『最初』に書いたのは"String -> Maybe a"だった。Address == "EOF"問題に気がついて『残り』も返すようにした。
*3:ReadS = "String -> [(a, String)]"。リスト([])もMonadかつMonadPlus。