packages feed

rattletrap 4.1.2 → 5.0.0

raw patch · 16 files changed

+303/−52 lines, 16 filesdep ~basePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base

API changes (from Hackage documentation)

- Rattletrap: [rigidBodyStateAttributeUnknown1] :: RigidBodyStateAttribute -> Maybe Bool
- Rattletrap: [rigidBodyStateAttributeUnknown2] :: RigidBodyStateAttribute -> Maybe Bool
- Rattletrap.Type.RigidBodyStateAttribute: [rigidBodyStateAttributeUnknown1] :: RigidBodyStateAttribute -> Maybe Bool
- Rattletrap.Type.RigidBodyStateAttribute: [rigidBodyStateAttributeUnknown2] :: RigidBodyStateAttribute -> Maybe Bool
+ Rattletrap.Decode.Quaternion: decodeQuaternionBits :: DecodeBits Quaternion
+ Rattletrap.Decode.Rotation: decodeRotationBits :: (Int, Int, Int) -> DecodeBits Rotation
+ Rattletrap.Encode.Quaternion: putQuaternion :: Quaternion -> BitPut ()
+ Rattletrap.Encode.Rotation: putRotation :: Rotation -> BitPut ()
+ Rattletrap.Type.Quaternion: ComponentW :: Component
+ Rattletrap.Type.Quaternion: ComponentX :: Component
+ Rattletrap.Type.Quaternion: ComponentY :: Component
+ Rattletrap.Type.Quaternion: ComponentZ :: Component
+ Rattletrap.Type.Quaternion: Quaternion :: Double -> Double -> Double -> Double -> Quaternion
+ Rattletrap.Type.Quaternion: [quaternionW] :: Quaternion -> Double
+ Rattletrap.Type.Quaternion: [quaternionX] :: Quaternion -> Double
+ Rattletrap.Type.Quaternion: [quaternionY] :: Quaternion -> Double
+ Rattletrap.Type.Quaternion: [quaternionZ] :: Quaternion -> Double
+ Rattletrap.Type.Quaternion: compressPart :: Double -> CompressedWord
+ Rattletrap.Type.Quaternion: data Component
+ Rattletrap.Type.Quaternion: data Quaternion
+ Rattletrap.Type.Quaternion: decompressPart :: CompressedWord -> Double
+ Rattletrap.Type.Quaternion: instance Data.Aeson.Types.FromJSON.FromJSON Rattletrap.Type.Quaternion.Quaternion
+ Rattletrap.Type.Quaternion: instance Data.Aeson.Types.ToJSON.ToJSON Rattletrap.Type.Quaternion.Quaternion
+ Rattletrap.Type.Quaternion: instance GHC.Classes.Eq Rattletrap.Type.Quaternion.Component
+ Rattletrap.Type.Quaternion: instance GHC.Classes.Eq Rattletrap.Type.Quaternion.Quaternion
+ Rattletrap.Type.Quaternion: instance GHC.Classes.Ord Rattletrap.Type.Quaternion.Component
+ Rattletrap.Type.Quaternion: instance GHC.Classes.Ord Rattletrap.Type.Quaternion.Quaternion
+ Rattletrap.Type.Quaternion: instance GHC.Show.Show Rattletrap.Type.Quaternion.Component
+ Rattletrap.Type.Quaternion: instance GHC.Show.Show Rattletrap.Type.Quaternion.Quaternion
+ Rattletrap.Type.Quaternion: maxComponent :: Quaternion -> Component
+ Rattletrap.Type.Quaternion: maxCompressedValue :: Word
+ Rattletrap.Type.Quaternion: toPart :: Double -> Double -> Double -> Double
+ Rattletrap.Type.Quaternion: toQuaternion :: Component -> Double -> Double -> Double -> Quaternion
+ Rattletrap.Type.Rotation: RotationCompressedWordVector :: CompressedWordVector -> Rotation
+ Rattletrap.Type.Rotation: RotationQuaternion :: Quaternion -> Rotation
+ Rattletrap.Type.Rotation: data Rotation
+ Rattletrap.Type.Rotation: instance Data.Aeson.Types.FromJSON.FromJSON Rattletrap.Type.Rotation.Rotation
+ Rattletrap.Type.Rotation: instance Data.Aeson.Types.ToJSON.ToJSON Rattletrap.Type.Rotation.Rotation
+ Rattletrap.Type.Rotation: instance GHC.Classes.Eq Rattletrap.Type.Rotation.Rotation
+ Rattletrap.Type.Rotation: instance GHC.Classes.Ord Rattletrap.Type.Rotation.Rotation
+ Rattletrap.Type.Rotation: instance GHC.Show.Show Rattletrap.Type.Rotation.Rotation
- Rattletrap: RigidBodyStateAttribute :: Bool -> Vector -> Maybe Bool -> CompressedWordVector -> Maybe Bool -> Maybe Vector -> Maybe Vector -> RigidBodyStateAttribute
+ Rattletrap: RigidBodyStateAttribute :: Bool -> Vector -> Rotation -> Maybe Vector -> Maybe Vector -> RigidBodyStateAttribute
- Rattletrap: [rigidBodyStateAttributeRotation] :: RigidBodyStateAttribute -> CompressedWordVector
+ Rattletrap: [rigidBodyStateAttributeRotation] :: RigidBodyStateAttribute -> Rotation
- Rattletrap.Decode.CompressedWordVector: decodeCompressedWordVectorBits :: (Int, Int, Int) -> DecodeBits CompressedWordVector
+ Rattletrap.Decode.CompressedWordVector: decodeCompressedWordVectorBits :: DecodeBits CompressedWordVector
- Rattletrap.Type.RigidBodyStateAttribute: RigidBodyStateAttribute :: Bool -> Vector -> Maybe Bool -> CompressedWordVector -> Maybe Bool -> Maybe Vector -> Maybe Vector -> RigidBodyStateAttribute
+ Rattletrap.Type.RigidBodyStateAttribute: RigidBodyStateAttribute :: Bool -> Vector -> Rotation -> Maybe Vector -> Maybe Vector -> RigidBodyStateAttribute
- Rattletrap.Type.RigidBodyStateAttribute: [rigidBodyStateAttributeRotation] :: RigidBodyStateAttribute -> CompressedWordVector
+ Rattletrap.Type.RigidBodyStateAttribute: [rigidBodyStateAttributeRotation] :: RigidBodyStateAttribute -> Rotation

Files

library/Rattletrap/Data.hs view
@@ -1,4 +1,11 @@-module Rattletrap.Data where+module Rattletrap.Data+  ( rawParentClasses+  , rawClassesWithLocation+  , rawClassesWithRotation+  , rawObjectClasses+  , rawAttributeTypes+  , rawCrc32Table+  ) where  import Rattletrap.Type.AttributeType @@ -123,6 +130,7 @@   , ("Archetypes.GameEvent.GameEvent_Season:CarArchetype", "TAGame.Car_TA")   , ("Archetypes.GameEvent.GameEvent_Season", "TAGame.GameEvent_Season_TA")   , ("Archetypes.GameEvent.GameEvent_Soccar", "TAGame.GameEvent_Soccar_TA")+  , ("Archetypes.GameEvent.GameEvent_SoccarLan", "TAGame.GameEvent_Soccar_TA")   , ("Archetypes.GameEvent.GameEvent_SoccarPrivate", "TAGame.GameEvent_SoccarPrivate_TA")   , ("Archetypes.GameEvent.GameEvent_SoccarSplitscreen", "TAGame.GameEvent_SoccarSplitscreen_TA")   , ("Archetypes.SpecialPickups.SpecialPickup_BallFreeze", "TAGame.SpecialPickup_BallFreeze_TA")
library/Rattletrap/Decode/CompressedWordVector.hs view
@@ -6,12 +6,12 @@ import Rattletrap.Decode.CompressedWord import Rattletrap.Type.CompressedWordVector -decodeCompressedWordVectorBits :: (Int, Int, Int) -> DecodeBits CompressedWordVector-decodeCompressedWordVectorBits version =+decodeCompressedWordVectorBits :: DecodeBits CompressedWordVector+decodeCompressedWordVectorBits =   CompressedWordVector-    <$> decodeCompressedWordBits (limit version)-    <*> decodeCompressedWordBits (limit version)-    <*> decodeCompressedWordBits (limit version)+    <$> decodeCompressedWordBits limit+    <*> decodeCompressedWordBits limit+    <*> decodeCompressedWordBits limit -limit :: (Int, Int, Int) -> Word-limit version = if version >= (868, 22, 7) then 262144 else 65536+limit :: Word+limit = 65536
+ library/Rattletrap/Decode/Quaternion.hs view
@@ -0,0 +1,26 @@+module Rattletrap.Decode.Quaternion+  ( decodeQuaternionBits+  )+where++import Rattletrap.Decode.Common+import Rattletrap.Decode.CompressedWord+import Rattletrap.Type.CompressedWord+import Rattletrap.Type.Quaternion++decodeQuaternionBits :: DecodeBits Quaternion+decodeQuaternionBits =+  toQuaternion <$> decodeComponent <*> decodePart <*> decodePart <*> decodePart++decodeComponent :: DecodeBits Component+decodeComponent = do+  x <- decodeCompressedWordBits 3+  case compressedWordValue x of+    0 -> pure ComponentX+    1 -> pure ComponentY+    2 -> pure ComponentZ+    3 -> pure ComponentW+    y -> fail ("invalid component: " <> show y)++decodePart :: DecodeBits Double+decodePart = decompressPart <$> decodeCompressedWordBits maxCompressedValue
library/Rattletrap/Decode/Replay.hs view
@@ -30,8 +30,19 @@ getVersion header =   ( fromIntegral (word32leValue (headerEngineVersion header))   , fromIntegral (word32leValue (headerLicenseeVersion header))-  , maybe 0 (fromIntegral . word32leValue) (headerPatchVersion header)+  , getPatchVersion header   )++getPatchVersion :: Header -> Int+getPatchVersion header = case headerPatchVersion header of+  Just version -> fromIntegral (word32leValue version)+  Nothing ->+    case dictionaryLookup (toStr "MatchType") (headerProperties header) of+      -- This is an ugly, ugly hack to handle replays from season 2 of RLCS.+      -- See `decodeSpawnedReplicationBits` and #85.+      Just Property { propertyValue = PropertyValueName str }+        | fromStr str == "Lan" -> -1+      _ -> 0  getNumFrames :: Header -> Int getNumFrames header =
library/Rattletrap/Decode/RigidBodyStateAttribute.hs view
@@ -3,7 +3,7 @@   ) where  import Rattletrap.Decode.Common-import Rattletrap.Decode.CompressedWordVector+import Rattletrap.Decode.Rotation import Rattletrap.Decode.Vector import Rattletrap.Type.RigidBodyStateAttribute @@ -12,8 +12,6 @@   sleeping <- getBool   RigidBodyStateAttribute sleeping     <$> decodeVectorBits version-    <*> decodeWhen (version >= (868, 22, 7)) getBool-    <*> decodeCompressedWordVectorBits version-    <*> decodeWhen (version >= (868, 22, 7)) getBool+    <*> decodeRotationBits version     <*> decodeWhen (not sleeping) (decodeVectorBits version)     <*> decodeWhen (not sleeping) (decodeVectorBits version)
+ library/Rattletrap/Decode/Rotation.hs view
@@ -0,0 +1,14 @@+module Rattletrap.Decode.Rotation+  ( decodeRotationBits+  )+where++import Rattletrap.Decode.Common+import Rattletrap.Decode.CompressedWordVector+import Rattletrap.Decode.Quaternion+import Rattletrap.Type.Rotation++decodeRotationBits :: (Int, Int, Int) -> DecodeBits Rotation+decodeRotationBits version = if version >= (868, 22, 7)+  then RotationQuaternion <$> decodeQuaternionBits+  else RotationCompressedWordVector <$> decodeCompressedWordVectorBits
+ library/Rattletrap/Encode/Quaternion.hs view
@@ -0,0 +1,41 @@+module Rattletrap.Encode.Quaternion+  ( putQuaternion+  )+where++import Rattletrap.Encode.CompressedWord+import Rattletrap.Type.CompressedWord+import Rattletrap.Type.Quaternion++import qualified Data.Binary.Bits.Put as BinaryBits++putQuaternion :: Quaternion -> BinaryBits.BitPut ()+putQuaternion q = do+  let c = maxComponent q+  putComponent c+  case c of+    ComponentX -> putParts (quaternionY q) (quaternionZ q) (quaternionW q)+    ComponentY -> putParts (quaternionX q) (quaternionZ q) (quaternionW q)+    ComponentZ -> putParts (quaternionX q) (quaternionY q) (quaternionW q)+    ComponentW -> putParts (quaternionX q) (quaternionY q) (quaternionZ q)++putComponent :: Component -> BinaryBits.BitPut ()+putComponent component = putCompressedWord+  (CompressedWord+    3+    (case component of+      ComponentX -> 0+      ComponentY -> 1+      ComponentZ -> 2+      ComponentW -> 3+    )+  )++putParts :: Double -> Double -> Double -> BinaryBits.BitPut ()+putParts a b c = do+  putPart a+  putPart b+  putPart c++putPart :: Double -> BinaryBits.BitPut ()+putPart = putCompressedWord . compressPart
library/Rattletrap/Encode/RigidBodyStateAttribute.hs view
@@ -2,7 +2,7 @@   ( putRigidBodyStateAttribute   ) where -import Rattletrap.Encode.CompressedWordVector+import Rattletrap.Encode.Rotation import Rattletrap.Encode.Vector import Rattletrap.Type.RigidBodyStateAttribute @@ -12,10 +12,8 @@ putRigidBodyStateAttribute rigidBodyStateAttribute = do   BinaryBits.putBool (rigidBodyStateAttributeSleeping rigidBodyStateAttribute)   putVector (rigidBodyStateAttributeLocation rigidBodyStateAttribute)-  maybe (pure ()) BinaryBits.putBool (rigidBodyStateAttributeUnknown1 rigidBodyStateAttribute)-  putCompressedWordVector+  putRotation     (rigidBodyStateAttributeRotation rigidBodyStateAttribute)-  maybe (pure ()) BinaryBits.putBool (rigidBodyStateAttributeUnknown2 rigidBodyStateAttribute)   case rigidBodyStateAttributeLinearVelocity rigidBodyStateAttribute of     Nothing -> pure ()     Just linearVelocity -> putVector linearVelocity
+ library/Rattletrap/Encode/Rotation.hs view
@@ -0,0 +1,15 @@+module Rattletrap.Encode.Rotation+  ( putRotation+  )+where++import Rattletrap.Encode.CompressedWordVector+import Rattletrap.Encode.Quaternion+import Rattletrap.Type.Rotation++import qualified Data.Binary.Bits.Put as BinaryBits++putRotation :: Rotation -> BinaryBits.BitPut ()+putRotation r = case r of+  RotationCompressedWordVector cwv -> putCompressedWordVector cwv+  RotationQuaternion q -> putQuaternion q
+ library/Rattletrap/Type/Quaternion.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE TemplateHaskell #-}++module Rattletrap.Type.Quaternion+  ( Quaternion(..)+  , Component(..)+  , toQuaternion+  , toPart+  , compressPart+  , decompressPart+  , maxComponent+  , maxCompressedValue+  )+where++import Rattletrap.Type.Common+import Rattletrap.Type.CompressedWord++import qualified Data.Maybe as Maybe++data Quaternion = Quaternion+  { quaternionX :: Double+  , quaternionY :: Double+  , quaternionZ :: Double+  , quaternionW :: Double+  } deriving (Eq, Ord, Show)++$(deriveJson ''Quaternion)++data Component+  = ComponentX+  | ComponentY+  | ComponentZ+  | ComponentW+  deriving (Eq, Ord, Show)++toQuaternion :: Component -> Double -> Double -> Double -> Quaternion+toQuaternion component a b c =+  let d = toPart a b c+  in+    case component of+      ComponentX -> Quaternion d a b c+      ComponentY -> Quaternion a d b c+      ComponentZ -> Quaternion a b d c+      ComponentW -> Quaternion a b c d++toPart :: Double -> Double -> Double -> Double+toPart a b c = sqrt (1 - (a * a) - (b * b) - (c * c))++compressPart :: Double -> CompressedWord+compressPart =+  CompressedWord maxCompressedValue+    . round+    . (* wordToDouble maxCompressedValue)+    . (+ 0.5)+    . (/ 2.0)+    . (/ maxValue)++decompressPart :: CompressedWord -> Double+decompressPart x =+  (* maxValue)+    . (* 2.0)+    . subtract 0.5+    . (/ wordToDouble (compressedWordLimit x))+    . wordToDouble+    $ compressedWordValue x++maxComponent :: Quaternion -> Component+maxComponent quaternion+  = let+      x = quaternionX quaternion+      y = quaternionY quaternion+      z = quaternionZ quaternion+      w = quaternionW quaternion+      parts =+        [(x, ComponentX), (y, ComponentY), (z, ComponentZ), (w, ComponentW)]+      biggestPart = maximum parts+      roundTrip = decompressPart . compressPart+      computedPart = Maybe.fromMaybe+        biggestPart+        (Maybe.listToMaybe+          (filter (\(value, _) -> value /= roundTrip value) parts)+        )+    in snd+      (if (biggestPart == computedPart)+          || (abs (fst biggestPart - fst computedPart) > 0.00001)+        then biggestPart+        else computedPart+      )++numBits :: Word+numBits = 18++wordToDouble :: Word -> Double+wordToDouble = fromIntegral++maxCompressedValue :: Word+maxCompressedValue = (2 ^ numBits) - 1++maxValue :: Double+maxValue = 1.0 / sqrt 2.0
library/Rattletrap/Type/RigidBodyStateAttribute.hs view
@@ -5,15 +5,13 @@   ) where  import Rattletrap.Type.Common-import Rattletrap.Type.CompressedWordVector+import Rattletrap.Type.Rotation import Rattletrap.Type.Vector  data RigidBodyStateAttribute = RigidBodyStateAttribute   { rigidBodyStateAttributeSleeping :: Bool   , rigidBodyStateAttributeLocation :: Vector-  , rigidBodyStateAttributeUnknown1 :: Maybe Bool-  , rigidBodyStateAttributeRotation :: CompressedWordVector-  , rigidBodyStateAttributeUnknown2 :: Maybe Bool+  , rigidBodyStateAttributeRotation :: Rotation   , rigidBodyStateAttributeLinearVelocity :: Maybe Vector   , rigidBodyStateAttributeAngularVelocity :: Maybe Vector   } deriving (Eq, Ord, Show)
+ library/Rattletrap/Type/Rotation.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE TemplateHaskell #-}++module Rattletrap.Type.Rotation+  ( Rotation(..)+  )+where++import Rattletrap.Type.Common+import Rattletrap.Type.Quaternion+import Rattletrap.Type.CompressedWordVector++data Rotation+  = RotationCompressedWordVector CompressedWordVector+  | RotationQuaternion Quaternion+  deriving (Eq, Ord, Show)++$(deriveJson ''Rotation)
library/Rattletrap/Utility/Bytes.hs view
@@ -1,4 +1,9 @@-module Rattletrap.Utility.Bytes where+module Rattletrap.Utility.Bytes+  ( encodeLatin1+  , padBytes+  , reverseByte+  , reverseBytes+  ) where  import qualified Data.Bits as Bits import qualified Data.ByteString.Lazy as LazyBytes
package.yaml view
@@ -1,5 +1,5 @@ name: rattletrap-version: 4.1.2+version: 5.0.0  category: Game description: Rattletrap parses and generates Rocket League replays.@@ -31,7 +31,9 @@ ghc-options:   - -optP -Wno-nonportable-include-path # https://github.com/haskell/cabal/issues/4739   - -Weverything+  - -Wno-all-missed-specialisations   - -Wno-implicit-prelude+  - -Wno-missing-exported-signatures # https://ghc.haskell.org/trac/ghc/ticket/14794   - -Wno-missing-import-lists   - -Wno-safe   - -Wno-unsafe
rattletrap.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: f97575be8798337622664b2f229d5417b0e7e29fefe6a61109a65cb02abb3208+-- hash: 9ba2a9aa65fbdd16f1e3edc2524f922e6ffb9e794cd408c41397f03c5cb37ad8  name:           rattletrap-version:        4.1.2+version:        5.0.0 synopsis:       Parse and generate Rocket League replays. description:    Rattletrap parses and generates Rocket League replays. category:       Game@@ -85,6 +85,7 @@       Rattletrap.Decode.ProductAttribute       Rattletrap.Decode.Property       Rattletrap.Decode.PropertyValue+      Rattletrap.Decode.Quaternion       Rattletrap.Decode.QWordAttribute       Rattletrap.Decode.RemoteId       Rattletrap.Decode.Replay@@ -92,6 +93,7 @@       Rattletrap.Decode.ReplicationValue       Rattletrap.Decode.ReservationAttribute       Rattletrap.Decode.RigidBodyStateAttribute+      Rattletrap.Decode.Rotation       Rattletrap.Decode.Section       Rattletrap.Decode.SpawnedReplication       Rattletrap.Decode.StatEventAttribute@@ -155,6 +157,7 @@       Rattletrap.Encode.ProductAttribute       Rattletrap.Encode.Property       Rattletrap.Encode.PropertyValue+      Rattletrap.Encode.Quaternion       Rattletrap.Encode.QWordAttribute       Rattletrap.Encode.RemoteId       Rattletrap.Encode.Replay@@ -162,6 +165,7 @@       Rattletrap.Encode.ReplicationValue       Rattletrap.Encode.ReservationAttribute       Rattletrap.Encode.RigidBodyStateAttribute+      Rattletrap.Encode.Rotation       Rattletrap.Encode.Section       Rattletrap.Encode.SpawnedReplication       Rattletrap.Encode.StatEventAttribute@@ -228,6 +232,7 @@       Rattletrap.Type.ProductAttribute       Rattletrap.Type.Property       Rattletrap.Type.PropertyValue+      Rattletrap.Type.Quaternion       Rattletrap.Type.QWordAttribute       Rattletrap.Type.RemoteId       Rattletrap.Type.Replay@@ -235,6 +240,7 @@       Rattletrap.Type.ReplicationValue       Rattletrap.Type.ReservationAttribute       Rattletrap.Type.RigidBodyStateAttribute+      Rattletrap.Type.Rotation       Rattletrap.Type.Section       Rattletrap.Type.SpawnedReplication       Rattletrap.Type.StatEventAttribute@@ -256,7 +262,7 @@       Paths_rattletrap   hs-source-dirs:       library-  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -Wno-safe -Wno-unsafe+  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-safe -Wno-unsafe   build-depends:       aeson >=1.2.4 && <1.5     , aeson-pretty >=0.8.5 && <0.9@@ -279,7 +285,7 @@       Paths_rattletrap   hs-source-dirs:       executables-  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -Wno-safe -Wno-unsafe -rtsopts -threaded+  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-safe -Wno-unsafe -rtsopts -threaded   build-depends:       aeson >=1.2.4 && <1.5     , aeson-pretty >=0.8.5 && <0.9@@ -306,7 +312,7 @@       Paths_rattletrap   hs-source-dirs:       tests-  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -Wno-safe -Wno-unsafe -rtsopts -threaded+  ghc-options: -optP -Wno-nonportable-include-path -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-safe -Wno-unsafe -rtsopts -threaded   build-depends:       HUnit >=1.6.0 && <1.7     , aeson >=1.2.4 && <1.5
tests/Main.hs view
@@ -1,17 +1,26 @@-import Control.Exception (displayException, try)-import Control.Monad (unless)-import Data.ByteString.Lazy (readFile)-import Prelude hiding (readFile)-import Rattletrap.Console.Main (rattletrap)-import System.FilePath (addExtension, joinPath)-import System.IO.Temp (withSystemTempDirectory)-import Text.Printf (printf)+module Main+  ( main+  )+where +import qualified Control.Monad as Monad+import qualified Data.ByteString.Lazy as Bytes+import qualified Rattletrap.Console.Main as Rattletrap+import qualified System.Exit as Exit+import qualified System.FilePath as Path+import qualified System.IO.Temp as Temp import qualified Test.HUnit as Test -main :: IO Test.Counts-main = withSystemTempDirectory "rattletrap-" (Test.runTestTT . toTests)+main :: IO ()+main = Temp.withSystemTempDirectory "rattletrap-" (runTests . toTests) +runTests :: Test.Test -> IO ()+runTests test = do+  result <- Test.runTestTT test+  Monad.when+    (Test.errors result > 0 || Test.failures result > 0)+    Exit.exitFailure+ toTests :: FilePath -> Test.Test toTests directory = Test.TestList (fmap (toTest directory) replays) @@ -21,23 +30,23 @@   (Test.TestCase (toAssertion directory uuid))  toLabel :: String -> String -> String-toLabel = printf "%s: %s"+toLabel uuid name = uuid <> ": " <> name  toAssertion :: FilePath -> String -> Test.Assertion toAssertion directory uuid = do-  let inputFile = joinPath ["replays", addExtension uuid ".replay"]-  result <- try (readFile inputFile)-  case result of-    Left problem -> putStrLn (displayException (problem :: IOError))-    Right input -> do-      let jsonFile = joinPath [directory, addExtension uuid ".json"]-      rattletrap "" ["--compact", "--input", inputFile, "--output", jsonFile]-      let outputFile = joinPath [directory, addExtension uuid ".replay"]-      rattletrap "" ["--input", jsonFile, "--output", outputFile]-      output <- readFile outputFile-      unless-        (output == input)-        (Test.assertFailure "output does not match input")+  let+    inputFile = Path.joinPath ["replays", Path.addExtension uuid ".replay"]+    jsonFile = Path.joinPath [directory, Path.addExtension uuid ".json"]+    outputFile = Path.joinPath [directory, Path.addExtension uuid ".replay"]+  input <- Bytes.readFile inputFile+  Rattletrap.rattletrap+    ""+    ["--compact", "--input", inputFile, "--output", jsonFile]+  Rattletrap.rattletrap "" ["--input", jsonFile, "--output", outputFile]+  output <- Bytes.readFile outputFile+  Monad.unless+    (output == input)+    (Test.assertFailure "output does not match input")  replays :: [(String, String)] replays =@@ -64,11 +73,13 @@   , ("3381", "patch 1.37")   , ("372d", "a camera yaw attribute")   , ("387f", "a frozen attribute")+  , ("3abd", "rlcs")   , ("3ea1", "a custom team name")   , ("4126", "a game mode after Neo Tokyo")   , ("42f0", "reservations after Neo Tokyo")   , ("504e", "some messages")   , ("520e", "no pickup attribute")+  , ("524f", "quat edge case")   , ("52aa", "a match-ending attribute")   , ("540d", "a demolish attribute")   , ("551c", "private match settings")@@ -101,6 +112,7 @@   , ("c837", "a spectator")   , ("cc4c", "after Starbase ARC")   , ("d044", "hoops mutators")+  , ("d236", "rlcs s2")   , ("d428", "a private hockey match")   , ("d7fb", "an explosion attribute")   , ("db70", "new lag indicator")