diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -20,67 +20,47 @@
 The tttool tool
 ---------------
 
-Use the tool `tttool.hs` to investigate the gme files and build new ones. It
+Use the tool `tttool` to investigate the gme files and build new ones. It
 supports various subcommands:
 
-    Usage: tttool [options] command
+    GME creation commands:
+    assemble                 creates a gme file from the given source
 
-    Options:
-        -t <transcriptfile>
-           in the screen output, replaces media file indices by a transscript
+    OID code creation commands:
 
-    Commands:
-        info <file.gme>...
-           general information
-        media [-d dir] <file.gme>...
-           dumps all audio samples to the given directory (default: media/)
-        scripts <file.gme>...
-           prints the decoded scripts for each OID
-        script <file.gme> <n>
-           prints the decoded scripts for the given OID
-        raw-scripts <file.gme>...
-           prints the scripts for each OID, in their raw form
-        raw-script <file.gme> <n>
-           prints the scripts for the given OID, in their raw form
-        binaries [-d dir] <file.gme>...
-           dumps all binaries to the given directory (default: binaries/)
-        games <file.gme>...
-           prints the decoded games
-        lint <file.gme>
-           checks for errors in the file or in this program
-        segments <file.gme>...
-           lists all known parts of the file, with description.
-        segment <file.gme> <pos>
-           which segment contains the given position.
-        holes <file.gme>...
-           lists all unknown parts of the file.
-        explain <file.gme>...
-           lists all parts of the file, with description and hexdump.
-        play <file.gme or file.yaml>
-           interactively play: Enter OIDs, and see what happens.
-        rewrite <infile.gme> <outfile.gme>
-           parses the file and serializes it again (for debugging).
-        export <infile.gme> [<outfile.yaml>]
-           dumps the file in the human-readable yaml format
-        assemble <infile.yaml> <outfile.gme>
-           creates a gme file from the given source
-        oid-code [-d DPI] <codes>
-           creates a PNG file for each given code
-           scale this to 10cm×10cm
-           By default, it creates a 1200 dpi image. With -d 600, you
-           obtain a 600 dpi image. With -d 600d resp. 1200d you can double the
-           size of the pixel.
-           <codes> can be a range, e.g. 1,3,1000-1085.
-           Uses oid-<code>.png as the file name.
-        oid-code [-d DPI] <infile.yaml>
-           Like above, but creates one file for each code in the yaml file.
-           Uses oid-<product-id>-<scriptname or code>.png as the file name.
-        raw-oid-code [-d DPI] <raw codes>
-           creates a PNG file with the given "raw code". Usually not needed.
-           Uses oid-raw-<code>.png as the file name.
+    oid-table                creates a PDF file with all codes in the yaml file
+    oid-codes                creates PNG files for every OID in the yaml file.
+    oid-code                 creates PNG files for each given code(s)
 
-A transscript is simply a `;`-separated table of OIDs and some text, see for example [`transcript/WWW_Bauernhof.csv`](transcript/WWW_Bauernhof.csv).
+    GME analysis commands:
+    info                     Print general information about a GME file
+    export                   dumps the file in the human-readable yaml format
+    scripts                  prints the decoded scripts for each OID
+    script                   prints the decoded scripts for a specific OID
+    games                    prints the decoded games
+    lint                     checks for errors in the file or in this program
+    segments                 lists all known parts of the file, with description.
+    segment                  prints the decoded scripts for a specific OID
+    explain                  print a hexdump of a GME file with descriptions
+    holes                    lists all unknown parts of the file.
+    rewrite                  parses the file and reads it again (for debugging)
 
+    GME extraction commands:
+    media                    dumps all audio samples
+    binaries                 dumps all binaries
+
+    Simulation commands:
+    play                     interactively play a GME file
+
+Run
+
+    ./tttool --help
+
+to learn about global options (e.g. DPI settings), and
+
+    ./tttool command --help
+
+for the options of the individual command.
 
 Installation
 ------------
diff --git a/src/Commands.hs b/src/Commands.hs
new file mode 100644
--- /dev/null
+++ b/src/Commands.hs
@@ -0,0 +1,341 @@
+{-# LANGUAGE CPP, RecordWildCards, TupleSections #-}
+module Commands where
+
+import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Char8 as BC
+import System.Exit
+import System.FilePath
+import qualified Data.Binary.Builder as Br
+import qualified Data.Binary.Get as G
+import Text.Printf
+import Data.Bits
+import Data.List
+import Data.Either
+import Data.Functor
+import Data.Maybe
+import Data.Ord
+import Control.Monad
+import System.Directory
+import qualified Data.Map as M
+import Data.Foldable (for_)
+
+import Types
+import Constants
+import KnownCodes
+import GMEParser
+import GMEWriter
+import GMERun
+import PrettyPrint
+import OidCode
+import OidTable
+import Utils
+import TipToiYaml
+import Lint
+
+-- Main commands
+
+dumpAudioTo :: FilePath -> FilePath -> IO ()
+dumpAudioTo directory file = do
+    (tt,_) <- parseTipToiFile <$> B.readFile file
+
+    printf "Audio Table entries: %d\n" (length (ttAudioFiles tt))
+
+    createDirectoryIfMissing False directory
+    forMn_ (ttAudioFiles tt) $ \n audio -> do
+        let audiotype = maybe "raw" snd $ find (\(m,t) -> m `B.isPrefixOf` audio) fileMagics
+        let filename = printf "%s/%s_%d.%s" directory (takeBaseName file) n audiotype
+        if B.null audio
+        then do
+            printf "Skipping empty file %s...\n" filename
+        else do
+            B.writeFile filename audio
+            printf "Dumped sample %d as %s\n" n filename
+
+dumpBinariesTo :: FilePath -> FilePath -> IO ()
+dumpBinariesTo directory file = do
+    (TipToiFile {..},_) <- parseTipToiFile <$> B.readFile file
+
+    let binaries =
+            map (1,) ttBinaries1 ++
+            map (2,) ttBinaries2 ++
+            map (3,) ttBinaries3 ++
+            map (4,) ttBinaries4
+
+    printf "Binary Table entries: %d\n" (length binaries)
+
+    createDirectoryIfMissing False directory
+    forM_ binaries $ \(n,(desc,binary)) -> do
+        let filename = printf "%s/%d_%s" directory (n::Int) (BC.unpack desc)
+        if B.null binary
+        then do
+            printf "Skipping empty file %s...\n" filename
+        else do
+            B.writeFile filename binary
+            printf "Dumped binary %s from block %d as %s\n" (BC.unpack desc) n filename
+
+dumpScripts :: Conf -> Bool -> Maybe Int -> FilePath -> IO ()
+dumpScripts conf raw sel file = do
+    t <- readTransscriptFile (cTransscriptFile conf)
+    bytes <- B.readFile file
+    let (tt,_) = parseTipToiFile bytes
+        st' | Just n <- sel = filter ((== fromIntegral n) . fst) (ttScripts tt)
+            | otherwise     = ttScripts tt
+
+    forM_ st' $ \(i, ms) -> case ms of
+        Nothing -> do
+            printf "Script for OID %d: Disabled\n" i
+        Just lines -> do
+            printf "Script for OID %d:\n" i
+            forM_ lines $ \line -> do
+                if raw then printf "%s\n"     (lineHex bytes line)
+                       else printf "    %s\n" (ppLine t line)
+
+
+dumpInfo :: Conf -> FilePath -> IO ()
+dumpInfo conf file = do
+    t <- readTransscriptFile (cTransscriptFile conf)
+    (TipToiFile {..},_) <- parseTipToiFile <$> B.readFile file
+    let st = ttScripts
+
+    printf "Product ID: %d\n" ttProductId
+    printf "Raw XOR value: 0x%08X\n" ttRawXor
+    printf "Magic XOR value: 0x%02X\n" ttAudioXor
+    printf "Comment: %s\n" (BC.unpack ttComment)
+    printf "Date: %s\n" (BC.unpack ttDate)
+    printf "Number of registers: %d\n" (length ttInitialRegs)
+    printf "Initial registers: %s\n" (show ttInitialRegs)
+    printf "Initial sounds: %s\n" (ppPlayListList t ttWelcome)
+    printf "Scripts for OIDs from %d to %d; %d/%d are disabled.\n"
+        (fst (head st)) (fst (last st))
+        (length (filter (isNothing . snd) st)) (length st)
+    printf "Audio table entries: %d\n" (length ttAudioFiles)
+    when ttAudioFilesDoubles $ printf "Audio table repeated twice\n"
+    printf "Binary tables entries: %d/%d/%d/%d\n"
+        (length ttBinaries1)
+        (length ttBinaries2)
+        (length ttBinaries3)
+        (length ttBinaries4)
+    for_ ttSpecialOIDs $ \(oid1, oid2) ->
+        printf "Speical OIDs: %d, %d\n" oid1 oid2
+    printf "Checksum found 0x%08X, calculated 0x%08X\n" ttChecksum ttChecksumCalc
+
+lint :: FilePath -> IO ()
+lint file = do
+    (tt,segments) <- parseTipToiFile <$> B.readFile file
+    lintTipToi tt segments
+
+play :: Conf -> FilePath -> IO ()
+play conf file = do
+    t <- readTransscriptFile (cTransscriptFile conf)
+    (cm,tt) <-
+        if ".yaml" `isSuffixOf` file
+        then do
+            (tty, extraCodeMap) <- readTipToiYaml file
+            (tt, codeMap) <- ttYaml2tt (takeDirectory file) tty extraCodeMap
+            return (codeMap, tt)
+        else do
+            (tt,_) <- parseTipToiFile <$> B.readFile file
+            return (M.empty, tt)
+    playTipToi cm t tt
+
+segments :: FilePath -> IO ()
+segments file = do
+    (tt,segments) <- parseTipToiFile <$> B.readFile file
+    mapM_ printSegment segments
+
+printSegment (o,l,desc) = printf "At 0x%08X Size %8d: %s\n" o l (ppDesc desc)
+
+explain :: FilePath -> IO ()
+explain file = do
+    bytes <- B.readFile file
+    let (tt,segments) = parseTipToiFile bytes
+    forM_ (addHoles segments) $ \e -> case e of
+        Left (o,l) -> do
+            printSegment (o,l,["-- unknown --"])
+            printExtract bytes o l
+            putStrLn ""
+        Right ss@((o,l,_):_) -> do
+            mapM_ printSegment ss
+            printExtract bytes o l
+            putStrLn ""
+
+printExtract :: B.ByteString -> Offset -> Word32 -> IO ()
+printExtract b o 0 = return ()
+printExtract b o l = do
+    let o1 = o .&. 0xFFFFFFF0
+    lim_forM_ [o1, o1+0x10 .. (o + l-1)] $ \s -> do
+        let s' = max o s
+        let d  = fromIntegral s' - fromIntegral s
+        let l' = (min (o + l) (s + 0x10)) - s'
+        printf "   0x%08X: %s%s\n"
+            s
+            (replicate (d*3) ' ')
+            (prettyHex (extract s' l' b))
+  where
+    lim_forM_ l act
+        = if length l > 30
+          then do act (head l)
+                  printf "   (skipping %d lines)\n" (length l - 2) :: IO ()
+                  act (last l)
+          else do forM_ l act
+
+findPosition :: Integer -> FilePath -> IO ()
+findPosition pos' file = do
+    (tt,segments) <- parseTipToiFile <$> B.readFile file
+    case find (\(o,l,_) -> pos >= o && pos < o + l) segments of
+        Just s -> do
+            printf "Offset 0x%08X is part of this segment:\n" pos
+            printSegment s
+        Nothing -> do
+            let before = filter (\(o,l,_) -> pos >= o + l) segments
+                after = filter (\(o,l,_) -> pos < o) segments
+                printBefore | null before = printf "(nothing before)\n"
+                            | otherwise   = printSegment (maximumBy (comparing (\(o,l,_) -> o+l)) before)
+                printAfter  | null after  = printf "(nothing after)\n"
+                            | otherwise   = printSegment (minimumBy (comparing (\(o,l,_) -> o)) after)
+            printf "Offset %08X not found. It lies between these two segments:\n" pos
+            printBefore
+            printAfter
+
+    where
+    pos = fromIntegral pos'
+
+
+-- returns a list of segments in case of overlap
+addHoles :: [Segment] -> [Either (Offset, Word32) [Segment]]
+addHoles = go 0
+   where go at [] = []
+         go at ss@((o,l,d):_)
+            | at /= o -- a hole
+            = Left (at, o-at) : go o ss
+            | otherwise -- no hole
+            = let (this, others) = span (\(o',l',_) -> o == o' && l == l') ss
+              in Right this : go (o + l) others
+
+unknown_segments :: FilePath -> IO ()
+unknown_segments file = do
+    bytes <- B.readFile file
+    let (_,segments) = parseTipToiFile bytes
+    let unknown_segments =
+            filter (\(o,l) -> not
+                (l == 2 && G.runGet (G.skip (fromIntegral o) >> G.getWord16le) bytes == 0)) $
+            lefts $ addHoles $ segments
+    printf "Unknown file segments: %d (%d bytes total)\n"
+        (length unknown_segments) (sum (map snd unknown_segments))
+    forM_ unknown_segments $ \(o,l) ->
+        printf "   Offset: %08X to %08X (%d bytes)\n" o (o+l) l
+
+
+withEachFile :: (FilePath -> IO ()) -> [FilePath] -> IO ()
+withEachFile _ [] = error "withEachFile []"
+withEachFile a [f] = a f 
+withEachFile a fs = forM_ fs $ \f -> do 
+    printf "%s:\n" f 
+    a f
+
+
+dumpGames :: Conf -> FilePath -> IO ()
+dumpGames conf file = do
+    t <- readTransscriptFile (cTransscriptFile conf)
+    bytes <- B.readFile file
+    let (tt,_) = parseTipToiFile bytes
+    forMn_ (ttGames tt) $ \n g -> do
+        printf "Game %d:\n" n
+        printf "%s\n" (ppGame t g)
+
+writeTipToi :: FilePath -> TipToiFile -> IO ()
+writeTipToi out tt = do
+    let bytes = writeTipToiFile tt
+    let checksum = B.foldl' (\s b -> fromIntegral b + s) 0 bytes
+    B.writeFile out $ Br.toLazyByteString $
+        Br.fromLazyByteString bytes `Br.append` Br.putWord32le checksum
+
+rewrite :: FilePath -> FilePath -> IO ()
+rewrite inf out = do
+    (tt,_) <- parseTipToiFile <$> B.readFile inf
+    writeTipToi out tt
+
+export :: FilePath -> FilePath -> IO ()
+export inf out = do
+    (tt,_) <- parseTipToiFile <$> B.readFile inf
+    let tty = tt2ttYaml (printf "media/%s_%%s" (takeBaseName inf)) tt
+    ex <- doesFileExist out
+    if ex
+        then printf "File \"%s\" does already exist. Please remove it first\nif you want to export \"%s\" again.\n" out inf >> exitFailure
+        else writeTipToiYaml out tty
+
+
+assemble :: FilePath -> FilePath -> IO ()
+assemble inf out = do
+    (tty, codeMap) <- readTipToiYaml inf
+    (tt, totalMap) <- ttYaml2tt (takeDirectory inf) tty codeMap
+    warnTipToi tt
+    writeTipToiCodeYaml inf tty codeMap totalMap
+    writeTipToi out tt
+
+genOidTable :: Conf -> FilePath -> FilePath -> IO ()
+genOidTable conf inf out = do
+    (tty, codeMap) <- readTipToiYaml inf
+    (tt, totalMap) <- ttYaml2tt (takeDirectory inf) tty codeMap
+    let codes = ("START", fromIntegral (ttProductId tt)) : M.toList totalMap
+    let pdfFile = oidTable conf inf codes
+    B.writeFile out pdfFile
+
+genPNGsForFile :: Conf -> FilePath -> IO ()
+genPNGsForFile conf inf = do
+    (tty, codeMap) <- readTipToiYaml inf
+    (tt, totalMap) <- ttYaml2tt (takeDirectory inf) tty codeMap
+    let codes = ("START", fromIntegral (ttProductId tt)) : M.toList totalMap
+    forM_  codes $ \(s,c) -> do
+        genPNG conf c $ printf "oid-%d-%s.png" (ttProductId tt) s
+
+genPNG :: Conf -> Word16 -> String -> IO ()
+genPNG conf c filename =
+    case code2RawCode c of
+        Nothing -> printf "Skipping %s, code %d not known.\n" filename c
+        Just r -> do
+            printf "Writing %s.. (Code %d, raw code %d)\n" filename c r
+            genRawPNG' conf r filename
+
+genPNGsForCodes :: Bool -> Conf -> [Word16] -> IO ()
+genPNGsForCodes False conf codes =
+    forM_ codes $ \c -> do
+        genPNG conf c $ printf "oid-%d.png" c
+genPNGsForCodes True conf codes =
+    forM_ codes $ \r -> do
+        let filename = printf "oid-raw-%d.png" r
+        printf "Writing %s... (raw code %d)\n" filename r
+        genRawPNG' conf r filename
+
+genRawPNG' :: Conf -> Word16 -> FilePath -> IO ()
+genRawPNG' conf =
+    genRawPNG w h (cDPI conf) (cPixelSize conf)
+  where
+    (w,h) = cCodeDimPixels conf
+
+cCodeDimPixels :: Conf -> (Int, Int)
+cCodeDimPixels conf = (w',h')
+  where
+    (w,h) = cCodeDim conf
+    -- (Roughly) 25mm per inch
+    w' = (w * cDPI conf) `div` 25
+    h' = (h * cDPI conf) `div` 25
+
+readTransscriptFile :: Maybe FilePath -> IO Transscript
+readTransscriptFile Nothing = return M.empty
+readTransscriptFile (Just transcriptfile_) = do
+    file <- readFile transcriptfile_
+    return $ M.fromList
+        [ (idx, string)
+        | l <- lines file
+        , (idxstr:string:_) <- return $ wordsWhen (';'==) l
+        , Just idx <- return $ readMaybe idxstr
+        ]
+
+-- Avoiding dependencies, using code from http://stackoverflow.com/a/4981265/946226
+wordsWhen     :: (Char -> Bool) -> String -> [String]
+wordsWhen p s =  case dropWhile p s of
+                      "" -> []
+                      s' -> w : wordsWhen p s''
+                            where (w, s'') = break p s'
+
diff --git a/src/GMEParser.hs b/src/GMEParser.hs
--- a/src/GMEParser.hs
+++ b/src/GMEParser.hs
@@ -17,6 +17,7 @@
 import Control.Monad.RWS.Strict
 import Control.Exception
 import Control.Arrow
+import Debug.Trace
 
 import Types
 import Constants
@@ -193,7 +194,15 @@
         ]
 
     actions =
-        [ (B.pack [0xE8,0xFF], \r -> do
+        [ (B.pack [0xE0,0xFF], \r -> do
+            unless (r == 0) $ fail "Non-zero register for RandomVariant command"
+            Const 0x0000 <- getTVal
+            return RandomVariant)
+        , (B.pack [0xE1,0xFF], \r -> do
+            unless (r == 0) $ fail "Non-zero register for PlayAllVariant command"
+            Const 0x0000 <- getTVal
+            return PlayAllVariant)
+        , (B.pack [0xE8,0xFF], \r -> do
             unless (r == 0) $ fail "Non-zero register for Play command"
             Const n <- getTVal
             return (Play n))
@@ -201,6 +210,10 @@
             unless (r == 0) $ fail "Non-zero register for Random command"
             Const n <- getTVal
             return (Random (lowbyte n) (highbyte n)))
+        , (B.pack [0x00,0xFB], \r -> do
+            unless (r == 0) $ fail "Non-zero register for PlayAll command"
+            Const n <- getTVal
+            return (PlayAll (lowbyte n) (highbyte n)))
         , (B.pack [0xFF,0xFA], \r -> do
             unless (r == 0) $ fail "Non-zero register for Cancel command"
             Const 0xFFFF <- getTVal
@@ -216,7 +229,10 @@
         , (B.pack [0xF8,0xFF], \r -> do
             _ <- getTVal
             return (Neg r))
-        ] ++ 
+        , (B.pack [0x00,0xFF], \r -> do
+            v <- getTVal
+            return (Timer r v))
+        ] ++
         [ (B.pack (arithOpCode o), \r -> do
             n <- getTVal
             return (ArithOp o r n))
@@ -286,12 +302,15 @@
 getPlayList :: SGet PlayList
 getPlayList = getArray getWord16 getWord16
 
+getGameIdList :: SGet [GameId]
+getGameIdList = getArray getWord16 (subtract 1 <$> getWord16)
+
+getGameIdListList :: SGet [[GameId]]
+getGameIdListList = indirections getWord16 "" getGameIdList
+
 getOidList :: SGet [OID]
 getOidList = getArray getWord16 getWord16
 
-getGidList :: SGet [OID]
-getGidList = getArray getWord16 getWord16
-
 getPlayListList :: SGet PlayListList
 getPlayListList = indirections getWord16 "" getPlayList
 
@@ -304,52 +323,71 @@
     plls <- indirections (return 9) "playlistlist " getPlayListList
     return (SubGame u oid1s oid2s oid3s plls)
 
+
 getGame :: SGet Game
 getGame = do
-    t <- getWord16
-    case t of
-      6 -> do
-        b <- getWord16
-        u1 <- getWord16
-        c <- getWord16
-        u2 <- getBS 18
-        plls <- indirections (return 7) "playlistlistA-" getPlayListList
-        sg1s <- indirections (return b) "subgameA-" getSubGame
-        sg2s <- indirections (return c) "subgameB-" getSubGame
-        u3 <- getBS 20
-        pll2s <- indirections (return 10) "playlistlistB-" getPlayListList
-        pl <- indirection "playlist" getPlayList
-
-        return (Game6 u1 u2 plls sg1s sg2s u3 pll2s pl)
-      7 -> do
-        (u1,c,u2,plls, sgs, u3, pll2s) <- common
-        pll <- indirection "playlistlist" getPlayListList
-        return (Game7 u1 c u2 plls sgs u3 pll2s pll)
-      8 -> do
-        (u1,c,u2,plls, sgs, u3, pll2s) <- common
-        oidl <- indirection "oidlist" getOidList
-        gidl <- indirection "gidlist" getGidList
-        pll1 <- indirection "playlistlist1" getPlayListList
-        pll2 <- indirection "playlistlist2" getPlayListList
-        return (Game8 u1 c u2 plls sgs u3 pll2s oidl gidl pll1 pll2)
+    gGameType <- getWord16
+    getRealGame gGameType
 
-      253 -> do
-        return Game253
+getRealGame :: Word16 -> SGet Game
+getRealGame 253 = return Game253
+getRealGame gGameType = do
+    gSubgameCount             <- getWord16
+    gRounds                   <- getWord16
+    gUnknownC                 <- getIf (/=6) getWord16
+    gBonusSubgameCount        <- getIf (==6) getWord16
+    gBonusRounds              <- getIf (==6) getWord16
+    gBonusTarget              <- getIf (==6) getWord16
+    gUnknownI                 <- getIf (==6) getWord16
+    gEarlyRounds              <- getWord16
+    gUnknownQ                 <- getIf (==6) getWord16
+    gRepeatLastMedia          <- getWord16
+    gUnknownX                 <- getWord16
+    gUnknownW                 <- getWord16
+    gUnknownV                 <- getWord16
+    gStartPlayList            <- indirection "startplaylist" getPlayListList
+    gRoundEndPlayList         <- indirection "roundendplaylist" getPlayListList
+    gFinishPlayList           <- indirection "finishplaylist" getPlayListList
+    gRoundStartPlayList       <- indirection "roundstartplaylist" getPlayListList
+    gLaterRoundStartPlayList  <- indirection "laterroundstartplaylist" getPlayListList
+    gRoundStartPlayList2      <- getIf (==6) $ indirection "roundendplaylist2" getPlayListList
+    gLaterRoundStartPlayList2 <- getIf (==6) $ indirection "laterroundstartplaylist2" getPlayListList
+    let subgameCount | gGameType == 6 = gSubgameCount + gBonusSubgameCount
+                     | otherwise      = gSubgameCount
+    gSubgames                 <- indirections (return subgameCount) "subgame-" getSubGame
+    gTargetScores             <- if gGameType == 6 then replicateM 2 getWord16
+                                                   else replicateM 10 getWord16
+    gBonusTargetScores        <- getIf (==6) $ replicateM 8 getWord16
+    let fplCount | gGameType == 6 = 2
+                 | otherwise      = 10
+    gFinishPlayLists          <- indirections (return fplCount) "finishplaylist-" getPlayListList
+    gBonusFinishPlayLists     <- getIf (==6) $ indirections (return 8) "bonus finishplaylist-" getPlayListList
+    gBonusSubgameIds          <- getIf (==6) $ indirection "subgameidlist" getGameIdList
+    gSubgameGroups            <- getIf (==7) $ indirection "subgamegroups" getGameIdListList
+    gGameSelectOIDs           <- getIf (==8) $ indirection "gameSelectOids" getOidList
+    gGameSelect               <- getIf (==8) $ indirection "gameSelect" getGameIdList
+    gGameSelectErrors1        <- getIf (==8) $ indirection "gameSelectErrors1" getPlayListList
+    gGameSelectErrors2        <- getIf (==8) $ indirection "gameSelectErrors2" getPlayListList
+    gExtraOIDs                <- getIf (==16) $ indirection "extra oids" getOidList
+    gExtraPlayLists           <- case gGameType of
+        9 ->  indirections (return 75) "playlist-" getPlayListList
+        10 -> indirections (return 1) "playlist-" getPlayListList
+        16 -> indirections (return 3) "playlist-" getPlayListList
+        _ -> return $ error $ "error in gExtraPlayLists"
 
-      _ -> do
-        (u1,c,u2,plls, sgs, u3, pll2s) <- common
-        return (UnknownGame t u1 c u2 plls sgs u3 pll2s)
- where
-    common = do -- the common header of a non-type-6-game
-        b <- getWord16
-        u1 <- getWord16
-        c <- getWord16
-        u2 <- getBS 10
-        plls <- indirections (return 5) "playlistlistA-" getPlayListList
-        sgs <- indirections (return b) "subgame-" getSubGame
-        u3 <- getBS 20
-        pll2s <- indirections (return 10) "playlistlistB-" getPlayListList
-        return (u1, c, u2, plls, sgs, u3, pll2s)
+    case gGameType of
+      6 -> return $ Game6 {..}
+      7 -> return $ Game7 {..}
+      8 -> return $ Game8 {..}
+      9 -> return $ Game9 {..}
+      10 -> return $ Game10 {..}
+      16 -> return $ Game16 {..}
+      253 -> return Game253
+      _ -> return $ CommonGame {..}
+  where
+    getIf :: (Word16 -> Bool) -> SGet a -> SGet a
+    getIf p a | p gGameType = a
+              | otherwise   = return (error "getIf used wrongly")
 
 
 getInitialRegs :: SGet [Word16]
@@ -380,7 +418,7 @@
 
     jumpTo 0x0090
     ttBinaries1 <- fromMaybe [] <$> maybeIndirection "Binaries 1" getBinaries
-    ttSpecialOIDs <- maybeIndirection "specials symbols" getSpecials
+    ttSpecialOIDs <- maybeIndirection "special symbols" getSpecials
     ttBinaries2 <- fromMaybe [] <$> maybeIndirection "Binaries 1" getBinaries
 
     jumpTo 0x00A0
diff --git a/src/GMEWriter.hs b/src/GMEWriter.hs
--- a/src/GMEWriter.hs
+++ b/src/GMEWriter.hs
@@ -8,8 +8,9 @@
 import Control.Monad
 import Control.Applicative (Applicative)
 import qualified Data.Map as M
-import Control.Monad.Writer.Strict
-import Control.Monad.State.Strict
+import Control.Monad.Writer.Lazy
+import Control.Monad.State.Lazy
+import Debug.Trace
 
 import Types
 import Constants
@@ -107,22 +108,149 @@
     seek 0x0200 -- Just to be safe
     sto <- getAddress $ putScriptTable ttScripts
     ast <- getAddress $ putWord16 0x00 -- For now, no additional script table
-    gto <- getAddress $ putGameTable
+    gto <- getAddress $ putGameTable ttGames
     iro <- getAddress $ putInitialRegs ttInitialRegs
     mft <- getAddress $ putAudioTable ttAudioXor ttAudioFiles
-    ipllo <- getAddress $ putOffsets putWord16 $ map putPlayList ttWelcome
+    ipllo <- getAddress $ putPlayListList ttWelcome
     return ()
 
-putGameTable :: SPut
-putGameTable = mdo
-    putWord32 1 -- Hardcoded empty
-    putWord32 offset
-    offset <- getAddress $ do
-       putWord16 253
-       putWord16 0
+putGameTable :: [Game] -> SPut
+putGameTable games = putOffsets putWord32 $ map putGame games
+
+putGame :: Game -> SPut
+putGame (Game6 {..}) = mdo
+    putWord16 6
+    putWord16 (fromIntegral (length gSubgames) - gBonusSubgameCount)
+    putWord16 gRounds
+    putWord16 gBonusSubgameCount
+    putWord16 gBonusRounds
+    putWord16 gBonusTarget
+    putWord16 gUnknownI
+    putWord16 gEarlyRounds
+    putWord16 gUnknownQ
+    putWord16 gRepeatLastMedia
+    putWord16 gUnknownX
+    putWord16 gUnknownW
+    putWord16 gUnknownV
+    putWord32 spl
+    putWord32 repl
+    putWord32 fpl
+    putWord32 rspl
+    putWord32 lrspl
+    putWord32 rspl2
+    putWord32 lrspl2
+    mapM_ putWord32 sgo
+    mapM_ putWord16 gTargetScores
+    mapM_ putWord16 gBonusTargetScores
+    mapM_ putWord32 fpll
+    mapM_ putWord32 fpll2
+    putWord32 gilo
+
+
+    spl   <- getAddress $ putPlayListList gStartPlayList
+    repl  <- getAddress $ putPlayListList gRoundEndPlayList
+    fpl   <- getAddress $ putPlayListList gFinishPlayList
+    rspl  <- getAddress $ putPlayListList gRoundStartPlayList
+    lrspl <- getAddress $ putPlayListList gLaterRoundStartPlayList
+    rspl2  <- getAddress $ putPlayListList gRoundStartPlayList2
+    lrspl2 <- getAddress $ putPlayListList gLaterRoundStartPlayList2
+    fpll  <- mapM (getAddress . putPlayListList) gFinishPlayLists
+    fpll2  <- mapM (getAddress . putPlayListList) gBonusFinishPlayLists
+
+    sgo <- mapM (getAddress . putSubGame) gSubgames
+
+    gilo <- getAddress $ putGameIdList gBonusSubgameIds
+
     return ()
 
+putGame (Game253) = mdo
+    putWord16 253
 
+putGame g = mdo
+    putWord16 (gameType g)
+    putWord16 (fromIntegral $ length (gSubgames g))
+    putWord16 (gRounds g)
+    putWord16 (gUnknownC g)
+    putWord16 (gEarlyRounds g)
+    putWord16 (gRepeatLastMedia g)
+    putWord16 (gUnknownX g)
+    putWord16 (gUnknownW g)
+    putWord16 (gUnknownV g)
+    putWord32 spl
+    putWord32 repl
+    putWord32 fpl
+    putWord32 rspl
+    putWord32 lrspl
+    mapM_ putWord32 sgo
+    mapM_ putWord16 (gTargetScores g)
+    mapM_ putWord32 fpll
+
+    case g of
+        Game7 {..} -> mdo
+            putWord32 sggo
+            sggo <- getAddress $ do
+                putOffsets putWord16 $ map putGameIdList gSubgameGroups
+            return ()
+        Game8 {..} -> mdo
+            putWord32 gso
+            putWord32 gs
+            putWord32 gse1
+            putWord32 gse2
+
+            gso <- getAddress $ putOidList gGameSelectOIDs
+            gs  <- getAddress $ putArray putWord16 $ map putWord16 gGameSelect
+            gse1 <- getAddress $ putPlayListList gGameSelectErrors1
+            gse2 <- getAddress $ putPlayListList gGameSelectErrors2
+
+            return ()
+        Game9 {..} -> mdo
+            mapM_ putWord32 epll
+            epll <- mapM (getAddress . putPlayListList) gExtraPlayLists
+            return ()
+        Game10 {..} -> mdo
+            mapM_ putWord32 epll
+            epll <- mapM (getAddress . putPlayListList) gExtraPlayLists
+            return ()
+        Game16 {..} -> mdo
+            putWord32 eoids
+            mapM_ putWord32 epll
+            eoids <- getAddress $ putOidList gExtraOIDs
+            epll <- mapM (getAddress . putPlayListList) gExtraPlayLists
+            return ()
+        _ -> return ()
+
+    spl   <- getAddress $ putPlayListList $ gStartPlayList g
+    repl  <- getAddress $ putPlayListList $ gRoundEndPlayList g
+    fpl   <- getAddress $ putPlayListList $ gFinishPlayList g
+    rspl  <- getAddress $ putPlayListList $ gRoundStartPlayList g
+    lrspl <- getAddress $ putPlayListList $ gLaterRoundStartPlayList g
+    fpll  <- mapM (getAddress . putPlayListList) (gFinishPlayLists g)
+
+    sgo <- mapM (getAddress . putSubGame) (gSubgames g)
+    return ()
+
+
+
+putPlayListList :: [PlayList] -> SPut
+putPlayListList playlistlist = do
+    putOffsets putWord16 $ map putPlayList playlistlist
+
+putGameIdList :: [GameId] -> SPut
+putGameIdList = putArray putWord16 . map (putWord16 . (+1))
+
+putOidList :: [GameId] -> SPut
+putOidList = putArray putWord16 . map putWord16
+
+putSubGame :: SubGame -> SPut
+putSubGame (SubGame {..}) = mdo
+    putBS sgUnknown
+    putArray putWord16 $ map putWord16 sgOids1
+    putArray putWord16 $ map putWord16 sgOids2
+    putArray putWord16 $ map putWord16 sgOids3
+    mapM_ putWord32 pll 
+    pll  <- mapM (getAddress . putPlayListList) sgPlaylist
+    return ()
+
 putScriptTable :: [(Word16, Maybe [Line ResReg])] -> SPut
 putScriptTable [] = error "Cannot create file with an empty script table"
 putScriptTable scripts = mdo
@@ -186,6 +314,14 @@
     putWord16 r
     mapM_ putWord8 [0xF8, 0xFF]
     putTVal (Const 0)
+putCommand RandomVariant = do
+    putWord16 0
+    mapM_ putWord8 [0xE0, 0xFF]
+    putTVal (Const 0)
+putCommand PlayAllVariant = do
+    putWord16 0
+    mapM_ putWord8 [0xE1, 0xFF]
+    putTVal (Const 0)
 putCommand (Play n) = do
     putWord16 0
     mapM_ putWord8 [0xE8, 0xFF]
@@ -194,6 +330,10 @@
     putWord16 0
     mapM_ putWord8 [0x00, 0xFC]
     putTVal (Const (lowhigh a b))
+putCommand (PlayAll a b) = do
+    putWord16 0
+    mapM_ putWord8 [0x00, 0xFB]
+    putTVal (Const (lowhigh a b))
 putCommand (Game n) = do
     putWord16 0
     mapM_ putWord8 [0x00, 0xFD]
@@ -205,6 +345,10 @@
 putCommand (Jump v) = do
     putWord16 0
     mapM_ putWord8 [0xFF, 0xF8]
+    putTVal v
+putCommand (Timer r v) = do
+    putWord16 r
+    mapM_ putWord8 [0x00, 0xFF]
     putTVal v
 putCommand (NamedJump s) = error "putCommand: Unresolved NamedJump"
 putCommand (Unknown b r v) = do
diff --git a/src/Lint.hs b/src/Lint.hs
--- a/src/Lint.hs
+++ b/src/Lint.hs
@@ -1,4 +1,4 @@
-module Lint (lintTipToi) where
+module Lint (lintTipToi, warnTipToi) where
 
 import Text.Printf
 import Data.Maybe
@@ -50,3 +50,13 @@
         = printf "   Offset %08X Size %d (%s) overlaps Offset %08X Size %d (%s) by %d\n"
             o1 l1 (ppDesc d1) o2 l2 (ppDesc d2) overlap
       where overlap = o1 + l1 - o2
+
+warnTipToi :: TipToiFile -> IO ()
+warnTipToi tt = do
+    sequence_
+      [ printf "[Script %d line %d] Warning: More than 8 commands per line may cause problems\n"
+            c n
+      | (c, Just lines) <- ttScripts tt
+      , (n,Line _ _ cmds _) <- zip [(1::Int)..] lines
+      , length cmds > 8
+      ]
diff --git a/src/OidCode.hs b/src/OidCode.hs
--- a/src/OidCode.hs
+++ b/src/OidCode.hs
@@ -1,17 +1,22 @@
-{-# LANGUAGE BangPatterns, TupleSections #-}
+{-# LANGUAGE BangPatterns, TupleSections, FlexibleContexts #-}
 
-module OidCode (genRawPNG, DPI(..), PixelSize(..)) where
+module OidCode (genRawPixels, genRawPNG, DPI(..), PixelSize(..)) where
 
 import Data.Word
 import Data.Bits
 import Data.Functor
+import qualified Data.ByteString.Lazy as B
+import Data.Monoid
 import Control.Monad
-import Control.Monad.Writer.Strict
 import Codec.Picture
 import Codec.Picture.Types
+import Codec.Picture.Metadata
 import Control.Monad.ST
 import Control.Applicative
+import Data.Vector.Storable.ByteString
 
+import Utils
+
 -- Image generation
 
 checksum :: Word16 -> Word16
@@ -27,173 +32,96 @@
     (&) = (.&.)
 
 
-{-
-oidSVG :: Int -> S.Svg
-oidSVG code | code >= 4^8 = error $ printf "Code %d too large to draw" code
-oidSVG code = S.docTypeSvg ! A.version (S.toValue "1.1")
-                           ! A.width (S.toValue "1mm")
-                           ! A.height (S.toValue "1mm")
-                           ! A.viewbox (S.toValue "0 0 48 48") $ do
-    S.defs pattern
-    S.rect ! A.width (S.toValue "48") ! A.height (S.toValue "48")
-           ! A.fill (S.toValue $ "url(#"++patid++")")
-  where
-    quart 8 = checksum code
-    quart n = (code `div` 4^n) `mod` 4
-    patid = "pat-" ++ show code
-
-    pattern = S.pattern ! A.width (S.toValue "48")
-                        ! A.height (S.toValue "48")
-                        ! A.id_ (S.toValue patid)
-                        ! A.patternunits (S.toValue "userSpaceOnUse") $ S.g (f (0,0))
-    f = mconcat $ map position $
-        zip (flip (,) <$> [3,2,1] <*> [3,2,1])
-            [ value (quart n) | n <- [0..8] ] ++
-        [ (p, plain) | p <- [(0,0), (1,0), (2,0), (3,0), (0,1), (0,3) ] ] ++
-        [ ((0,2), special) ]
-
-    -- pixel = S.rect ! A.width (S.toValue "2") ! A.height (S.toValue "2") ! pos (7,7)
-    pixel (x,y) = S.path ! A.d path
-      where path = mkPath $ do
-            S.m (x+5) (y+5)
-            S.hr 2
-            S.vr 2
-            S.hr (-2)
-            S.z
-
-    plain = pixel
-    value 0 = at (2,2)   plain
-    value 1 = at (-2,2)  plain
-    value 2 = at (-2,-2) plain
-    value 3 = at (2,-2)  plain
-    special = at (3,0)   plain
-
-    position ((n,m), p) = at (n*12, m*12) p
-
-    -- Drawing combinators
-    at (x, y) f = f . ((+x) *** (+y))
-
-genSVGs :: String -> IO ()
-genSVGs code_str = do
-    codes <- parseRange code_str
-    forM_ codes $ \c -> do
-        let filename = printf "oid%d.svg" c
-        printf "Writing %s...\n" filename
-        genSVG c filename
-
-genSVG :: Int -> FilePath -> IO ()
-genSVG code filename = B.writeFile filename (renderSvg (oidSVG code))
--}
-
-data DPI = D1200 | D600
-data PixelSize = SinglePixel | DoublePixel
+type DPI = Int
+type PixelSize = Int
 
-imageFromBlackPixels :: Int -> Int -> [(Int, Int)] -> Image PixelYA8
+imageFromBlackPixels :: ColorConvertible PixelYA8 p => Int -> Int -> [(Int, Int)] -> Image p
 imageFromBlackPixels width height pixels = runST $ do
     i <- createMutableImage width height background
     forM_ pixels $ \(x,y) -> do
         writePixel i x y black
     freezeImage i
   where
-    black =      PixelYA8 minBound maxBound
-    background = PixelYA8 maxBound minBound
+    black =      promotePixel $ PixelYA8 minBound maxBound
+    background = promotePixel $ PixelYA8 maxBound minBound
 
-oidImage :: DPI -> PixelSize -> Word16 -> Image PixelYA8
-oidImage dpi ps code =
-    imageFromBlackPixels
-        (width *4*dotsPerPoint)
-        (height*4*dotsPerPoint)
-        (tile f)
+-- | Renders a single OID Image, returns its dimensions and the black pixels therein
+singeOidImage :: DPI -> PixelSize -> Word16 -> ((Int, Int), [(Int, Int)])
+singeOidImage dpi ps code = ((width, height), pixels)
   where
-    width = 100 -- in mm
-    height = 100 -- in mm
-    !dotsPerPoint | D1200 <- dpi = 12
-                  |  D600 <- dpi =  6
+    spacePerPoint = dpi `div2` 100
+    width  = 4*spacePerPoint
+    height = 4*spacePerPoint
 
+    pixels = mconcat $ map position $
+        zip (flip (,) <$> [3,2,1] <*> [3,2,1])
+            [ value (quart n) | n <- [0..8] ] ++
+        [ (p, centeredDot) | p <- [(0,0), (1,0), (2,0), (3,0), (0,1), (0,3) ] ] ++
+        [ ((0,2), special) ]
 
+
     quart 8 = checksum code
     quart n = (code `div` 4^n) `mod` 4
 
-    f = mconcat $ map position $
-        zip (flip (,) <$> [3,2,1] <*> [3,2,1])
-            [ value (quart n) | n <- [0..8] ] ++
-        [ (p, plain) | p <- [(0,0), (1,0), (2,0), (3,0), (0,1), (0,3) ] ] ++
-        [ ((0,2), special) ]
 
-    plain | D1200 <- dpi, SinglePixel <- ps = coordsOfDots
-                [ "           "
-                , "           "
-                , "           "
-                , "           "
-                , "           "
-                , "    **     "
-                , "    **     "
-                , "           "
-                , "           "
-                , "           "
-                , "           "
-                , "           "
-                ]
-          | D1200 <- dpi, DoublePixel <- ps = coordsOfDots
-                [ "           "
-                , "           "
-                , "           "
-                , "           "
-                , "   ****    "
-                , "   ****    "
-                , "   ****    "
-                , "   ****    "
-                , "           "
-                , "           "
-                , "           "
-                , "           "
-                ]
-          | D600 <- dpi, SinglePixel <- ps  = coordsOfDots
-                [ "      "
-                , "      "
-                , "  *   "
-                , "      "
-                , "      "
-                , "      "
-                ]
-          | D600 <- dpi, DoublePixel <- ps  = coordsOfDots
-                [ "      "
-                , "      "
-                , "  **  "
-                , "  **  "
-                , "      "
-                , "      "
-                ]
+    dot = [(x,y) | x <- [1..ps], y <- [1..ps]]
 
+    centeredDot | xshift < 0 || yshift < 0 = error "Dots too large. Try a smaller pixel size"
+                | otherwise = at (xshift, yshift) dot
+      where xshift = (spacePerPoint - ps) `div` 2 - 1
+            yshift = (spacePerPoint - ps) `div` 2 - 1
 
-    s  | D1200 <- dpi = 2
-       | D600  <- dpi = 1
-    ss | D1200 <- dpi = 3
-       | D600  <- dpi = 2
-    value 0 = at ( s, s) plain
-    value 1 = at (-s, s) plain
-    value 2 = at (-s,-s) plain
-    value 3 = at ( s,-s) plain
-    special = at (ss,0)  plain
+    -- | how many pixels to shift dots horizontally
+    s  = dpi `div2` 600
+    -- | how many pixels to shift the special dot horizontally
+    ss = dpi `div2` 400
 
-    position ((n,m), p) = at (n*dotsPerPoint, m*dotsPerPoint) p
+    value 0 = at ( s, s) centeredDot
+    value 1 = at (-s, s) centeredDot
+    value 2 = at (-s,-s) centeredDot
+    value 3 = at ( s,-s) centeredDot
+    special = at (ss,0)  centeredDot
 
-    coordsOfDots :: [String] -> [(Int, Int)]
-    coordsOfDots rows =
-        [ (x,y)
-        | (row, y) <- zip rows [0..]
-        , (c, x)   <- zip row  [0..]
-        , c == '*'
-        ]
+    position ((n,m), p) = at (n*spacePerPoint, m*spacePerPoint) p
 
     -- Drawing combinators
-
     at (x, y) = map (\(x', y') -> (x + x', y + y'))
-    tile f = concat [ at (x*4*dotsPerPoint, y*4*dotsPerPoint) f
-                    | x <- [0..width-1], y <- [0..height-1]]
 
+    -- integer division rounded up
+    x `div2` y = ((x-1) `div` y) + 1
 
+oidImage :: ColorConvertible PixelYA8 p => Int -> Int -> DPI -> PixelSize -> Word16 -> Image p
+oidImage w h dpi ps code =
+    imageFromBlackPixels w h tiledPixels
+  where
+    ((cw,ch), pixels) = singeOidImage dpi ps code
 
-genRawPNG :: DPI -> PixelSize -> Word16 -> FilePath -> IO ()
-genRawPNG dpi ps code filename = writePng filename (oidImage dpi ps code)
+    tiledPixels =
+        [ (x',y')
+        | (x,y) <- pixels
+        , x' <- [x,x + cw..w-1]
+        , y' <- [y,y + ch..h-1]
+        ]
 
+-- Width and height in pixels
+genRawPixels :: Int -> Int -> DPI -> PixelSize -> Word16 -> B.ByteString
+genRawPixels w h dpi ps code =
+    -- All very shaky here, but it seems to work
+    B.fromStrict $
+    vectorToByteString $
+    imageData $
+    (oidImage w h dpi ps code :: Image PixelRGB8)
+
+
+genRawPNG :: Int -> Int -> DPI -> PixelSize -> Word16 -> FilePath -> IO ()
+genRawPNG w h dpi ps code filename =
+    B.writeFile filename $
+    encodePngWithMetadata metadata $
+    (oidImage w h dpi ps code :: Image PixelYA8)
+  where
+    metadata = mconcat
+        [ singleton DpiX (fromIntegral dpi)
+        , singleton DpiY (fromIntegral dpi)
+        , singleton Title $ "Tiptoi OID Code " ++ show code
+        , singleton Software $ "tttool " ++ tttoolVersion
+        ]
diff --git a/src/OidTable.hs b/src/OidTable.hs
new file mode 100644
--- /dev/null
+++ b/src/OidTable.hs
@@ -0,0 +1,156 @@
+module OidTable where
+
+import Data.Word
+import qualified Data.ByteString.Lazy as LB
+import Graphics.PDF
+import Control.Monad hiding (forM_)
+import Data.Foldable (forM_)
+import Data.List.Split
+import Text.Printf
+import Control.Arrow ((***))
+import Codec.Compression.Zlib
+
+import OidCode
+import KnownCodes
+import Utils
+import Types
+
+
+-- IO technically unnecessary: https://github.com/alpheccar/HPDF/issues/7
+
+oidTable :: Conf -> String -> [(String, Word16)] -> LB.ByteString
+oidTable conf title entries | entriesPerPage < 1 = error "OID codes too large to fit on a single page"
+                            | otherwise = pdfByteString docInfo a4rect $ do
+    -- Replace codes by images
+    entries' <- forM entries $ \(d,rc) ->
+        case code2RawCode rc of
+            Nothing -> return (d, Nothing)
+            Just c -> do
+                image <- createPDFRawImageFromByteString imageWidthPx imageHeightPx False FlateDecode $
+                    compressWith defaultCompressParams { compressLevel = defaultCompression } $
+                    genRawPixels imageWidthPx imageHeightPx (cDPI conf) (cPixelSize conf) $
+                    c
+                return (d, Just image)
+
+    let chunks = chunksOf entriesPerPage entries'
+    let totalPages = length chunks
+
+    forM_ (zip [1::Int ..] chunks) $ \(pageNum, thisPage) -> do
+        page <- addPage Nothing
+
+        drawWithPage page $ do
+            displayFormattedText titleRect NormalParagraph titleFont $ do
+                setJustification Centered
+                paragraph $ txt title
+
+            displayFormattedText footerRect NormalParagraph footerFont $ do
+                setJustification LeftJustification
+                paragraph $ txt $ "Created by tttool-" ++ tttoolVersion
+
+            displayFormattedText footerRect NormalParagraph footerFont $ do
+                setJustification RightJustification
+                paragraph $ txt $ printf "%d/%d" pageNum totalPages
+
+            forM_ (zip thisPage positions) $ \((e,mbi),p) -> do
+                withNewContext $ do
+                    applyMatrix $ translate  p
+                    forM_ mbi $ \i -> withNewContext $ do
+                        applyMatrix $ translate  (0 :+ (-imageHeight))
+                        applyMatrix $ scale (1/px) (1/px)
+                        drawXObject i
+                    withNewContext $ do
+                        applyMatrix $ translate  (0 :+ (-imageHeight - subtitleSep))
+                        let fontRect = Rectangle (0 :+ (-subtitleHeight)) (imageWidth :+ 0)
+                        addShape fontRect
+                        setAsClipPath
+                        displayFormattedText fontRect NormalParagraph bodyFont $ do
+                            paragraph $ txt e
+  where
+    docInfo = standardDocInfo
+        { author=toPDFString $ "tttool-" ++ tttoolVersion
+        , compressed = False
+        }
+
+    -- Configure-dependent dimensions (all in pt)
+    (imageWidth,imageHeight) = (*mm) *** (*mm) $ fromIntegral *** fromIntegral $cCodeDim conf
+
+    -- Static dimensions (all in pt)
+
+    -- Page paddings
+    padTop, padLeft, padBot, padRight :: Double
+    padTop   = 1*cm
+    padBot   = 1*cm
+    padLeft  = 2*cm
+    padRight = 2*cm
+
+    titleHeight  = 1*cm
+    titleSep     = 0.5*cm
+    footerHeight = 0.5*cm
+    footerSep    = 0.5*cm
+
+    imageSepH = 0.4*cm
+    imageSepV = 0.2*cm
+
+    subtitleHeight = 0.4*cm
+    subtitleSep    = 0.2*cm
+
+    -- Derived dimensions (all in pt)
+    titleRect = Rectangle
+        (padLeft          :+ (a4h - padTop - titleHeight))
+        ((a4w - padRight) :+ (a4h - padTop))
+    titleFont = Font (PDFFont Helvetica 12) black black
+
+    footerRect = Rectangle
+        (padLeft          :+ padBot)
+        ((a4w - padRight) :+ (padBot + footerHeight))
+    footerFont = Font (PDFFont Helvetica 8) black black
+
+    bodyFont = Font (PDFFont Helvetica 8) black black
+
+    bodyWidth  = a4w - padLeft - padRight
+    bodyHeight = a4h - padTop - titleHeight - titleSep - footerSep - footerHeight - padBot
+
+    positions = map (+(padLeft :+ (padBot + footerHeight + footerSep))) $
+        calcPositions bodyWidth  bodyHeight
+                      imageWidth (imageHeight + subtitleSep + subtitleHeight)
+                      imageSepH  imageSepV
+    entriesPerPage = length positions
+
+
+    -- Derived dimensions (all in pixels)
+    imageWidthPx = floor (imageWidth * px)
+    imageHeightPx = floor (imageHeight * px)
+
+    -- config-dependent conversion factors
+    px :: Double
+    px = fromIntegral (cDPI conf) / 72
+
+
+calcPositions
+    :: Double -- ^ total width
+    -> Double -- ^ total height
+    -> Double -- ^ entry width
+    -> Double -- ^ entry height
+    -> Double -- ^ pad width
+    -> Double -- ^ pad height
+    -> [Point]
+calcPositions tw th ew eh pw ph = [ x :+ (th - y) | y <- ys , x <- xs]
+  where
+    xs = [0,ew+pw..tw-ew]
+    ys = [0,eh+ph..th-eh]
+
+-- Conversation factor
+cm :: Double
+cm = 28.3465
+
+mm :: Double
+mm = 2.83465
+
+-- A4 dimensions
+a4w, a4h :: Double
+a4w = 595
+a4h = 842
+
+a4rect :: PDFRect
+a4rect = PDFRect 0 0 595 842
+
diff --git a/src/OneLineParser.hs b/src/OneLineParser.hs
--- a/src/OneLineParser.hs
+++ b/src/OneLineParser.hs
@@ -1,4 +1,4 @@
-module OneLineParser (parseOneLine) where
+module OneLineParser (parseOneLine, parseOneLinePure) where
 
 import System.Exit
 import Text.Parsec hiding (Line, lookAhead, spaces)
@@ -9,10 +9,6 @@
 -- Parser utilities
 -- | A nicer way to print an error message
 
-printLineParserErrorMessage :: String -> ParseError -> IO a
-printLineParserErrorMessage input err = do
-    putStrLn (lineParserErrorMessage input err)
-    exitFailure
 
 lineParserErrorMessage :: String -> ParseError -> String
 lineParserErrorMessage input err =
@@ -26,6 +22,12 @@
 
 parseOneLine :: Parser a -> String -> String -> IO a
 parseOneLine p name input =
+    case parseOneLinePure p name input of
+        Left e ->  putStrLn e >> exitFailure
+        Right l -> return l
+
+parseOneLinePure :: Parser a -> String -> String -> Either String a
+parseOneLinePure p name input =
     case P.parse p name input of
-        Left e ->  printLineParserErrorMessage input e
+        Left e ->  Left $ lineParserErrorMessage input e
         Right l -> return l
diff --git a/src/PrettyPrint.hs b/src/PrettyPrint.hs
--- a/src/PrettyPrint.hs
+++ b/src/PrettyPrint.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE TypeSynonymInstances, RecordWildCards #-}
 module PrettyPrint where
 
 import qualified Data.ByteString.Lazy as B
@@ -32,7 +32,7 @@
     map ppConditional cs ++ map (ppCommand True M.empty xs) as
 
 -- Group consecutive runs of numbers, if they do not have a description
-groupRuns :: (Eq a, Enum a) => (a -> Maybe b) -> [a] -> [Either b [a]]
+groupRuns :: (Eq a, Bounded a, Enum a) => (a -> Maybe b) -> [a] -> [Either b [a]]
 groupRuns l = go
   where
     go []  = []
@@ -41,8 +41,10 @@
     go (x:xs) = case l x of
         Just l -> Left l : go xs
         Nothing -> case go xs of
-            Right (y:ys):r' | succ x == y -> Right (x:y:ys) : r'
-            r                             -> Right [x] : r
+            Right (y:ys):r'
+              | x /= maxBound
+              , succ x == y    -> Right (x:y:ys) : r'
+            r                  -> Right [x] : r
 
 ppPlayList :: Transscript -> PlayList -> String
 ppPlayList t xs = "[" ++ commas (map go (groupRuns (flip M.lookup t) xs)) ++ "]"
@@ -103,19 +105,29 @@
 ppArithOp Set  = ":="
 
 ppCommand :: Reg r => Bool -> Transscript -> PlayList -> Command r -> String
-ppCommand True t xs (Play n)     | not (validIndex xs (fromIntegral n)) = ""
-ppCommand True t xs (Random a b) | any (not . validIndex xs . fromIntegral) [b..a] = ""
+ppCommand True t xs p
+    | any (not . validIndex xs) (indices p) = ""
 
-ppCommand _ t xs (Play n)        = printf "P(%s)" (ppPlayIndex t xs (fromIntegral n))
-ppCommand _ t xs (Random a b)    = printf "P(%s)" $ commas $ map (ppPlayIndex t xs . fromIntegral ) [b..a]
-ppCommand _ t xs (Cancel)        = printf "C"
+ppCommand _ t xs (Play n)        = printf "P(%s)" $ ppPlayIndex t xs (fromIntegral n)
+ppCommand _ t xs (Random a b)    = printf "P(%s)" $ ppPlayRange t xs [b..a]
+ppCommand _ t xs (PlayAll a b)   = printf "PA(%s)" $ ppPlayRange t xs [b..a]
+ppCommand _ t xs PlayAllVariant  = printf "PA*(%s)" $ ppPlayAll t xs
+ppCommand _ t xs RandomVariant   = printf "P*(%s)" $ ppPlayAll t xs
+ppCommand _ t xs Cancel          = printf "C"
 ppCommand _ t xs (Jump v)        = printf "J(%s)" (ppTVal v)
+ppCommand _ t xs (Timer r v)     = printf "T(%s,%s)" (ppReg r) (ppTVal v)
 ppCommand _ t xs (NamedJump v)   = printf "J(%s)" v
 ppCommand _ t xs (Game b)        = printf "G(%d)" b
 ppCommand _ t xs (ArithOp o r n) = ppReg r ++ ppArithOp o ++ ppTVal n
 ppCommand _ t xs (Neg r)       = printf "Neg(%s)" (ppReg r)
 ppCommand _ t xs (Unknown b r n) = printf "?(%s,%s) (%s)" (ppReg r) (ppTVal n) (prettyHex b)
 
+indices :: Command r -> [Int]
+indices (Play n)      = [fromIntegral n]
+indices (Random a b)  = map fromIntegral [b..a]
+indices (PlayAll a b) = map fromIntegral [b..a]
+indices _ = []
+
 validIndex :: PlayList -> Int -> Bool
 validIndex xs n = n >= 0 && n < length xs
 
@@ -123,89 +135,159 @@
 ppPlayIndex t xs n | validIndex xs n = transcribe t (xs !! n)
                    | otherwise       = "invalid_index_" ++ show n
 
+ppPlayRange :: Transscript -> PlayList -> [Word8] -> String
+ppPlayRange t xs = commas . map (ppPlayIndex t xs . fromIntegral)
+
+ppPlayAll :: Transscript -> PlayList -> String
+ppPlayAll t = commas . map (transcribe t)
+
 spaces = intercalate " "
 commas = intercalate ","
 quote s = printf "'%s'" s
 
-ppGame :: Transscript -> Game -> String
-ppGame t (Game6 u1 u2 plls sg1s sg2s u3 pll2s pl) =
-    printf (unlines ["  type: 6", "  u1:   %d", "  u2:   %s",
-                     "  playlistlists: (%d)", "%s",
-                     "  subgames1: (%d)", "%s",
-                     "  subgames2: (%d)", "%s",
-                     "  u3: %s",
-                     "  playlistlists: (%d)","%s",
-                     "  playlist: %s"])
-    u1 (prettyHex u2)
-    (length plls)   (indent 4 (map (ppPlayListList t) plls))
-    (length sg1s)   (concatMap (ppSubGame t) sg1s)
-    (length sg2s)   (concatMap (ppSubGame t) sg2s)
-    (prettyHex u3)
-    (length pll2s)  (indent 4 (map (ppPlayListList t) pll2s))
-    (show pl)
-ppGame t (Game7 u1 c u2 plls sgs u3 pll2s pll) =
-    printf (unlines ["  type: 6", "  u1:   %d", "  u2:   %s",
-                     "  playlistlists: (%d)", "%s",
-                     "  subgames: (%d)", "%s",
-                     "  u3: %s",
-                     "  playlistlists: (%d)","%s",
-                     "  playlistlist: %s"])
-    u1 (prettyHex u2)
-    (length plls)   (indent 4 (map (ppPlayListList t) plls))
-    (length sgs)    (concatMap (ppSubGame t) sgs)
-    (prettyHex u3)
-    (length pll2s)  (indent 4 (map (ppPlayListList t) pll2s))
-    (ppPlayListList t pll)
-ppGame t (Game8 u1 c u2 plls sgs u3 pll2s oidl gidl pll1 pll2) =
-    printf (unlines ["  type: 6", "  u1:   %d", "  u2:   %s",
-                     "  playlistlists: (%d)", "%s",
+ppCommonGame :: Transscript -> Game -> String
+ppCommonGame t g =
+    printf (unlines ["  type: %d",
+                     "  rounds: %d",
+                     "  unknown (c): %d",
+                     "  early rounds: %d",
+                     "  repeat last media OID: %d",
+                     "  unknown (x): %d",
+                     "  unknown (w): %d",
+                     "  unknown (v): %d",
+                     "  start play list:               %s",
+                     "  round end play list:           %s",
+                     "  finish play list:              %s",
+                     "  round start play list:         %s",
+                     "  later round start play list:   %s",
                      "  subgames: (%d)", "%s",
-                     "  u3: %s",
-                     "  playlistlists: (%d)","%s",
-                     "  oids: %s",
-                     "  gids: %s",
-                     "  playlistlist: %s",
-                     "  playlistlist: %s"
+                     "  target scores: (%d) %s",
+                     "  finish play lists: (%d)", "%s"
                      ])
-    u1 (prettyHex u2)
-    (length plls)   (indent 4 (map (ppPlayListList t) plls))
-    (length sgs)    (concatMap (ppSubGame t) sgs)
-    (prettyHex u3)
-    (length pll2s)  (indent 4 (map (ppPlayListList t) pll2s))
-    (ppOidList oidl) (show gidl)
-    (ppPlayListList t pll1) (ppPlayListList t pll2)
-ppGame t (UnknownGame typ u1 c u2 plls sgs u3 pll2s) =
-    printf (unlines ["  type: %d",
-                     "  u1:   %d",
-                     "  c:    %d",
-                     "  u2:   %s",
-                     "  playlistlists: (%d)", "%s",
+    (gameType g)
+    (gRounds g)
+    (gUnknownC g)
+    (gEarlyRounds g)
+    (gRepeatLastMedia g)
+    (gUnknownX g) (gUnknownW g) (gUnknownV g)
+    (ppPlayListList t (gStartPlayList g))
+    (ppPlayListList t (gRoundEndPlayList g))
+    (ppPlayListList t (gFinishPlayList g))
+    (ppPlayListList t (gRoundStartPlayList g))
+    (ppPlayListList t (gLaterRoundStartPlayList g))
+    (length (gSubgames g))       (ppSubGames t (gSubgames g))
+    (length (gTargetScores g))   (show (gTargetScores g))
+    (length (gFinishPlayLists g))(indent 4 (map (ppPlayListList t) (gFinishPlayLists g)))
+
+ppGame :: Transscript -> Game -> String
+ppGame t g@(CommonGame {..}) =
+    ppCommonGame t g
+
+ppGame t (Game6 {..}) =
+    printf (unlines ["  type: 6",
+                     "  rounds: %d",
+                     "  bonus rounds: %d",
+                     "  rounds target: %d",
+                     "  unknown (i): %d",
+                     "  early rounds: %d",
+                     "  unknown (q): %d",
+                     "  repeat last media OID: %d",
+                     "  unknown (x): %d",
+                     "  unknown (w): %d",
+                     "  unknown (v): %d",
+                     "  start play list:               %s",
+                     "  round end play list:           %s",
+                     "  finish play list:              %s",
+                     "  round start play list:         %s",
+                     "  later round start play list:   %s",
+                     "  round start play list 2:       %s",
+                     "  later round start play list 2: %s",
+                     "  bonus subgame count: %d",
                      "  subgames: (%d)", "%s",
-                     "  u3: %s",
-                     "  playlistlists: (%d)","%s"])
-    typ u1 c (prettyHex u2)
-    (length plls)   (indent 4 (map (ppPlayListList t) plls))
-    (length sgs)    (concatMap (ppSubGame t) sgs)
-    (prettyHex u3)
-    (length pll2s)  (indent 4 (map (ppPlayListList t) pll2s))
+                     "  target scores: (%d) %s",
+                     "  bonus target scores: (%d) %s",
+                     "  finish play lists: (%d)", "%s",
+                     "  bonus finish play lists: (%d)", "%s",
+                     "  bonus subgame ids: %s"
+                     ])
+    gRounds
+    gBonusRounds
+    gBonusTarget
+    gUnknownI
+    gEarlyRounds
+    gUnknownQ
+    gRepeatLastMedia
+    gUnknownX gUnknownW gUnknownV
+    (ppPlayListList t gStartPlayList)
+    (ppPlayListList t gRoundEndPlayList)
+    (ppPlayListList t gFinishPlayList)
+    (ppPlayListList t gRoundStartPlayList)
+    (ppPlayListList t gLaterRoundStartPlayList)
+    (ppPlayListList t gRoundStartPlayList2)
+    (ppPlayListList t gLaterRoundStartPlayList2)
+    gBonusSubgameCount
+    (length gSubgames)           (ppSubGames t gSubgames)
+    (length gTargetScores)       (show gTargetScores)
+    (length gBonusTargetScores)  (show gBonusTargetScores)
+    (length gFinishPlayLists)    (indent 4 (map (ppPlayListList t) gFinishPlayLists))
+    (length gBonusFinishPlayLists)    (indent 4 (map (ppPlayListList t) gBonusFinishPlayLists))
+    (show gBonusSubgameIds)
+
+ppGame t g@(Game7 {..}) = (ppCommonGame t g ++) $
+    printf (unlines [ "  subgame groups: %s"
+                     ])
+        (show gSubgameGroups)
+
+ppGame t g@(Game8 {..}) = (ppCommonGame t g ++) $
+    printf (unlines ["  game select OIDs:     %s",
+                     "  game select games:    %s",
+                     "  game select errors 1: %s",
+                     "  game select errors 2: %s"
+                     ])
+        (show gGameSelectOIDs)
+        (show gGameSelect)
+        (ppPlayListList t gGameSelectErrors1)
+        (ppPlayListList t gGameSelectErrors2)
+
+ppGame t g@(Game9 {..}) = (ppCommonGame t g ++) $
+    printf (unlines ["  extra play lists (%d):","%s"
+                     ])
+        (length gExtraPlayLists)  (indent 4 (map (ppPlayListList t) gExtraPlayLists))
+
+ppGame t g@(Game10 {..}) = (ppCommonGame t g ++) $
+    printf (unlines ["  extra play lists (%d):","%s"
+                     ])
+        (length gExtraPlayLists)  (indent 4 (map (ppPlayListList t) gExtraPlayLists))
+
+ppGame t g@(Game16 {..}) = (ppCommonGame t g ++) $
+    printf (unlines ["  extra OIDs (%d): %s",
+                     "  extra play lists (%d):","%s"
+                     ])
+        (length gExtraOIDs)       (show gExtraOIDs)
+        (length gExtraPlayLists)  (indent 4 (map (ppPlayListList t) gExtraPlayLists))
+
 ppGame t Game253 =
     printf (unlines ["  type: 253"
                      ])
-ppGame t _ = "TODO"
 
-ppSubGame :: Transscript -> SubGame -> String
-ppSubGame t (SubGame u oids1 oids2 oids3 plls) = printf (unlines
-    [ "    Subgame:"
+ppSubGames :: Transscript -> [SubGame] -> String
+ppSubGames t = concatMap (uncurry (ppSubGame t)) . zip [0..]
+
+ppSubGame :: Transscript -> Int -> SubGame -> String
+ppSubGame t n (SubGame u oids1 oids2 oids3 plls) = printf (unlines
+    [ "    Subgame %d:"
     , "      u: %s"
     , "      oids1: %s"
     , "      oids2: %s"
     , "      oids3: %s"
-    , "      playlistlists: (%d)" , "%s"
+    , "      playlist: (%d)" , "%s"
     ])
+    n
     (prettyHex u)
     (ppOidList oids1) (ppOidList oids2) (ppOidList oids3)
     (length plls)  (indent 8 (map (ppPlayListList t) plls))
 
+indent :: Int -> [String] -> String
 indent n = intercalate "\n" . map (replicate n ' ' ++)
 
 checkLine :: Int -> Line ResReg -> [String]
diff --git a/src/RangeParser.hs b/src/RangeParser.hs
--- a/src/RangeParser.hs
+++ b/src/RangeParser.hs
@@ -10,8 +10,8 @@
 import Types
 import OneLineParser
 
-parseRange :: String -> IO [Word16]
-parseRange = parseOneLine rangeParser "command line"
+parseRange :: String -> Either String [Word16]
+parseRange = parseOneLinePure rangeParser "command line"
 
 rangeParser :: Parser [Word16]
 rangeParser = concat <$> oneRangeParser `sepBy1` many1 (P.char ' ' <|> P.char ',')
diff --git a/src/TipToiYaml.hs b/src/TipToiYaml.hs
--- a/src/TipToiYaml.hs
+++ b/src/TipToiYaml.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE RecordWildCards, DeriveGeneric, CPP, TupleSections #-}
+{-# LANGUAGE RecordWildCards, DeriveGeneric, CPP, TupleSections, TemplateHaskell #-}
 
 module TipToiYaml
     ( tt2ttYaml, ttYaml2tt
@@ -17,7 +17,6 @@
 import Data.Char
 import Data.Either
 import Data.Functor
-import Data.Version
 import Data.Maybe
 import Control.Monad
 import System.Directory
@@ -33,6 +32,7 @@
 import Data.Time (getCurrentTime, formatTime)
 import Data.Yaml hiding ((.=), Parser)
 import Data.Aeson.Types hiding ((.=), Parser)
+import Data.Aeson.TH
 import Text.Parsec hiding (Line, lookAhead, spaces)
 import Text.Parsec.String
 import qualified Text.Parsec as P
@@ -41,8 +41,9 @@
 import GHC.Generics
 import qualified Data.Foldable as F
 import qualified Data.Traversable as T
+import Data.Traversable (for, traverse)
 import Control.Arrow
-import Control.Applicative ((<*>), (<*))
+import Control.Applicative (Applicative(..), (<*>), (<*))
 
 import TextToSpeech
 import Language
@@ -52,8 +53,7 @@
 import PrettyPrint
 import OneLineParser
 import Utils
-
-import Paths_tttool
+import TipToiYamlAux
 
 data TipToiYAML = TipToiYAML
     { ttyScripts :: M.Map String [String]
@@ -65,6 +65,7 @@
     , ttyScriptCodes :: Maybe CodeMap
     , ttySpeak :: Maybe SpeakSpecs
     , ttyLanguage :: Maybe Language
+    , ttyGames :: Maybe [GameYaml]
     }
     deriving Generic
 
@@ -82,11 +83,11 @@
     parseJSON v = do
         m <- parseJSON v
         l <- T.traverse parseJSON  $ M.lookup "language" m
-        m' <- T.traverse parseJSON $ M.delete "language" m 
+        m' <- T.traverse parseJSON $ M.delete "language" m
         return $ SpeakSpec l m'
 
 instance ToJSON SpeakSpec where
-    toJSON (SpeakSpec (Just l) m) = toJSON $ M.insert "language" (ppLang l) m 
+    toJSON (SpeakSpec (Just l) m) = toJSON $ M.insert "language" (ppLang l) m
     toJSON (SpeakSpec Nothing m)  = toJSON $ m
 
 toSpeakMap :: Language -> Maybe SpeakSpecs -> M.Map String (Language, String)
@@ -96,10 +97,10 @@
     go (SpeakSpec ml m) = M.map ((l',)) m
       where l' = fromMaybe l ml
     e = error "Conflicting definitions in section \"speak\""
-          
 
-newtype SpeakSpecs = SpeakSpecs [SpeakSpec] 
 
+newtype SpeakSpecs = SpeakSpecs [SpeakSpec]
+
 instance FromJSON SpeakSpecs where
     parseJSON (Array a) = SpeakSpecs <$> mapM parseJSON  (V.toList a)
     parseJSON v = SpeakSpecs . (:[]) <$> parseJSON v
@@ -108,23 +109,179 @@
     toJSON (SpeakSpecs [x]) = toJSON x
     toJSON (SpeakSpecs l)   = Array $ V.fromList $ map toJSON $ l
 
-options = defaultOptions { fieldLabelModifier = map fix . map toLower . drop 3 }
+type PlayListListYaml = String
+
+type OIDListYaml = String
+
+data GameYaml = CommonGameYaml
+        { gyGameType                 :: Word16
+        , gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        }
+    | Game6Yaml
+        { gyRounds                   :: Word16
+        , gyBonusSubgameCount        :: Word16
+        , gyBonusRounds              :: Word16
+        , gyBonusTarget              :: Word16
+        , gyUnknownI                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyUnknownQ                 :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gyRoundStartPlayList2      :: PlayListListYaml
+        , gyLaterRoundStartPlayList2 :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyBonusTargetScores        :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gyBonusFinishPlayLists     :: [PlayListListYaml]
+        , gyBonusSubgameIds          :: [Word16]
+        }
+    | Game7Yaml
+        { gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gySubgameGroups            :: [[GameId]]
+        }
+    | Game8Yaml
+        { gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gyGameSelectOIDs           :: OIDListYaml
+        , gyGameSelect               :: [Word16]
+        , gyGameSelectErrors1        :: PlayListListYaml
+        , gyGameSelectErrors2        :: PlayListListYaml
+        }
+    | Game9Yaml
+        { gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gyExtraPlayLists           :: [PlayListListYaml]
+        }
+    | Game10Yaml
+        { gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gyExtraPlayLists           :: [PlayListListYaml]
+        }
+    | Game16Yaml
+        { gyRounds                   :: Word16
+        , gyUnknownC                 :: Word16
+        , gyEarlyRounds              :: Word16
+        , gyRepeatLastMedia          :: Word16
+        , gyUnknownX                 :: Word16
+        , gyUnknownW                 :: Word16
+        , gyUnknownV                 :: Word16
+        , gyStartPlayList            :: PlayListListYaml
+        , gyRoundEndPlayList         :: PlayListListYaml
+        , gyFinishPlayList           :: PlayListListYaml
+        , gyRoundStartPlayList       :: PlayListListYaml
+        , gyLaterRoundStartPlayList  :: PlayListListYaml
+        , gySubgames                 :: [SubGameYaml]
+        , gyTargetScores             :: [Word16]
+        , gyFinishPlayLists          :: [PlayListListYaml]
+        , gyExtraOIDs                :: OIDListYaml
+        , gyExtraPlayLists           :: [PlayListListYaml]
+        }
+    | Game253Yaml
+
+data SubGameYaml = SubGameYaml
+    { sgUnknown :: String
+    , sgOids1 :: OIDListYaml
+    , sgOids2 :: OIDListYaml
+    , sgOids3 :: OIDListYaml
+    , sgPlaylist :: [PlayListListYaml]
+    }
+
+
+$(deriveJSON gameYamlOptions ''GameYaml)
+$(deriveJSON gameYamlOptions ''SubGameYaml)
+
+tipToiYamlOptions = defaultOptions { fieldLabelModifier = map fix . map toLower . drop 3 }
        where fix '_' = '-'
              fix c   = c
 
 instance FromJSON TipToiYAML where
-    parseJSON = genericParseJSON $ options
+    parseJSON = genericParseJSON tipToiYamlOptions
 instance ToJSON TipToiYAML where
-    toJSON = genericToJSON options
+    toJSON = genericToJSON tipToiYamlOptions
 #if MIN_VERSION_aeson(0,10,0)
-    toEncoding = genericToEncoding options
+    toEncoding = genericToEncoding tipToiYamlOptions
 #endif
 instance FromJSON TipToiCodesYAML where
-    parseJSON = genericParseJSON $ options
+    parseJSON = genericParseJSON tipToiYamlOptions
 instance ToJSON TipToiCodesYAML where
-    toJSON = genericToJSON options
+    toJSON = genericToJSON tipToiYamlOptions
 #if MIN_VERSION_aeson(0,10,0)
-    toEncoding = genericToEncoding options
+    toEncoding = genericToEncoding tipToiYamlOptions
 #endif
 
 
@@ -133,7 +290,7 @@
     { ttyProduct_Id = ttProductId
     , ttyInit = Just $ spaces $ [ ppCommand True M.empty [] (ArithOp Set (RegPos r) (Const n))
                                 | (r,n) <- zip [0..] ttInitialRegs , n /= 0]
-    , ttyWelcome = Just $ commas $ map show $ concat ttWelcome
+    , ttyWelcome = Just $ playListList2Yaml ttWelcome
     , ttyComment = Just $ BC.unpack ttComment
     , ttyScripts = M.fromList
         [ (show oid, map exportLine ls) | (oid, Just ls) <- ttScripts]
@@ -141,9 +298,325 @@
     , ttyScriptCodes = Nothing
     , ttySpeak = Nothing
     , ttyLanguage = Nothing
+    , ttyGames = list2Maybe $ map game2gameYaml ttGames
     }
 
+list2Maybe [] = Nothing
+list2Maybe xs = Just xs
 
+playListList2Yaml :: PlayListList -> PlayListListYaml
+playListList2Yaml = commas . map show . concat
+
+oidList2Yaml :: [OID] -> OIDListYaml
+oidList2Yaml = unwords . map show
+
+subGame2Yaml :: SubGame -> SubGameYaml
+subGame2Yaml (SubGame u o1 o2 o3 pl) = SubGameYaml
+    { sgUnknown = prettyHex u
+    , sgOids1 = oidList2Yaml o1
+    , sgOids2 = oidList2Yaml o2
+    , sgOids3 = oidList2Yaml o3
+    , sgPlaylist = map playListList2Yaml pl
+    }
+
+game2gameYaml :: Game -> GameYaml
+game2gameYaml CommonGame {..} = CommonGameYaml
+        { gyGameType                 = gGameType
+        , gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        }
+game2gameYaml Game6 {..} = Game6Yaml
+        { gyRounds                   = gRounds
+        , gyBonusSubgameCount        = gBonusSubgameCount
+        , gyBonusRounds              = gBonusRounds
+        , gyBonusTarget              = gBonusTarget
+        , gyUnknownI                 = gUnknownI
+        , gyEarlyRounds              = gEarlyRounds
+        , gyUnknownQ                 = gUnknownQ
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gyRoundStartPlayList2      = playListList2Yaml gRoundStartPlayList2
+        , gyLaterRoundStartPlayList2 = playListList2Yaml gLaterRoundStartPlayList2
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyBonusTargetScores        = gBonusTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gyBonusFinishPlayLists     = map playListList2Yaml gBonusFinishPlayLists
+        , gyBonusSubgameIds          = gBonusSubgameIds
+        }
+game2gameYaml Game7 {..} = Game7Yaml
+        { gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gySubgameGroups            = gSubgameGroups
+        }
+game2gameYaml Game8 {..} = Game8Yaml
+        { gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gyGameSelectOIDs           = oidList2Yaml gGameSelectOIDs
+        , gyGameSelect               = gGameSelect
+        , gyGameSelectErrors1        = playListList2Yaml gGameSelectErrors1
+        , gyGameSelectErrors2        = playListList2Yaml gGameSelectErrors2
+        }
+game2gameYaml Game9 {..} = Game9Yaml
+        { gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gyExtraPlayLists           = map playListList2Yaml gExtraPlayLists
+        }
+game2gameYaml Game10 {..} = Game10Yaml
+        { gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gyExtraPlayLists           = map playListList2Yaml gExtraPlayLists
+        }
+game2gameYaml Game16 {..} = Game16Yaml
+        { gyRounds                   = gRounds
+        , gyUnknownC                 = gUnknownC
+        , gyEarlyRounds              = gEarlyRounds
+        , gyRepeatLastMedia          = gRepeatLastMedia
+        , gyUnknownX                 = gUnknownX
+        , gyUnknownW                 = gUnknownW
+        , gyUnknownV                 = gUnknownV
+        , gyStartPlayList            = playListList2Yaml gStartPlayList
+        , gyRoundEndPlayList         = playListList2Yaml gRoundEndPlayList
+        , gyFinishPlayList           = playListList2Yaml gFinishPlayList
+        , gyRoundStartPlayList       = playListList2Yaml gRoundStartPlayList
+        , gyLaterRoundStartPlayList  = playListList2Yaml gLaterRoundStartPlayList
+        , gySubgames                 = map subGame2Yaml gSubgames
+        , gyTargetScores             = gTargetScores
+        , gyFinishPlayLists          = map playListList2Yaml gFinishPlayLists
+        , gyExtraOIDs                = oidList2Yaml gExtraOIDs
+        , gyExtraPlayLists           = map playListList2Yaml gExtraPlayLists
+        }
+game2gameYaml Game253 = Game253Yaml
+
+playListListFromYaml :: PlayListListYaml -> WithFileNames PlayListList
+playListListFromYaml =
+    fmap listify .
+    traverse recordFilename .
+    either error id .
+    parseOneLinePure parsePlayList "playlist"
+  where listify [] = []
+        listify x  = [x]
+
+oidListFromYaml :: OIDListYaml -> [OID]
+oidListFromYaml = map read . words
+
+subGameFromYaml :: SubGameYaml -> WithFileNames SubGame
+subGameFromYaml (SubGameYaml u o1 o2 o3 pl) = (\x -> SubGame
+    { sgUnknown = either error id $ parseOneLinePure parsePrettyHex "unknown" u
+    , sgOids1 = oidListFromYaml o1
+    , sgOids2 = oidListFromYaml o2
+    , sgOids3 = oidListFromYaml o3
+    , sgPlaylist = x
+    }) <$> traverse playListListFromYaml pl
+
+
+gameYaml2Game :: GameYaml -> WithFileNames Game
+gameYaml2Game CommonGameYaml {..} = pure CommonGame
+        <*> pure gyGameType
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+gameYaml2Game Game6Yaml {..} = pure Game6
+        <*> pure gyRounds
+        <*> pure gyBonusSubgameCount
+        <*> pure gyBonusRounds
+        <*> pure gyBonusTarget
+        <*> pure gyUnknownI
+        <*> pure gyEarlyRounds
+        <*> pure gyUnknownQ
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> playListListFromYaml gyRoundStartPlayList2
+        <*> playListListFromYaml gyLaterRoundStartPlayList2
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> pure gyBonusTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> traverse playListListFromYaml gyBonusFinishPlayLists
+        <*> pure gyBonusSubgameIds
+gameYaml2Game Game7Yaml {..} = pure Game7
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> pure gySubgameGroups
+gameYaml2Game Game8Yaml {..} = pure Game8
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> pure (oidListFromYaml gyGameSelectOIDs)
+        <*> pure gyGameSelect
+        <*> playListListFromYaml gyGameSelectErrors1
+        <*> playListListFromYaml gyGameSelectErrors2
+gameYaml2Game Game9Yaml {..} = pure Game9
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> traverse playListListFromYaml gyExtraPlayLists
+gameYaml2Game Game10Yaml {..} = pure Game10
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> traverse playListListFromYaml gyExtraPlayLists
+gameYaml2Game Game16Yaml {..} = pure Game16
+        <*> pure gyRounds
+        <*> pure gyUnknownC
+        <*> pure gyEarlyRounds
+        <*> pure gyRepeatLastMedia
+        <*> pure gyUnknownX
+        <*> pure gyUnknownW
+        <*> pure gyUnknownV
+        <*> playListListFromYaml gyStartPlayList
+        <*> playListListFromYaml gyRoundEndPlayList
+        <*> playListListFromYaml gyFinishPlayList
+        <*> playListListFromYaml gyRoundStartPlayList
+        <*> playListListFromYaml gyLaterRoundStartPlayList
+        <*> traverse subGameFromYaml gySubgames
+        <*> pure gyTargetScores
+        <*> traverse playListListFromYaml gyFinishPlayLists
+        <*> pure (oidListFromYaml gyExtraOIDs)
+        <*> traverse playListListFromYaml gyExtraPlayLists
+gameYaml2Game Game253Yaml = pure Game253
+
+
 mergeOnlyEqual :: String -> Word16 -> Word16 -> Word16
 mergeOnlyEqual _ c1 c2 | c1 == c2 = c1
 mergeOnlyEqual s c1 c2 = error $
@@ -167,32 +640,32 @@
     usedCodes = S.fromList $ M.elems codeMap
 
     f s = case readMaybe s of
-            Nothing -> Left s 
+            Nothing -> Left s
             Just n -> Right (n::Word16)
 
--- The following logic (for objectCodes) tries to use different object codes 
--- for different projects, as far as possible. This makes the detection of not 
+-- The following logic (for objectCodes) tries to use different object codes
+-- for different projects, as far as possible. This makes the detection of not
 -- having activated a book/product more robust.
 
--- We could theoretically set: 
+-- We could theoretically set:
 --    objectCodeOffsetMax = lastObjectCode - firstObjectCode.
--- This would assign perfectly usable object codes, and would minimize the 
--- probability of object code collisions between products, but sometimes 
--- object codes would wrap around from 14999 to 1000 even for small projects 
+-- This would assign perfectly usable object codes, and would minimize the
+-- probability of object code collisions between products, but sometimes
+-- object codes would wrap around from 14999 to 1000 even for small projects
 -- which may be undesirable. We arbitrarily do not use the last 999 possible
 -- offsets to avoid a wrap around in object codes for projects with <= 1000
--- object codes. This does not impose any limit on the number of object codes 
+-- object codes. This does not impose any limit on the number of object codes
 -- per project. Every project can always use all 14000 object codes.
     objectCodeOffsetMax = lastObjectCode - firstObjectCode - 999
 
--- Distribute the used object codes for different projects across the whole 
--- range of usable object codes. We do this by multiplying the productId with 
--- the golden ratio to achive a maximum distance between different projects, 
+-- Distribute the used object codes for different projects across the whole
+-- range of usable object codes. We do this by multiplying the productId with
+-- the golden ratio to achive a maximum distance between different projects,
 -- independent of the total number of different projects.
 -- 8035 = (14999-1000-999+1)*((sqrt(5)-1)/2)
     objectCodeOffset = toWord16(rem (productId * 8035) (toWord32(objectCodeOffsetMax) + 1))
 
--- objectCodes always contains _all_ possible object codes [firstObjectCode..lastObjectCode], 
+-- objectCodes always contains _all_ possible object codes [firstObjectCode..lastObjectCode],
 -- starting at firstObjectCode+objectCodeOffset and then wrapping around.
     objectCodes = [firstObjectCode + objectCodeOffset .. lastObjectCode] ++ [firstObjectCode .. firstObjectCode + objectCodeOffset - 1]
 
@@ -217,7 +690,9 @@
         Nothing -> error $ printf "Cannot jump to unknown script \"%s\"." s
         Just c -> c
 
-resolveRegs :: (M.Map Register Word16, [(t0, Maybe [Line Register])]) -> (M.Map ResReg Word16, [(t0, Maybe [Line ResReg])])
+resolveRegs ::
+    (M.Map Register Word16, [(a, Maybe [Line Register])]) ->
+    (M.Map ResReg Word16, [(a, Maybe [Line ResReg])])
 resolveRegs x = everywhere x
   where
     -- Could use generics somehow
@@ -235,13 +710,34 @@
 
 resolveJumps :: (String -> Word16) -> [(a, Maybe [Line b])] -> [(a, Maybe [Line b])]
 resolveJumps m = everywhere
-    where 
+  where
     everywhere = map (second (fmap (map resolveLine)))
     resolveLine (Line o cond cmds acts) = (Line o cond (map resolve cmds) acts)
     resolve (NamedJump n) = Jump (Const (m n))
     resolve c = c
 
+newtype WithFileNames a = WithFileNames
+    { runWithFileNames :: ((String -> Word16) -> a, [String] -> [String])
+    }
 
+instance Functor WithFileNames where
+    fmap f (WithFileNames (r,fns)) = WithFileNames (f . r, fns)
+
+instance Applicative WithFileNames where
+    pure x = WithFileNames (const x, id)
+    WithFileNames (r1,fns1) <*> WithFileNames (r2,fns2)
+        = WithFileNames (\m -> r1 m (r2 m),fns1 . fns2)
+
+recordFilename :: String -> WithFileNames Word16
+recordFilename fn = WithFileNames (($ fn), (fn :))
+
+resolveFileNames :: WithFileNames a -> (a, [String])
+resolveFileNames (WithFileNames (r,fns)) = (r filename_lookup, filenames)
+  where
+    filenames = S.toList $ S.fromList $ fns []
+    filename_lookup = (M.fromList (zip filenames [0..]) M.!)
+
+
 ttYaml2tt :: FilePath -> TipToiYAML -> CodeMap -> IO (TipToiFile, CodeMap)
 ttYaml2tt dir (TipToiYAML {..}) extCodeMap = do
     now <- getCurrentTime
@@ -257,27 +753,28 @@
         first = fst (M.findMin m)
         last = fst (M.findMax m)
 
-    welcome_names <- parseOneLine parseWelcome "welcome" (fromMaybe "" ttyWelcome)
+    welcome_names <- parseOneLine parsePlayList "welcome" (fromMaybe "" ttyWelcome)
 
-    (prescripts, filenames) <- liftM unzip $ forM [first .. last] $ \oid -> do
-       case M.lookup oid m of
-        Nothing -> return (\_ -> (oid, Nothing), [])
-        Just raw_lines -> do
-            (lines, filenames) <- liftM unzip $ forMn raw_lines $ \i raw_line -> do
-                let d = printf "Line %d of OID %d" i oid
-                (l,s) <- parseOneLine parseLine d raw_line
-                return (\f -> l (map f s), s)
-            return (\f -> (oid, Just (map ($ f) lines)), concat filenames)
 
-    let filenames' = S.toList $ S.fromList $ welcome_names ++ concat filenames
-    let filename_lookup = (M.fromList (zip filenames' [0..]) M.!)
-
-    let welcome = [map filename_lookup welcome_names]
+    let ((prescripts, welcome, games), filenames) = resolveFileNames $
+            (,,) <$>
+            for [first ..last] (\oid ->
+                (oid ,) <$>
+                for (M.lookup oid m) (\raw_lines ->
+                    forAn raw_lines (\i raw_line ->
+                        let d = printf "Line %d of OID %d" i oid
+                            (l,s) = either error id $ parseOneLinePure parseLine d raw_line
+                        in l <$> traverse recordFilename s
+                    )
+                )
+            ) <*>
+            traverse recordFilename welcome_names <*>
+            traverse gameYaml2Game (fromMaybe [] ttyGames)
 
     preInitRegs <- M.fromList <$> parseOneLine parseInitRegs "init" (fromMaybe "" ttyInit)
 
     -- resolve registers
-    let (initRegs, scripts) = resolveRegs (preInitRegs, map ($ filename_lookup) prescripts)
+    let (initRegs, scripts) = resolveRegs (preInitRegs, prescripts)
 
     -- resolve named jumps
     let scripts' = resolveJumps scriptMap scripts
@@ -289,14 +786,19 @@
             , r <- concatMap F.toList cs ++ concatMap F.toList as ]
 
     let ttySpeakMap = toSpeakMap (fromMaybe defaultLanguage ttyLanguage) ttySpeak
-    
+
     -- Generate text-to-spech files
     forM (M.elems ttySpeakMap) $ \(lang, txt) ->
         textToSpeech lang txt
 
-    files <- forM filenames' $ \fn -> case M.lookup fn ttySpeakMap of
+    -- Check which files do not exist
+
+
+    -- Not very nice, better to use something like Control.Applicative.Error if
+    -- it were in base, and not fixed to String.
+    files_with_errors <- forM filenames $ \fn -> case M.lookup fn ttySpeakMap of
         Just (lang, txt) -> do
-            B.readFile (ttsFileName lang txt)
+            Right <$> B.readFile (ttsFileName lang txt)
         Nothing -> do
             let paths = [ combine dir relpath
                     | ext <- map snd fileMagics
@@ -306,17 +808,21 @@
             ex <- filterM doesFileExist paths
             case ex of
                 [] -> do
-                    putStrLn "Could not find any of these files:"
-                    mapM_ putStrLn paths
-                    exitFailure
-                [f] -> B.readFile f
+                    return $ Left $ unlines $
+                      "Could not find any of these files:" :
+                      paths
+                [f] -> Right <$> B.readFile f
                 _  -> do
-                    putStrLn "Multiple matching files found:"
-                    mapM_ putStrLn ex
-                    exitFailure
+                    return $ Left $ unlines $
+                      "Multiple matching files found:" :
+                      paths
 
+    files <- case partitionEithers files_with_errors of
+        ([],files)  -> return files
+        (errors, _) -> putStr (unlines errors) >> exitFailure
+
     comment <- case ttyComment of
-        Nothing -> return $ BC.pack $ "created with tttool version " ++ showVersion version
+        Nothing -> return $ BC.pack $ "created with tttool version " ++ tttoolVersion
         Just c | length c > maxCommentLength -> do
                     printf "Comment is %d characters too long; the maximum is %d."
                            (length c - maxCommentLength) maxCommentLength
@@ -328,10 +834,10 @@
         , ttRawXor = knownRawXOR
         , ttComment = comment
         , ttDate = BC.pack date
-        , ttWelcome = welcome
+        , ttWelcome = [welcome]
         , ttInitialRegs = [fromMaybe 0 (M.lookup r initRegs) | r <- [0..maxReg]]
         , ttScripts = scripts'
-        , ttGames = []
+        , ttGames = games
         , ttAudioFiles = files
         , ttAudioXor = knownXOR
         , ttAudioFilesDoubles = False
@@ -395,8 +901,8 @@
     v <- parseWord16
     return (r,v)
 
-parseWelcome :: Parser [String]
-parseWelcome = P.commaSep lexer $ parseAudioRef
+parsePlayList :: Parser [String]
+parsePlayList = P.commaSep lexer $ parseAudioRef
 
 parseAudioRef :: Parser String
 parseAudioRef = P.lexeme lexer $ many1 (alphaNum <|> char '_')
@@ -450,13 +956,20 @@
          return (Unknown h r v : cmds, filenames)
 
     , descP "Play action" $
-      do char 'P'
+      do (withA, withStar) <- P.lexeme lexer $ do
+            char 'P'
+            withA <- optionBool (char 'A')
+            withStar <- optionBool (char '*')
+            return (withA, withStar)
          fns <- P.parens lexer $ P.commaSep1 lexer parseAudioRef
          let n = length fns
+         let c = case (withA, withStar, fns) of
+                (False, False, [fn]) -> Play (fromIntegral i)
+                (False, False, _)    -> Random (fromIntegral (i + n - 1)) (fromIntegral i)
+                (True,  False, _)    -> PlayAll (fromIntegral (i + n - 1)) (fromIntegral i)
+                (False, True,  _)    -> RandomVariant
+                (True,  True,  _)    -> PlayAllVariant
          (cmds, filenames) <- parseCommands (i+n)
-         let c = case fns of
-                [fn] -> Play (fromIntegral i)
-                _    -> Random (fromIntegral (i + n - 1)) (fromIntegral i)
          return (c : cmds, fns ++ filenames)
     , descP "Cancel" $
       do P.lexeme lexer $ char 'C'
@@ -470,6 +983,12 @@
             ]
          (cmds, filenames) <- parseCommands i
          return (cmd : cmds, filenames)
+    , descP "Timer action" $
+      do P.lexeme lexer $ char 'T'
+         (r,v) <- P.parens lexer $
+            (,) <$> parseReg <* P.comma lexer <*> parseTVal
+         (cmds, filenames) <- parseCommands i
+         return (Timer r v : cmds, filenames)
     , descP "Start Game" $
       do P.lexeme lexer $ char 'G'
          n <- P.parens lexer $ parseWord16
@@ -478,6 +997,8 @@
     ]
 
 
+optionBool :: Stream s m t => ParsecT s u m a -> ParsecT s u m Bool
+optionBool p = option False (const True <$> p)
 
 
 encodeFileCommented :: ToJSON a => FilePath -> String -> a -> IO ()
@@ -541,6 +1062,7 @@
     base = dropExtension fn
     ext  = takeExtension fn
 
+-- | Unused
 debugGame :: ProductID -> IO TipToiYAML
 debugGame productID = do
     return $ TipToiYAML
@@ -558,6 +1080,7 @@
             , let line = ppLine t $ Line 0 [] [Play n | n <- [0..5]] ([10] ++ chars)
             ]
         , ttyLanguage = Nothing
+        , ttyGames = Nothing
         }
   where
     t= M.fromList $
diff --git a/src/TipToiYamlAux.hs b/src/TipToiYamlAux.hs
new file mode 100644
--- /dev/null
+++ b/src/TipToiYamlAux.hs
@@ -0,0 +1,11 @@
+module TipToiYamlAux where
+
+import Data.Aeson.Types
+import Data.Char
+
+gameYamlOptions = defaultOptions
+    { fieldLabelModifier = map fix . map toLower . drop 2
+    , allNullaryToStringTag = True
+    }
+       where fix '_' = '-'
+             fix c   = c
diff --git a/src/Types.hs b/src/Types.hs
--- a/src/Types.hs
+++ b/src/Types.hs
@@ -45,6 +45,9 @@
 data Command r
     = Play Word16
     | Random Word8 Word8
+    | PlayAll Word8 Word8
+    | PlayAllVariant
+    | RandomVariant
     | Cancel
     | Game Word16
     | ArithOp ArithOp r (TVal r)
@@ -52,6 +55,7 @@
     | Unknown B.ByteString r (TVal r)
     | Jump (TVal r)
     | NamedJump String -- Only in YAML files, never read from GMEs
+    | Timer r (TVal r)
     deriving (Eq, Functor, Foldable)
 
 type PlayList = [Word16]
@@ -85,25 +89,182 @@
 type PlayListList = [PlayList]
 type GameId = Word16
 
-data Game
-    = Game6 Word16 B.ByteString [PlayListList] [SubGame] [SubGame] B.ByteString [PlayListList] PlayList
-    | Game7 Word16 Word16 B.ByteString [PlayListList] [SubGame] B.ByteString [PlayListList] PlayListList
-    | Game8 Word16 Word16 B.ByteString [PlayListList] [SubGame] B.ByteString [PlayListList] [OID] [GameId] PlayListList PlayListList
+data Game =
+    CommonGame
+        { gGameType                 :: Word16
+        , gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        }
+    | Game6
+        { gRounds                   :: Word16
+        , gBonusSubgameCount        :: Word16
+        , gBonusRounds              :: Word16
+        , gBonusTarget              :: Word16
+        , gUnknownI                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gUnknownQ                 :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gRoundStartPlayList2      :: PlayListList
+        , gLaterRoundStartPlayList2 :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gBonusTargetScores        :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gBonusFinishPlayLists     :: [PlayListList]
+        , gBonusSubgameIds          :: [Word16]
+        }
+    | Game7
+        { gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gSubgameGroups            :: [[GameId]]
+        }
+    | Game8
+        { gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gGameSelectOIDs           :: [Word16]
+        , gGameSelect               :: [Word16]
+        , gGameSelectErrors1        :: PlayListList
+        , gGameSelectErrors2        :: PlayListList
+        }
     | Game9
+        { gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gExtraPlayLists           :: [PlayListList]
+        }
     | Game10
+        { gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gExtraPlayLists           :: [PlayListList]
+        }
     | Game16
+        { gRounds                   :: Word16
+        , gUnknownC                 :: Word16
+        , gEarlyRounds              :: Word16
+        , gRepeatLastMedia          :: Word16
+        , gUnknownX                 :: Word16
+        , gUnknownW                 :: Word16
+        , gUnknownV                 :: Word16
+        , gStartPlayList            :: PlayListList
+        , gRoundEndPlayList         :: PlayListList
+        , gFinishPlayList           :: PlayListList
+        , gRoundStartPlayList       :: PlayListList
+        , gLaterRoundStartPlayList  :: PlayListList
+        , gSubgames                 :: [SubGame]
+        , gTargetScores             :: [Word16]
+        , gFinishPlayLists          :: [PlayListList]
+        , gExtraOIDs                :: [Word16]
+        , gExtraPlayLists           :: [PlayListList]
+        }
     | Game253
-    | UnknownGame Word16 Word16 Word16 B.ByteString [PlayListList] [SubGame] B.ByteString [PlayListList]
     deriving Show
 
 
+gameType :: Game -> Word16
+gameType (CommonGame {gGameType = gGameType }) = gGameType
+gameType Game6 {} = 6
+gameType Game7 {} = 7
+gameType Game8 {} = 8
+gameType Game9 {} = 9
+gameType Game10 {} = 10
+gameType Game16 {} = 16
+gameType Game253 {} = 253
+
 type OID = Word16
 
-data SubGame
-    = SubGame B.ByteString [OID] [OID] [OID] [PlayListList]
+data SubGame = SubGame
+    { sgUnknown :: B.ByteString
+    , sgOids1 :: [OID]
+    , sgOids2 :: [OID]
+    , sgOids3 :: [OID]
+    , sgPlaylist :: [PlayListList]
+    }
     deriving Show
 
+
 type Transscript = M.Map Word16 String
 type CodeMap = M.Map String Word16
 
+
+-- Command options
+
+data Conf = Conf
+    { cTransscriptFile :: Maybe FilePath
+    , cCodeDim :: (Int, Int)
+    , cDPI :: Int
+    , cPixelSize :: Int
+    }
+    deriving Show
 
diff --git a/src/Utils.hs b/src/Utils.hs
--- a/src/Utils.hs
+++ b/src/Utils.hs
@@ -1,10 +1,18 @@
 module Utils where
 
-import Control.Monad
+import Control.Applicative
+import Control.Monad (forM, forM_)
+import Data.Traversable (for)
 
+import Paths_tttool
+import Data.Version
+
+tttoolVersion :: String
+tttoolVersion = showVersion version
+
 -- Utilities
 
-readMaybe :: (Read a) => String -> Maybe a
+readMaybe :: Read a => String -> Maybe a
 readMaybe s = case reads s of
               [(x, "")] -> Just x
               _ -> Nothing
@@ -12,6 +20,9 @@
 
 forMn :: Monad m => [a] -> (Int -> a -> m b) -> m [b]
 forMn l f = forM (zip l [0..]) $ \(x,n) -> f n x
+
+forAn :: Applicative m => [a] -> (Int -> a -> m b) -> m [b]
+forAn l f = for (zip l [0..]) $ \(x,n) -> f n x
 
 forMn_ :: Monad m => [a] -> (Int -> a -> m b) -> m ()
 forMn_ l f = forM_ (zip l [0..]) $ \(x,n) -> f n x
diff --git a/src/tttool.hs b/src/tttool.hs
--- a/src/tttool.hs
+++ b/src/tttool.hs
@@ -1,450 +1,396 @@
-{-# LANGUAGE CPP, RecordWildCards, TupleSections #-}
+import Options.Applicative
 
-import qualified Data.ByteString.Lazy as B
-import qualified Data.ByteString.Lazy.Char8 as BC
-import System.Environment
-import System.Exit
 import System.FilePath
-import qualified Data.Binary.Builder as Br
-import qualified Data.Binary.Get as G
-import Text.Printf
-import Data.Bits
-import Data.List
-import Data.Either
-import Data.Functor
-import Data.Maybe
-import Data.Ord
-import Control.Monad
-import System.Directory
-import Numeric (readHex)
-import qualified Data.Map as M
-import Data.Foldable (for_)
-import Data.Version
+import Numeric
+import Data.List (intercalate)
+import Options.Applicative.Help.Chunk
+import Data.Monoid
 
 import Types
-import Constants
-import KnownCodes
-import GMEParser
-import GMEWriter
-import GMERun
-import PrettyPrint
 import RangeParser
-import OidCode
+import Commands
 import Utils
-import TipToiYaml
-import Lint
 
-import Paths_tttool
+-- Parameter parsing
 
+optionParser :: ParserInfo (IO ())
+optionParser =
+    info (helper <*> (conf <**> cmd)) $ mconcat
+    [ progDesc $ "tttool-" ++ tttoolVersion ++ " -- The swiss army knife for the Tiptoi hacker"
+    , footerDoc foot
+    , fullDesc
+    ]
+  where
+    foot = unChunk $ vsepChunks
+        [ paragraph "Please run \"tttool COMMAND --help\" for information on the particular command."
+        ]
 
--- Main commands
+    conf = pure Conf
+        <*> transscript
+        <*> codeDim
+        <*> dpi
+        <*> pixelSize
 
-dumpAudioTo :: FilePath -> FilePath -> IO ()
-dumpAudioTo directory file = do
-    (tt,_) <- parseTipToiFile <$> B.readFile file
+    transscript = optional $ strOption $ mconcat
+        [ long "transscript"
+        , short 't'
+        , metavar "FILE"
+        , help "Mapping from media file indices to plaintext. This should be a ';'-separated file, with OID codes in the first column and plain text in the second"
+        ]
 
-    printf "Audio Table entries: %d\n" (length (ttAudioFiles tt))
+    dpi = option auto $ mconcat
+        [ long "dpi"
+        , metavar "DPI"
+        , value 1200
+        , showDefault
+        , help "Use this resolution in dpi when creating OID codes"
+        ]
 
-    createDirectoryIfMissing False directory
-    forMn_ (ttAudioFiles tt) $ \n audio -> do
-        let audiotype = maybe "raw" snd $ find (\(m,t) -> m `B.isPrefixOf` audio) fileMagics
-        let filename = printf "%s/%s_%d.%s" directory (takeBaseName file) n audiotype
-        if B.null audio
-        then do
-            printf "Skipping empty file %s...\n" filename
-        else do
-            B.writeFile filename audio
-            printf "Dumped sample %d as %s\n" n filename
+    pixelSize = option auto $ mconcat
+        [ long "pixel-size"
+        , metavar "N"
+        , value 2
+        , showDefault
+        , help "Use this many pixels (squared) per dot in when creating OID codes."
+        ]
 
-dumpBinariesTo :: FilePath -> FilePath -> IO ()
-dumpBinariesTo directory file = do
-    (TipToiFile {..},_) <- parseTipToiFile <$> B.readFile file
+    codeDim = option parseCodeDim $ mconcat
+        [ long "code-dim"
+        , metavar "W[xH]"
+        , value (30,30)
+        , showDefaultWith showCodeDim
+        , help "Generate OID codes of this size, in millimeters"
+        ]
 
-    let binaries =
-            map (1,) ttBinaries1 ++
-            map (2,) ttBinaries2 ++
-            map (3,) ttBinaries3 ++
-            map (4,) ttBinaries4
+    showCodeDim (x,y) | x == y    = show x
+                      | otherwise = show x ++ "x" ++ show y
 
-    printf "Binary Table entries: %d\n" (length binaries)
+    parseCodeDim :: ReadM (Int, Int)
+    parseCodeDim = eitherReader go
+      where
+        go input = case reads input of
+            [(x,"")] -> return (x,x)
+            [(x,'x':rest)] -> case reads rest of
+                [(y,[])] -> return (x,y)
+                _        -> Left $ "Cannot parse dimensions " ++ input
+            _        -> Left $ "Cannot parse dimensions " ++ input
 
-    createDirectoryIfMissing False directory
-    forM_ binaries $ \(n,(desc,binary)) -> do
-        let filename = printf "%s/%d_%s" directory (n::Int) (BC.unpack desc)
-        if B.null binary
-        then do
-            printf "Skipping empty file %s...\n" filename
-        else do
-            B.writeFile filename binary
-            printf "Dumped binary %s from block %d as %s\n" (BC.unpack desc) n filename
+    cmd = hsubparser $ mconcat
+        [ cmdSep "GME creation commands:"
+        , assembleCmd
+        , cmdSep ""
 
-dumpScripts :: Transscript -> Bool -> Maybe Int -> FilePath -> IO ()
-dumpScripts t raw sel file = do
-    bytes <- B.readFile file
-    let (tt,_) = parseTipToiFile bytes
-        st' | Just n <- sel = filter ((== fromIntegral n) . fst) (ttScripts tt)
-            | otherwise     = ttScripts tt
+        , cmdSep "OID code creation commands:"
+        , oidTableCmd
+        , oidCodesCmd
+        , oidCodeCmd
+        , cmdSep ""
 
-    forM_ st' $ \(i, ms) -> case ms of
-        Nothing -> do
-            printf "Script for OID %d: Disabled\n" i
-        Just lines -> do
-            printf "Script for OID %d:\n" i
-            forM_ lines $ \line -> do
-                if raw then printf "%s\n"     (lineHex bytes line)
-                       else printf "    %s\n" (ppLine t line)
+        , cmdSep "GME analysis commands:"
+        , infoCmd
+        , exportCmd
+        , scriptsCmd
+        , scriptCmd
+        , gamesCmd
+        , lintCmd
+        , segmentsCmd
+        , segmentCmd
+        , explainCmd
+        , holesCmd
+        , rewriteCmd
+        , cmdSep ""
 
+        , cmdSep "GME extraction commands:"
+        , mediaCmd
+        , binariesCmd
+        , cmdSep ""
 
-dumpInfo :: Transscript -> FilePath -> IO ()
-dumpInfo t file = do
-    (TipToiFile {..},_) <- parseTipToiFile <$> B.readFile file
-    let st = ttScripts
+        , cmdSep "Simulation commands:"
+        , playCmd
+        ]
 
-    printf "Product ID: %d\n" ttProductId
-    printf "Raw XOR value: 0x%08X\n" ttRawXor
-    printf "Magic XOR value: 0x%02X\n" ttAudioXor
-    printf "Comment: %s\n" (BC.unpack ttComment)
-    printf "Date: %s\n" (BC.unpack ttDate)
-    printf "Number of registers: %d\n" (length ttInitialRegs)
-    printf "Initial registers: %s\n" (show ttInitialRegs)
-    printf "Initial sounds: %s\n" (ppPlayListList t ttWelcome)
-    printf "Scripts for OIDs from %d to %d; %d/%d are disabled.\n"
-        (fst (head st)) (fst (last st))
-        (length (filter (isNothing . snd) st)) (length st)
-    printf "Audio table entries: %d\n" (length ttAudioFiles)
-    when ttAudioFilesDoubles $ printf "Audio table repeated twice\n"
-    printf "Binary tables entries: %d/%d/%d/%d\n"
-        (length ttBinaries1)
-        (length ttBinaries2)
-        (length ttBinaries3)
-        (length ttBinaries4)
-    for_ ttSpecialOIDs $ \(oid1, oid2) ->
-        printf "Speical OIDs: %d, %d\n" oid1 oid2
-    printf "Checksum found 0x%08X, calculated 0x%08X\n" ttChecksum ttChecksumCalc
+only :: (Eq a, Show a) => [a] -> ReadM a -> ReadM a
+only valid r = do
+    x <- r
+    if x `elem` valid then return x
+                      else readerError msg
+  where msg = "Sorry, supported values are only: " ++ intercalate ", " (map show valid)
 
-lint :: FilePath -> IO ()
-lint file = do
-    (tt,segments) <- parseTipToiFile <$> B.readFile file
-    lintTipToi tt segments
+cmdSep :: String -> Mod CommandFields a
+cmdSep s = command s $ info empty mempty
 
-play :: Transscript -> FilePath -> IO ()
-play t file = do
-    (cm,tt) <-
-        if ".yaml" `isSuffixOf` file
-        then do
-            (tty, extraCodeMap) <- readTipToiYaml file
-            (tt, codeMap) <- ttYaml2tt (takeDirectory file) tty extraCodeMap
-            return (codeMap, tt)
-        else do
-            (tt,_) <- parseTipToiFile <$> B.readFile file
-            return (M.empty, tt)
-    playTipToi cm t tt
 
-segments :: FilePath -> IO ()
-segments file = do
-    (tt,segments) <- parseTipToiFile <$> B.readFile file
-    mapM_ printSegment segments
+-- Common option Parsers
 
-printSegment (o,l,desc) = printf "At 0x%08X Size %8d: %s\n" o l (ppDesc desc)
+gmeFileParser :: Parser FilePath
+gmeFileParser = strArgument $ mconcat
+    [ metavar "GME"
+    , help "GME file to read"
+    ]
 
-explain :: FilePath -> IO ()
-explain file = do
-    bytes <- B.readFile file
-    let (tt,segments) = parseTipToiFile bytes
-    forM_ (addHoles segments) $ \e -> case e of
-        Left (o,l) -> do
-            printSegment (o,l,["-- unknown --"])
-            printExtract bytes o l
-            putStrLn ""
-        Right ss@((o,l,_):_) -> do
-            mapM_ printSegment ss
-            printExtract bytes o l
-            putStrLn ""
+yamlFileParser :: Parser FilePath
+yamlFileParser = strArgument $ mconcat
+    [ metavar "YAML"
+    , help "Yaml file to read"
+    ]
 
-printExtract :: B.ByteString -> Offset -> Word32 -> IO ()
-printExtract b o l = do
-    let o1 = o .&. 0xFFFFFFF0
-    lim_forM_ [o1, o1+0x10 .. (o + l-1)] $ \s -> do
-        let s' = max o s
-        let d  = fromIntegral s' - fromIntegral s
-        let l' = (min (o + l) (s + 0x10)) - s'
-        printf "   0x%08X: %s%s\n"
-            s
-            (replicate (d*3) ' ')
-            (prettyHex (extract s' l' b))
+rawSwitchParser :: Parser Bool
+rawSwitchParser = switch $ mconcat
+    [ long "raw"
+    , help "print the scripts in their raw form"
+    ]
+
+-- Individual commands
+
+infoCmd :: Mod CommandFields (Conf -> IO ())
+infoCmd =
+    command "info" $
+    info parser $
+    progDesc "Print general information about a GME file"
   where
-    lim_forM_ l act
-        = if length l > 30
-          then do act (head l)
-                  printf "   (skipping %d lines)\n" (length l - 2) :: IO ()
-                  act (last l)
-          else do forM_ l act
+    parser = flip dumpInfo <$> gmeFileParser
 
-findPosition :: Integer -> FilePath -> IO ()
-findPosition pos' file = do
-    (tt,segments) <- parseTipToiFile <$> B.readFile file
-    case find (\(o,l,_) -> pos >= o && pos < o + l) segments of
-        Just s -> do
-            printf "Offset 0x%08X is part of this segment:\n" pos
-            printSegment s
-        Nothing -> do
-            let before = filter (\(o,l,_) -> pos >= o + l) segments
-                after = filter (\(o,l,_) -> pos < o) segments
-                printBefore | null before = printf "(nothing before)\n"
-                            | otherwise   = printSegment (maximumBy (comparing (\(o,l,_) -> o+l)) before)
-                printAfter  | null after  = printf "(nothing after)\n"
-                            | otherwise   = printSegment (minimumBy (comparing (\(o,l,_) -> o)) after)
-            printf "Offset %08X not found. It lies between these two segments:\n" pos
-            printBefore
-            printAfter
 
-    where
-    pos = fromIntegral pos'
+mediaCmd :: Mod CommandFields (Conf -> IO ())
+mediaCmd =
+    command "media" $
+    info parser $
+    progDesc "dumps all audio samples"
+  where
+    parser = const <$> (dumpAudioTo <$> mediaDirParser <*> gmeFileParser)
 
+    mediaDirParser :: Parser FilePath
+    mediaDirParser = strOption $ mconcat
+        [ long "dir"
+        , short 'd'
+        , metavar "DIR"
+        , help "Media output directory"
+        , value "media"
+        , showDefault
+        ]
 
--- returns a list of segments in case of overlap
-addHoles :: [Segment] -> [Either (Offset, Word32) [Segment]]
-addHoles = go 0
-   where go at [] = []
-         go at ss@((o,l,d):_)
-            | at /= o -- a hole
-            = Left (at, o-at) : go o ss
-            | otherwise -- no hole
-            = let (this, others) = span (\(o',l',_) -> o == o' && l == l') ss
-              in Right this : go (o + l) others
+scriptsCmd :: Mod CommandFields (Conf -> IO ())
+scriptsCmd =
+    command "scripts" $
+    info parser $
+    progDesc "prints the decoded scripts for each OID"
+  where
+    parser = (\r f c -> dumpScripts c r Nothing f)
+        <$> rawSwitchParser
+        <*> gmeFileParser
 
-unknown_segments :: FilePath -> IO ()
-unknown_segments file = do
-    bytes <- B.readFile file
-    let (_,segments) = parseTipToiFile bytes
-    let unknown_segments =
-            filter (\(o,l) -> not
-                (l == 2 && G.runGet (G.skip (fromIntegral o) >> G.getWord16le) bytes == 0)) $
-            lefts $ addHoles $ segments
-    printf "Unknown file segments: %d (%d bytes total)\n"
-        (length unknown_segments) (sum (map snd unknown_segments))
-    forM_ unknown_segments $ \(o,l) ->
-        printf "   Offset: %08X to %08X (%d bytes)\n" o (o+l) l
 
+scriptCmd :: Mod CommandFields (Conf -> IO ())
+scriptCmd =
+    command "script" $
+    info parser $
+    progDesc "prints the decoded scripts for a specific OID"
+  where
+    parser = (\r f n c -> dumpScripts c r (Just n) f)
+        <$> rawSwitchParser
+        <*> gmeFileParser
+        <*> scriptParser
 
-withEachFile :: (FilePath -> IO ()) -> [FilePath] -> IO ()
-withEachFile _ [] = main' undefined []
-withEachFile a [f] = a f 
-withEachFile a fs = forM_ fs $ \f -> do 
-    printf "%s:\n" f 
-    a f
+    scriptParser = argument auto $ mconcat
+        [ metavar "OID"
+        , help "OID to look up"
+        ]
 
+binariesCmd :: Mod CommandFields (Conf -> IO ())
+binariesCmd =
+    command "binaries" $
+    info parser $
+    progDesc "dumps all binaries"
+  where
+    parser = const <$> (dumpBinariesTo <$> binariesDirParser <*> gmeFileParser)
 
-dumpGames :: Transscript -> FilePath -> IO ()
-dumpGames t file = do
-    bytes <- B.readFile file
-    let (tt,_) = parseTipToiFile bytes
-    forMn_ (ttGames tt) $ \n g -> do
-        printf "Game %d:\n" n
-        printf "%s\n" (ppGame t g)
+    binariesDirParser :: Parser FilePath
+    binariesDirParser = strOption $ mconcat
+        [ long "dir"
+        , short 'd'
+        , metavar "DIR"
+        , help "Binaries output directory"
+        , value "binaries"
+        , showDefault
+        ]
 
-writeTipToi :: FilePath -> TipToiFile -> IO ()
-writeTipToi out tt = do
-    let bytes = writeTipToiFile tt
-    let checksum = B.foldl' (\s b -> fromIntegral b + s) 0 bytes
-    B.writeFile out $ Br.toLazyByteString $
-        Br.fromLazyByteString bytes `Br.append` Br.putWord32le checksum
+gamesCmd :: Mod CommandFields (Conf -> IO ())
+gamesCmd =
+    command "games" $
+    info parser $
+    progDesc "prints the decoded games"
+  where
+    parser = flip dumpGames <$> gmeFileParser
 
-rewrite :: FilePath -> FilePath -> IO ()
-rewrite inf out = do
-    (tt,_) <- parseTipToiFile <$> B.readFile inf
-    writeTipToi out tt
+lintCmd :: Mod CommandFields (Conf -> IO ())
+lintCmd =
+    command "lint" $
+    info parser $
+    progDesc "checks for errors in the file or in this program"
+  where
+    parser = const <$> (lint <$> gmeFileParser)
 
-export :: FilePath -> FilePath -> IO ()
-export inf out = do
-    (tt,_) <- parseTipToiFile <$> B.readFile inf
-    let tty = tt2ttYaml (printf "media/%s_%%s" (takeBaseName inf)) tt
-    ex <- doesFileExist out
-    if ex
-        then printf "File \"%s\" does already exist. Please remove it first\nif you want to export \"%s\" again.\n" out inf >> exitFailure
-        else writeTipToiYaml out tty
+segmentsCmd :: Mod CommandFields (Conf -> IO ())
+segmentsCmd =
+    command "segments" $
+    info parser $
+    progDesc "lists all known parts of the file, with description."
+  where
+    parser = const <$> (segments <$> gmeFileParser)
 
 
-assemble :: FilePath -> FilePath -> IO ()
-assemble inf out = do
-    (tty, codeMap) <- readTipToiYaml inf
-    (tt, totalMap) <- ttYaml2tt (takeDirectory inf) tty codeMap
-    writeTipToiCodeYaml inf tty codeMap totalMap
-    writeTipToi out tt
+segmentCmd :: Mod CommandFields (Conf -> IO ())
+segmentCmd =
+    command "segment" $
+    info parser $
+    progDesc "prints the decoded scripts for a specific OID"
+  where
+    parser = (\f n c -> findPosition n f)
+        <$> gmeFileParser
+        <*> offsetParser
 
+    offsetParser = argument hexReadM $ mconcat
+        [ metavar "POS"
+        , help "offset into the file to look up, in bytes"
+        ]
 
-genPNGs :: DPI -> PixelSize -> String -> IO ()
-genPNGs dpi ps arg = do
-    ex <- doesFileExist arg
-    if ex then genPNGsForFile dpi ps arg
-          else genPNGsForCodes dpi ps arg
+    hexReadM :: ReadM Integer
+    hexReadM = eitherReader go
+      where go n | Just int <- readMaybe n = return int
+                 | [(int,[])] <- readHex n = return int
+                 | otherwise               = Left $ "Cannot parse offset " ++ n
 
-genPNGsForFile :: DPI -> PixelSize -> FilePath -> IO ()
-genPNGsForFile dpi ps inf = do
-    (tty, codeMap) <- readTipToiYaml inf
-    (tt, totalMap) <- ttYaml2tt (takeDirectory inf) tty codeMap
-    let codes = ("START", fromIntegral (ttProductId tt)) : M.toList totalMap
-    forM_  codes $ \(s,c) -> do
-        genPNG dpi ps c $ printf "oid-%d-%s.png" (ttProductId tt) s
+holesCmd :: Mod CommandFields (Conf -> IO ())
+holesCmd =
+    command "holes" $
+    info parser $
+    progDesc "lists all unknown parts of the file."
+  where
+    parser = const <$> (unknown_segments <$> gmeFileParser)
 
-genPNGsForCodes :: DPI -> PixelSize -> String -> IO ()
-genPNGsForCodes dpi ps code_str = do
-    codes <- parseRange code_str
-    forM_ codes $ \c -> do
-        genPNG dpi ps c $ printf "oid-%d.png" c
+explainCmd :: Mod CommandFields (Conf -> IO ())
+explainCmd =
+    command "explain" $
+    info parser $
+    progDesc "print a hexdump of a GME file with descriptions"
+  where
+    parser = const <$> (explain <$> gmeFileParser)
 
-genPNG :: DPI -> PixelSize -> Word16 -> String -> IO ()
-genPNG dpi ps c filename =
-    case code2RawCode c of
-        Nothing -> printf "Skipping %s, code %d not known.\n" filename c
-        Just r -> do
-            printf "Writing %s.. (Code %d, raw code %d)\n" filename c r
-            genRawPNG dpi ps r filename
+playCmd :: Mod CommandFields (Conf -> IO ())
+playCmd =
+    command "play" $
+    info parser $
+    progDesc "interactively play a GME file"
+  where
+    parser = flip play <$> gmeFileParser
 
 
-genPNGsForRawCodes :: DPI -> PixelSize -> String -> IO ()
-genPNGsForRawCodes dpi ps code_str = do
-    codes <- parseRange code_str
-    forM_ codes $ \r -> do
-        let filename = printf "oid-raw-%d.png" r
-        printf "Writing %s... (raw code %d)\n" filename r
-        genRawPNG dpi ps r filename
+rewriteCmd :: Mod CommandFields (Conf -> IO ())
+rewriteCmd =
+    command "rewrite" $
+    info parser $
+    progDesc "parses the file and reads it again (for debugging)"
+  where
+    parser = const <$> (rewrite <$> gmeFileParser <*> outFileParser)
 
+    outFileParser :: Parser FilePath
+    outFileParser = strArgument $ mconcat
+        [ metavar "OUT"
+        , help "GME file to write"
+        ]
 
--- The main function
+twoFiles :: String -> (FilePath -> FilePath -> a) -> (FilePath -> Maybe FilePath -> a)
+twoFiles suffix go inFile (Just outFile) = go inFile outFile
+twoFiles suffix go inFile Nothing = go inFile outFile
+  where outFile = dropExtension inFile <.> suffix
 
 
-readTransscriptFile :: FilePath -> IO Transscript
-readTransscriptFile transcriptfile_ = do
-    file <- readFile transcriptfile_
-    return $ M.fromList
-        [ (idx, string)
-        | l <- lines file
-        , (idxstr:string:_) <- return $ wordsWhen (';'==) l
-        , Just idx <- return $ readMaybe idxstr
+exportCmd :: Mod CommandFields (Conf -> IO ())
+exportCmd =
+    command "export" $
+    info parser $
+    progDesc "dumps the file in the human-readable yaml format"
+  where
+    parser = const <$> (twoFiles "yaml" export <$> gmeFileParser <*> outFileParser)
+
+    outFileParser :: Parser (Maybe FilePath)
+    outFileParser = optional $ strArgument $ mconcat
+        [ metavar "OUT"
+        , help "YAML file to write"
         ]
 
--- Avoiding dependencies, using code from http://stackoverflow.com/a/4981265/946226
-wordsWhen     :: (Char -> Bool) -> String -> [String]
-wordsWhen p s =  case dropWhile p s of
-                      "" -> []
-                      s' -> w : wordsWhen p s''
-                            where (w, s'') = break p s'
+assembleCmd :: Mod CommandFields (Conf -> IO ())
+assembleCmd =
+    command "assemble" $
+    info parser $
+    progDesc "creates a gme file from the given source"
+  where
+    parser = const <$> (twoFiles "gme" assemble <$> yamlFileParser <*> outFileParser)
 
-main' t ("-t":transscript:args) =
-    do t2 <- readTransscriptFile transscript
-       main' (t `M.union` t2) args
+    outFileParser :: Parser (Maybe FilePath)
+    outFileParser = optional $ strArgument $ mconcat
+        [ metavar "OUT"
+        , help "GME file to write"
+        ]
 
-main' t ("export": inf : [] )       = main' t ("export":inf: dropExtension inf <.> "yaml":[])
-main' t ("assemble": inf : [] )     = main' t ("assemble":inf: dropExtension inf <.> "gme":[])
+oidTableCmd :: Mod CommandFields (Conf -> IO ())
+oidTableCmd =
+    command "oid-table" $
+    info parser $
+    progDesc "creates a PDF file with all codes in the yaml file"
+  where
+    parser = (\a b conf -> twoFiles "pdf" (genOidTable conf) a b) <$> yamlFileParser <*> outFileParser
 
-main' t ("info": files)             = withEachFile (dumpInfo t) files
-main' t ("media": "-d": dir: files) = withEachFile (dumpAudioTo dir) files
-main' t ("media": files)            = withEachFile (dumpAudioTo "media") files
-main' t ("scripts": files)          = withEachFile (dumpScripts t False Nothing) files
-main' t ("script":  file : n:[])
-    | Just int <- readMaybe n       =              dumpScripts t False (Just int) file
-main' t ("raw-scripts": files)      = withEachFile (dumpScripts t True Nothing) files
-main' t ("raw-script": file : n:[])
-    | Just int <- readMaybe n       =              dumpScripts t True (Just int) file
-main' t ("binaries": "-d": dir: files) = withEachFile (dumpBinariesTo dir) files
-main' t ("binaries": files)            = withEachFile (dumpBinariesTo "binaries") files
-main' t ("games": files)            = withEachFile (dumpGames t) files
-main' t ("lint": files)             = withEachFile lint files
-main' t ("segments": files)         = withEachFile segments files
-main' t ("segment": file : n :[])
-    | Just int <- readMaybe n       =              findPosition int file
-    | [(int,[])] <- readHex n       =              findPosition int file
-main' t ("holes": files)            = withEachFile unknown_segments files
-main' t ("explain": files)          = withEachFile explain files
-main' t ("play": file : [])         =              play t file
-main' t ("rewrite": inf : out: [])  =              rewrite inf out
-main' t ("export": inf : out: [] )  =              export inf out
-main' t ("assemble": inf : out: []) =              assemble inf out
-main' t ("oid-code": "-d" : "600" : codes@(_:_))
-                                    =              genPNGs D600 SinglePixel (unwords codes)
-main' t ("oid-code": "-d" : "1200" : codes@(_:_))
-                                    =              genPNGs D1200 SinglePixel (unwords codes)
-main' t ("oid-code": "-d" : "600d" : codes@(_:_))
-                                    =              genPNGs D600 DoublePixel (unwords codes)
-main' t ("oid-code": "-d" : "1200d" : codes@(_:_))
-                                    =              genPNGs D1200 DoublePixel (unwords codes)
-main' t ("oid-code": "-d" : _)      = do
-    putStrLn $ "The parameter to -d has to be 600, 1200, 600d or 1200d"
-    exitFailure
-main' t ("oid-code": codes@(_:_))   =              genPNGs D1200 SinglePixel (unwords codes)
-main' t ("raw-oid-code": "-d" : "600" : codes@(_:_))
-                                    =              genPNGsForRawCodes D600 SinglePixel (unwords codes)
-main' t ("raw-oid-code": "-d" : "1200" : codes@(_:_))
-                                    =              genPNGsForRawCodes D1200 SinglePixel (unwords codes)
-main' t ("raw-oid-code": "-d" : "600d" : codes@(_:_))
-                                    =              genPNGsForRawCodes D600 DoublePixel (unwords codes)
-main' t ("raw-oid-code": "-d" : "1200d" : codes@(_:_))
-                                    =              genPNGsForRawCodes D1200 DoublePixel (unwords codes)
-main' t ("raw-oid-code": "-d" : _)  = do
-    putStrLn $ "The parameter to -d has to be 600, 1200, 600d or 1200d"
-    exitFailure
-main' t ("raw-oid-code": codes@(_:_)) =            genPNGsForRawCodes D1200 SinglePixel (unwords codes)
-main' _ _ = do
-    prg <- getProgName
-    putStrLn $ "This is the Tiptoi toolkit, version " ++ showVersion version
-    putStrLn $ ""
-    putStrLn $ "Usage: " ++ prg ++ " [options] command"
-    putStrLn $ ""
-    putStrLn $ "Options:"
-    putStrLn $ "    -t <transcriptfile>"
-    putStrLn $ "       in the screen output, replaces media file indices by a transscript"
-    putStrLn $ ""
-    putStrLn $ "Commands:"
-    putStrLn $ "    info <file.gme>..."
-    putStrLn $ "       general information"
-    putStrLn $ "    media [-d dir] <file.gme>..."
-    putStrLn $ "       dumps all audio samples to the given directory (default: media/)"
-    putStrLn $ "    scripts <file.gme>..."
-    putStrLn $ "       prints the decoded scripts for each OID"
-    putStrLn $ "    script <file.gme> <n>"
-    putStrLn $ "       prints the decoded scripts for the given OID"
-    putStrLn $ "    raw-scripts <file.gme>..."
-    putStrLn $ "       prints the scripts for each OID, in their raw form"
-    putStrLn $ "    raw-script <file.gme> <n>"
-    putStrLn $ "       prints the scripts for the given OID, in their raw form"
-    putStrLn $ "    binaries [-d dir] <file.gme>..."
-    putStrLn $ "       dumps all binaries to the given directory (default: binaries/)"
-    putStrLn $ "    games <file.gme>..."
-    putStrLn $ "       prints the decoded games"
-    putStrLn $ "    lint <file.gme>"
-    putStrLn $ "       checks for errors in the file or in this program"
-    putStrLn $ "    segments <file.gme>..."
-    putStrLn $ "       lists all known parts of the file, with description."
-    putStrLn $ "    segment <file.gme> <pos>"
-    putStrLn $ "       which segment contains the given position."
-    putStrLn $ "    holes <file.gme>..."
-    putStrLn $ "       lists all unknown parts of the file."
-    putStrLn $ "    explain <file.gme>..."
-    putStrLn $ "       lists all parts of the file, with description and hexdump."
-    putStrLn $ "    play <file.gme or file.yaml>"
-    putStrLn $ "       interactively play: Enter OIDs, and see what happens."
-    putStrLn $ "    rewrite <infile.gme> <outfile.gme>"
-    putStrLn $ "       parses the file and serializes it again (for debugging)."
-    putStrLn $ "    export <infile.gme> [<outfile.yaml>]"
-    putStrLn $ "       dumps the file in the human-readable yaml format"
-    putStrLn $ "    assemble <infile.yaml> <outfile.gme>"
-    putStrLn $ "       creates a gme file from the given source"
-    putStrLn $ "    oid-code [-d DPI] <codes>"
-    putStrLn $ "       creates a PNG file for each given code"
-    putStrLn $ "       scale this to 10cm×10cm"
-    putStrLn $ "       By default, it creates a 1200 dpi image. With -d 600, you"
-    putStrLn $ "       obtain a 600 dpi image. With -d 600d resp. 1200d you can"
-    putStrLn $ "       double the size of the pixel."
-    putStrLn $ "       <codes> can be a range, e.g. 1,3,1000-1085."
-    putStrLn $ "       Uses oid-<code>.png as the file name."
-    putStrLn $ "    oid-code [-d DPI] <infile.yaml>"
-    putStrLn $ "       Like above, but creates one file for each code in the yaml file."
-    putStrLn $ "       Uses oid-<product-id>-<scriptname or code>.png as the file name."
-    putStrLn $ "    raw-oid-code [-d DPI] <raw codes>"
-    putStrLn $ "       creates a PNG file with the given \"raw code\". Usually not needed."
-    putStrLn $ "       Uses oid-raw-<code>.png as the file name."
-    exitFailure
+    outFileParser :: Parser (Maybe FilePath)
+    outFileParser = optional $ strArgument $ mconcat
+        [ metavar "OUT"
+        , help "PDF file to write"
+        ]
 
-main = getArgs >>= (main' M.empty)
+oidCodesCmd :: Mod CommandFields (Conf -> IO ())
+oidCodesCmd =
+    command "oid-codes" $
+    info parser $
+    progDesc "creates PNG files for every OID in the yaml file." <>
+    footerDoc foot
+  where
+    foot = unChunk $ vsepChunks
+        [ paragraph "Uses oid-<code>.png as the file name."
+        , paragraph "Use the global options to configure size, resolution and blackness of the code (see ./tttool --help)."
+        , paragraph $ "Note that it used to work to call \"tttool oid-code foo.yaml\". " ++
+                      "Please use \"tttool oid-codes\" for that now."
+        ]
+    parser = flip genPNGsForFile <$> yamlFileParser
 
+oidCodeCmd :: Mod CommandFields (Conf -> IO ())
+oidCodeCmd =
+    command "oid-code" $
+    info parser $
+    progDesc "creates PNG files for each given code(s)" <>
+    footerDoc foot
+  where
+    foot = unChunk $ vsepChunks
+        [ paragraph "Uses oid-<code>.png as the file name."
+        , paragraph "Use the global options to configure size, resolution and blackness of the code (see ./tttool --help)."
+        , paragraph $ "Note that it used to work to call \"tttool oid-code foo.yaml\". " ++
+                      "Please use \"tttool oid-codes\" for that now."
+        ]
+
+    parser =(\raw range c -> genPNGsForCodes raw c range) <$> rawCodeSwitchParser <*> codeRangeParser
+
+    codeRangeParser :: Parser [Word16]
+    codeRangeParser = argument (eitherReader parseRange) $ mconcat
+        [ metavar "RANGE"
+        , help "OID range, for example e.g. 1,3,1000-1085."
+        ]
+
+    rawCodeSwitchParser :: Parser Bool
+    rawCodeSwitchParser = switch $ mconcat
+        [ long "raw"
+        , help "take the given codes as \"raw codes\" (rarely needed)"
+        ]
+
+main :: IO ()
+main = do
+    act <- customExecParser (prefs showHelpOnError) optionParser
+    act
diff --git a/tttool.cabal b/tttool.cabal
--- a/tttool.cabal
+++ b/tttool.cabal
@@ -1,5 +1,5 @@
 name:                tttool
-version:             1.5.1
+version:             1.6
 synopsis:            Working with files for the Tiptoi® pen
 description:         The Ravensburger Tiptoi® pen is programmed via special
                      files. Their file format has been reverse engineered; this
@@ -37,18 +37,21 @@
     Lint,
     Language,
     OidCode,
+    OidTable,
     OneLineParser,
     PlaySound,
     PrettyPrint,
     RangeParser,
     TipToiYaml,
+    TipToiYamlAux,
     TextToSpeech,
     KnownCodes,
     Types,
+    Commands,
     Utils
 
   build-depends:
-    base        == 4.5.*   ||  == 4.6.*  ||  == 4.7.* ||  == 4.8.*,
+    base        >= 4.5 &&  < 4.10,
     binary      == 0.5.*   ||  == 0.7.*,
     containers  == 0.4.*   ||  == 0.5.*,
     directory   == 1.2.*,
@@ -56,7 +59,7 @@
     filepath    == 1.3.*   ||  == 1.4.*,
     template-haskell >= 2.7 && < 2.11,
 
-    JuicyPixels == 3.1.*   ||  == 3.2.*,
+    JuicyPixels >= 3.2.5   &&   < 3.3,
     aeson       >= 0.7     &&   < 0.11,
     hashable    == 1.2.*,
     haskeline   == 0.7.*,
@@ -65,15 +68,22 @@
     process     >= 1.1      &&   < 1.5,
     random      >= 1.0      &&   < 1.2,
     vector      >= 0.10     &&   < 0.12,
-    yaml        == 0.8.*
+    yaml        == 0.8.*,
+    HPDF        >= 1.4.10   && < 1.5,
+    split       == 0.2.*,
+    optparse-applicative == 0.12.*,
+    spool       == 0.1.*,
+    zlib        >= 0.5      && < 0.7
 
   if impl(ghc < 7.5)
     build-depends: ghc-prim
 
+  ghc-options: -fcontext-stack=42
+
   if flag(old-locale)
     build-depends: time == 1.4.*, old-locale
   else
-    build-depends: time == 1.5.*
+    build-depends: time >= 1.5 && < 1.7
 
   if flag(bytestring_has_builder)
     build-depends:
@@ -88,6 +98,7 @@
   default-language:    Haskell2010
   ghc-options:
     -fwarn-unused-imports
+    -fwarn-incomplete-patterns
     -rtsopts
     -with-rtsopts=-K100M
 
