Skip to content

Commit

Permalink
Merge pull request #258 from tfausak/gh-256-boxcars
Browse files Browse the repository at this point in the history
Support replays from the boxcars test suite
  • Loading branch information
tfausak authored Mar 26, 2022
2 parents 4480ce0 + 57efb18 commit 0757a52
Show file tree
Hide file tree
Showing 23 changed files with 188 additions and 21 deletions.
3 changes: 3 additions & 0 deletions rattletrap.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ library
Rattletrap.Type.Attribute.Product
Rattletrap.Type.Attribute.ProductValue
Rattletrap.Type.Attribute.QWord
Rattletrap.Type.Attribute.RepStatTitle
Rattletrap.Type.Attribute.Reservation
Rattletrap.Type.Attribute.RigidBodyState
Rattletrap.Type.Attribute.Rotation
Rattletrap.Type.Attribute.StatEvent
Rattletrap.Type.Attribute.String
Rattletrap.Type.Attribute.TeamPaint
Expand Down Expand Up @@ -174,6 +176,7 @@ library
Rattletrap.Type.RemoteId.Epic
Rattletrap.Type.RemoteId.PlayStation
Rattletrap.Type.RemoteId.PsyNet
Rattletrap.Type.RemoteId.QQ
Rattletrap.Type.RemoteId.Splitscreen
Rattletrap.Type.RemoteId.Steam
Rattletrap.Type.RemoteId.Switch
Expand Down
Binary file added replays/0121.replay
Binary file not shown.
Binary file added replays/0ca5.replay
Binary file not shown.
Binary file added replays/43a9.replay
Binary file not shown.
Binary file added replays/5123.replay
Binary file not shown.
Binary file added replays/54ae.replay
Binary file not shown.
Binary file added replays/7588.replay
Binary file not shown.
Binary file added replays/ae46.replay
Binary file not shown.
Binary file added replays/c62c.replay
Binary file not shown.
Binary file added replays/d5d6.replay
Binary file not shown.
Binary file added replays/e2f9.replay
Binary file not shown.
4 changes: 4 additions & 0 deletions src/lib/Rattletrap/Console/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ import qualified Rattletrap.Type.Attribute.PrivateMatchSettings as Attribute.Pri
import qualified Rattletrap.Type.Attribute.Product as Attribute.Product
import qualified Rattletrap.Type.Attribute.ProductValue as Attribute.ProductValue
import qualified Rattletrap.Type.Attribute.QWord as Attribute.QWord
import qualified Rattletrap.Type.Attribute.RepStatTitle as Attribute.RepStatTitle
import qualified Rattletrap.Type.Attribute.Reservation as Attribute.Reservation
import qualified Rattletrap.Type.Attribute.RigidBodyState as Attribute.RigidBodyState
import qualified Rattletrap.Type.Attribute.Rotation as Attribute.Rotation
import qualified Rattletrap.Type.Attribute.StatEvent as Attribute.StatEvent
import qualified Rattletrap.Type.Attribute.String as Attribute.String
import qualified Rattletrap.Type.Attribute.TeamPaint as Attribute.TeamPaint
Expand Down Expand Up @@ -195,8 +197,10 @@ schema =
, Attribute.Product.schema
, Attribute.ProductValue.schema
, Attribute.QWord.schema
, Attribute.RepStatTitle.schema
, Attribute.Reservation.schema
, Attribute.RigidBodyState.schema
, Attribute.Rotation.schema
, Attribute.StatEvent.schema
, Attribute.String.schema
, Attribute.TeamPaint.schema
Expand Down
23 changes: 23 additions & 0 deletions src/lib/Rattletrap/Data.hs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ classesWithLocation = Set.fromList $ fmap
, "Archetypes.Ball.Ball_Breakout"
, "Archetypes.Ball.Ball_Default"
, "Archetypes.Ball.Ball_Puck"
, "Archetypes.Ball.Ball_Trajectory"
, "Archetypes.Ball.CubeBall"
, "Archetypes.Car.Car_Default"
, "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
Expand All @@ -90,6 +91,7 @@ classesWithLocation = Set.fromList $ fmap
, "TAGame.Ball_Haunted_TA"
, "TAGame.Ball_TA"
, "TAGame.CameraSettingsActor_TA"
, "TAGame.Cannon_TA"
, "TAGame.Car_Season_TA"
, "TAGame.Car_TA"
, "TAGame.CarComponent_Boost_TA"
Expand All @@ -105,6 +107,7 @@ classesWithLocation = Set.fromList $ fmap
, "TAGame.GameEvent_Soccar_TA"
, "TAGame.GameEvent_SoccarPrivate_TA"
, "TAGame.GameEvent_SoccarSplitscreen_TA"
, "TAGame.GameEvent_Tutorial_TA"
, "TAGame.GRI_TA"
, "TAGame.MaxTimeWarningData_TA"
, "TAGame.PickupTimer_TA"
Expand Down Expand Up @@ -141,6 +144,7 @@ classesWithRotation = Set.fromList $ fmap
, "Archetypes.Ball.Ball_Breakout"
, "Archetypes.Ball.Ball_Default"
, "Archetypes.Ball.Ball_Puck"
, "Archetypes.Ball.Ball_Trajectory"
, "Archetypes.Ball.CubeBall"
, "Archetypes.Car.Car_Default"
, "Archetypes.GameEvent.GameEvent_Season:CarArchetype"
Expand All @@ -167,6 +171,8 @@ objectClasses = Map.fromList $ fmap
, ("Archetypes.Ball.Ball_God", "TAGame.Ball_God_TA")
, ("Archetypes.Ball.Ball_Haunted", "TAGame.Ball_Haunted_TA")
, ("Archetypes.Ball.Ball_Puck", "TAGame.Ball_TA")
, ("Archetypes.Ball.Ball_Training", "TAGame.Ball_TA")
, ("Archetypes.Ball.Ball_Trajectory", "TAGame.Ball_TA")
, ("Archetypes.Ball.CubeBall", "TAGame.Ball_TA")
, ("Archetypes.Car.Car_Default", "TAGame.Car_TA")
, ("Archetypes.CarComponents.CarComponent_Boost", "TAGame.CarComponent_Boost_TA")
Expand Down Expand Up @@ -205,6 +211,7 @@ objectClasses = Map.fromList $ fmap
, ("Archetypes.SpecialPickups.SpecialPickup_Tornado", "TAGame.SpecialPickup_Tornado_TA")
, ("Archetypes.Teams.Team0", "TAGame.Team_Soccar_TA")
, ("Archetypes.Teams.Team1", "TAGame.Team_Soccar_TA")
, ("Archetypes.Tutorial.Cannon", "TAGame.Cannon_TA")
, ("GameInfo_Basketball.GameInfo.GameInfo_Basketball:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("GameInfo_Breakout.GameInfo.GameInfo_Breakout:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("GameInfo_FootBall.GameInfo.GameInfo_FootBall:Archetype", "TAGame.GameEvent_Football_TA")
Expand All @@ -217,6 +224,8 @@ objectClasses = Map.fromList $ fmap
, ("GameInfo_Items.GameInfo.GameInfo_Items:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("GameInfo_Season.GameInfo.GameInfo_Season:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("GameInfo_Soccar.GameInfo.GameInfo_Soccar:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("GameInfo_Tutorial.GameEvent.GameEvent_Tutorial_Aerial", "TAGame.GameEvent_Tutorial_TA")
, ("GameInfo_Tutorial.GameInfo.GameInfo_Tutorial:GameReplicationInfoArchetype", "TAGame.GRI_TA")
, ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_0", "TAGame.HauntedBallTrapTrigger_TA")
, ("Haunted_TrainStation_P.TheWorld:PersistentLevel.HauntedBallTrapTrigger_TA_1", "TAGame.HauntedBallTrapTrigger_TA")
, ("ProjectX.Default__NetModeReplicator_X", "ProjectX.NetModeReplicator")
Expand All @@ -238,12 +247,15 @@ attributeTypes = Map.fromList $ fmap
[ ("Engine.Actor:bBlockActors", AttributeType.Boolean)
, ("Engine.Actor:bCollideActors", AttributeType.Boolean)
, ("Engine.Actor:bHidden", AttributeType.Boolean)
, ("Engine.Actor:bTearOff", AttributeType.Boolean)
, ("Engine.Actor:DrawScale", AttributeType.Float)
, ("Engine.Actor:RemoteRole", AttributeType.Enum)
, ("Engine.Actor:Role", AttributeType.Enum)
, ("Engine.Actor:Rotation", AttributeType.Rotation)
, ("Engine.GameReplicationInfo:bMatchIsOver", AttributeType.Boolean)
, ("Engine.GameReplicationInfo:GameClass", AttributeType.FlaggedInt)
, ("Engine.GameReplicationInfo:ServerName", AttributeType.String)
, ("Engine.Pawn:HealthMax", AttributeType.Int)
, ("Engine.Pawn:PlayerReplicationInfo", AttributeType.FlaggedInt)
, ("Engine.PlayerReplicationInfo:bBot", AttributeType.Boolean)
, ("Engine.PlayerReplicationInfo:bIsSpectator", AttributeType.Boolean)
Expand Down Expand Up @@ -292,10 +304,13 @@ attributeTypes = Map.fromList $ fmap
, ("TAGame.CameraSettingsActor_TA:CameraYaw", AttributeType.Byte)
, ("TAGame.CameraSettingsActor_TA:PRI", AttributeType.FlaggedInt)
, ("TAGame.CameraSettingsActor_TA:ProfileSettings", AttributeType.CamSettings)
, ("TAGame.Cannon_TA:FireCount", AttributeType.Byte)
, ("TAGame.Cannon_TA:Pitch", AttributeType.Float)
, ("TAGame.Car_TA:AddedBallForceMultiplier", AttributeType.Float)
, ("TAGame.Car_TA:AddedCarForceMultiplier", AttributeType.Float)
, ("TAGame.Car_TA:AttachedPickup", AttributeType.FlaggedInt)
, ("TAGame.Car_TA:ClubColors", AttributeType.ClubColors)
, ("TAGame.Car_TA:ReplicatedCarScale", AttributeType.Float)
, ("TAGame.Car_TA:ReplicatedDemolish_CustomFX", AttributeType.CustomDemolish)
, ("TAGame.Car_TA:ReplicatedDemolish", AttributeType.Demolish)
, ("TAGame.Car_TA:ReplicatedDemolishGoalExplosion", AttributeType.CustomDemolish)
Expand Down Expand Up @@ -325,10 +340,15 @@ attributeTypes = Map.fromList $ fmap
, ("TAGame.CrowdManager_TA:ReplicatedGlobalOneShotSound", AttributeType.FlaggedInt)
, ("TAGame.GameEvent_Soccar_TA:bBallHasBeenHit", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:bClubMatch", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:bMatchEnded", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:bNoContest", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:bOverTime", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:bUnlimitedTime", AttributeType.Boolean)
, ("TAGame.GameEvent_Soccar_TA:GameTime", AttributeType.Int)
, ("TAGame.GameEvent_Soccar_TA:GameWinner", AttributeType.FlaggedInt)
, ("TAGame.GameEvent_Soccar_TA:MatchWinner", AttributeType.FlaggedInt)
, ("TAGame.GameEvent_Soccar_TA:MaxScore", AttributeType.Int)
, ("TAGame.GameEvent_Soccar_TA:MVP", AttributeType.FlaggedInt)
, ("TAGame.GameEvent_Soccar_TA:ReplicatedMusicStinger", AttributeType.MusicStinger)
, ("TAGame.GameEvent_Soccar_TA:ReplicatedScoredOnTeam", AttributeType.Byte)
, ("TAGame.GameEvent_Soccar_TA:ReplicatedServerPerformanceState", AttributeType.Byte)
Expand All @@ -338,6 +358,7 @@ attributeTypes = Map.fromList $ fmap
, ("TAGame.GameEvent_Soccar_TA:SeriesLength", AttributeType.Int)
, ("TAGame.GameEvent_Soccar_TA:SubRulesArchetype", AttributeType.FlaggedInt)
, ("TAGame.GameEvent_SoccarPrivate_TA:MatchSettings", AttributeType.PrivateMatchSettings)
, ("TAGame.GameEvent_TA:bAllowReadyUp", AttributeType.Boolean)
, ("TAGame.GameEvent_TA:bCanVoteToForfeit", AttributeType.Boolean)
, ("TAGame.GameEvent_TA:bHasLeaveMatchPenalty", AttributeType.Boolean)
, ("TAGame.GameEvent_TA:BotSkill", AttributeType.Int)
Expand Down Expand Up @@ -385,6 +406,7 @@ attributeTypes = Map.fromList $ fmap
, ("TAGame.PRI_TA:PrimaryTitle", AttributeType.Title)
, ("TAGame.PRI_TA:ReplicatedGameEvent", AttributeType.FlaggedInt)
, ("TAGame.PRI_TA:ReplicatedWorstNetQualityBeyondLatency", AttributeType.Byte)
, ("TAGame.PRI_TA:RepStatTitles", AttributeType.RepStatTitle)
, ("TAGame.PRI_TA:SecondaryTitle", AttributeType.Title)
, ("TAGame.PRI_TA:SkillTier", AttributeType.FlaggedByte)
, ("TAGame.PRI_TA:SpectatorShortcut", AttributeType.Int)
Expand Down Expand Up @@ -416,6 +438,7 @@ attributeTypes = Map.fromList $ fmap
, ("TAGame.Team_TA:GameEvent", AttributeType.FlaggedInt)
, ("TAGame.Team_TA:LogoData", AttributeType.FlaggedInt)
, ("TAGame.Vehicle_TA:bDriving", AttributeType.Boolean)
, ("TAGame.Vehicle_TA:bPodiumMode", AttributeType.Boolean)
, ("TAGame.Vehicle_TA:bReplicatedHandbrake", AttributeType.Boolean)
, ("TAGame.Vehicle_TA:ReplicatedSteer", AttributeType.Byte)
, ("TAGame.Vehicle_TA:ReplicatedThrottle", AttributeType.Byte)
Expand Down
56 changes: 56 additions & 0 deletions src/lib/Rattletrap/Type/Attribute/RepStatTitle.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Rattletrap.Type.Attribute.RepStatTitle where

import qualified Rattletrap.BitGet as BitGet
import qualified Rattletrap.BitPut as BitPut
import qualified Rattletrap.Schema as Schema
import qualified Rattletrap.Type.Attribute.FlaggedInt as FlaggedInt
import qualified Rattletrap.Type.Str as Str
import qualified Rattletrap.Type.U32 as U32
import qualified Rattletrap.Utility.Json as Json

data RepStatTitle = RepStatTitle
{ unknown :: Bool
, name :: Str.Str
, target :: FlaggedInt.FlaggedInt
, value :: U32.U32
}
deriving (Eq, Show)

instance Json.FromJSON RepStatTitle where
parseJSON = Json.withObject "RepStatTitle" $ \object -> do
unknown <- Json.required object "unknown"
name <- Json.required object "name"
target <- Json.required object "target"
value <- Json.required object "value"
pure RepStatTitle { unknown, name, target, value }

instance Json.ToJSON RepStatTitle where
toJSON x = Json.object
[ Json.pair "unknown" $ unknown x
, Json.pair "name" $ name x
, Json.pair "target" $ target x
, Json.pair "value" $ value x
]

schema :: Schema.Schema
schema = Schema.named "attribute-rep-stat-title" $ Schema.object
[ (Json.pair "unknown" $ Schema.ref Schema.boolean, True)
, (Json.pair "name" $ Schema.ref Str.schema, True)
, (Json.pair "target" $ Schema.ref FlaggedInt.schema, True)
, (Json.pair "value" $ Schema.ref U32.schema, True)
]

bitPut :: RepStatTitle -> BitPut.BitPut
bitPut x =
BitPut.bool (unknown x)
<> Str.bitPut (name x)
<> FlaggedInt.bitPut (target x)
<> U32.bitPut (value x)

bitGet :: BitGet.BitGet RepStatTitle
bitGet = BitGet.label "RepStatTitle" $ do
unknown <- BitGet.label "unknown" BitGet.bool
name <- BitGet.label "name" Str.bitGet
target <- BitGet.label "target" FlaggedInt.bitGet
value <- BitGet.label "value" U32.bitGet
pure RepStatTitle { unknown, name, target, value }
28 changes: 28 additions & 0 deletions src/lib/Rattletrap/Type/Attribute/Rotation.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Rattletrap.Type.Attribute.Rotation where

import qualified Rattletrap.BitGet as BitGet
import qualified Rattletrap.BitPut as BitPut
import qualified Rattletrap.Schema as Schema
import qualified Rattletrap.Type.Int8Vector as Int8Vector
import qualified Rattletrap.Utility.Json as Json

newtype Rotation = Rotation
{ value :: Int8Vector.Int8Vector
} deriving (Eq, Show)

instance Json.FromJSON Rotation where
parseJSON = fmap Rotation . Json.parseJSON

instance Json.ToJSON Rotation where
toJSON = Json.toJSON . value

schema :: Schema.Schema
schema = Schema.named "attribute-rotation" $ Schema.ref Int8Vector.schema

bitPut :: Rotation -> BitPut.BitPut
bitPut = Int8Vector.bitPut . value

bitGet :: BitGet.BitGet Rotation
bitGet = BitGet.label "Rotation" $ do
value <- BitGet.label "value" Int8Vector.bitGet
pure Rotation { value }
2 changes: 2 additions & 0 deletions src/lib/Rattletrap/Type/AttributeType.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ data AttributeType
| PlayerHistoryKey
| PrivateMatchSettings
| QWord
| RepStatTitle
| Reservation
| RigidBodyState
| Rotation
| StatEvent
| String
| TeamPaint
Expand Down
14 changes: 14 additions & 0 deletions src/lib/Rattletrap/Type/AttributeValue.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import qualified Rattletrap.Type.Attribute.PickupNew as PickupNew
import qualified Rattletrap.Type.Attribute.PlayerHistoryKey as PlayerHistoryKey
import qualified Rattletrap.Type.Attribute.PrivateMatchSettings as PrivateMatchSettings
import qualified Rattletrap.Type.Attribute.QWord as QWord
import qualified Rattletrap.Type.Attribute.RepStatTitle as RepStatTitle
import qualified Rattletrap.Type.Attribute.Reservation as Reservation
import qualified Rattletrap.Type.Attribute.RigidBodyState as RigidBodyState
import qualified Rattletrap.Type.Attribute.Rotation as Rotation
import qualified Rattletrap.Type.Attribute.StatEvent as StatEvent
import qualified Rattletrap.Type.Attribute.String as String
import qualified Rattletrap.Type.Attribute.TeamPaint as TeamPaint
Expand Down Expand Up @@ -82,8 +84,10 @@ data AttributeValue
| PlayerHistoryKey PlayerHistoryKey.PlayerHistoryKey
| PrivateMatchSettings PrivateMatchSettings.PrivateMatchSettings
| QWord QWord.QWord
| RepStatTitle RepStatTitle.RepStatTitle
| Reservation Reservation.Reservation
| RigidBodyState RigidBodyState.RigidBodyState
| Rotation Rotation.Rotation
| StatEvent StatEvent.StatEvent
| String String.String
| TeamPaint TeamPaint.TeamPaint
Expand Down Expand Up @@ -124,8 +128,10 @@ instance Json.FromJSON AttributeValue where
, fmap PlayerHistoryKey $ Json.required object "player_history_key"
, fmap PrivateMatchSettings $ Json.required object "private_match_settings"
, fmap QWord $ Json.required object "q_word"
, fmap RepStatTitle $ Json.required object "rep_stat_title"
, fmap Reservation $ Json.required object "reservation"
, fmap RigidBodyState $ Json.required object "rigid_body_state"
, fmap Rotation $ Json.required object "rotation"
, fmap StatEvent $ Json.required object "stat_event"
, fmap String $ Json.required object "string"
, fmap TeamPaint $ Json.required object "team_paint"
Expand Down Expand Up @@ -167,8 +173,10 @@ instance Json.ToJSON AttributeValue where
PrivateMatchSettings y ->
Json.object [Json.pair "private_match_settings" y]
QWord y -> Json.object [Json.pair "q_word" y]
RepStatTitle y -> Json.object [Json.pair "rep_stat_title" y]
Reservation y -> Json.object [Json.pair "reservation" y]
RigidBodyState y -> Json.object [Json.pair "rigid_body_state" y]
Rotation y -> Json.object [Json.pair "rotation" y]
StatEvent y -> Json.object [Json.pair "stat_event" y]
String y -> Json.object [Json.pair "string" y]
TeamPaint y -> Json.object [Json.pair "team_paint" y]
Expand Down Expand Up @@ -209,8 +217,10 @@ schema = Schema.named "attribute-value" . Schema.oneOf $ fmap
, ("player_history_key", PlayerHistoryKey.schema)
, ("private_match_settings", PrivateMatchSettings.schema)
, ("q_word", QWord.schema)
, ("rep_stat_title", RepStatTitle.schema)
, ("reservation", Reservation.schema)
, ("rigid_body_state", RigidBodyState.schema)
, ("rotation", Rotation.schema)
, ("stat_event", StatEvent.schema)
, ("string", String.schema)
, ("team_paint", TeamPaint.schema)
Expand Down Expand Up @@ -251,8 +261,10 @@ bitPut value = case value of
PlayerHistoryKey x -> PlayerHistoryKey.bitPut x
PrivateMatchSettings x -> PrivateMatchSettings.bitPut x
QWord x -> QWord.bitPut x
RepStatTitle x -> RepStatTitle.bitPut x
Reservation x -> Reservation.bitPut x
RigidBodyState x -> RigidBodyState.bitPut x
Rotation x -> Rotation.bitPut x
StatEvent x -> StatEvent.bitPut x
String x -> String.bitPut x
TeamPaint x -> TeamPaint.bitPut x
Expand Down Expand Up @@ -308,9 +320,11 @@ bitGet version objectMap name = BitGet.label "AttributeValue" $ do
AttributeType.PrivateMatchSettings ->
fmap PrivateMatchSettings PrivateMatchSettings.bitGet
AttributeType.QWord -> fmap QWord QWord.bitGet
AttributeType.RepStatTitle -> fmap RepStatTitle RepStatTitle.bitGet
AttributeType.Reservation -> fmap Reservation $ Reservation.bitGet version
AttributeType.RigidBodyState ->
fmap RigidBodyState $ RigidBodyState.bitGet version
AttributeType.Rotation -> fmap Rotation Rotation.bitGet
AttributeType.StatEvent -> fmap StatEvent StatEvent.bitGet
AttributeType.String -> fmap String String.bitGet
AttributeType.TeamPaint -> fmap TeamPaint TeamPaint.bitGet
Expand Down
8 changes: 5 additions & 3 deletions src/lib/Rattletrap/Type/Property/Byte.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ bytePut byte = Str.bytePut (key byte) <> foldMap Str.bytePut (value byte)
byteGet :: ByteGet.ByteGet Byte
byteGet = ByteGet.label "Byte" $ do
key <- ByteGet.label "key" Str.byteGet
value <- ByteGet.label "value" $ Monad.whenMaybe
(Str.toString key /= "OnlinePlatform_Steam")
Str.byteGet
let
isSteam = key == Str.fromString "OnlinePlatform_Steam"
isPlayStation = key == Str.fromString "OnlinePlatform_PS4"
value <- ByteGet.label "value"
$ Monad.whenMaybe (not $ isSteam || isPlayStation) Str.byteGet
pure Byte { key, value }
Loading

0 comments on commit 0757a52

Please sign in to comment.