diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -10,8 +10,8 @@
 can be used to modify replays in order to force everyone into the same car or
 change the map a game was played on.
 
-Rattletrap supports every version of Rocket League up to [2.21][], which was
-released on 2022-10-11. If a replay can be played by the Rocket League client,
+Rattletrap supports every version of Rocket League up to [2.23][], which was
+released on 2022-11-29. If a replay can be played by the Rocket League client,
 it can be parsed by Rattletrap. (If not, that's a bug. Please report it!)
 
 ## Install
@@ -136,7 +136,7 @@
 [Docker badge]: https://img.shields.io/docker/v/taylorfausak/rattletrap?label=docker&logo=docker&logoColor=white
 [docker]: https://hub.docker.com/r/taylorfausak/rattletrap
 [Rocket League]: https://www.rocketleague.com
-[2.21]: https://www.rocketleague.com/news/patch-notes-v2-21/
+[2.23]: https://www.rocketleague.com/news/patch-notes-v2-23/
 [Ball Chasing]: https://ballchasing.com
 [Haskell]: https://www.haskell.org
 [the latest release]: https://github.com/tfausak/rattletrap/releases/latest
diff --git a/rattletrap.cabal b/rattletrap.cabal
--- a/rattletrap.cabal
+++ b/rattletrap.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.2
 
 name: rattletrap
-version: 11.2.14
+version: 12.0.0
 synopsis: Parse and generate Rocket League replays.
 description: Rattletrap parses and generates Rocket League replays.
 
@@ -114,6 +114,7 @@
     Rattletrap.Type.Attribute.FlaggedInt
     Rattletrap.Type.Attribute.Float
     Rattletrap.Type.Attribute.GameMode
+    Rattletrap.Type.Attribute.GameServer
     Rattletrap.Type.Attribute.Int
     Rattletrap.Type.Attribute.Int64
     Rattletrap.Type.Attribute.Loadout
diff --git a/src/lib/Rattletrap/Console/Main.hs b/src/lib/Rattletrap/Console/Main.hs
--- a/src/lib/Rattletrap/Console/Main.hs
+++ b/src/lib/Rattletrap/Console/Main.hs
@@ -29,6 +29,7 @@
 import qualified Rattletrap.Type.Attribute.FlaggedInt as Attribute.FlaggedInt
 import qualified Rattletrap.Type.Attribute.Float as Attribute.Float
 import qualified Rattletrap.Type.Attribute.GameMode as Attribute.GameMode
+import qualified Rattletrap.Type.Attribute.GameServer as Attribute.GameServer
 import qualified Rattletrap.Type.Attribute.Int as Attribute.Int
 import qualified Rattletrap.Type.Attribute.Int64 as Attribute.Int64
 import qualified Rattletrap.Type.Attribute.Loadout as Attribute.Loadout
@@ -181,6 +182,7 @@
         , Attribute.FlaggedInt.schema
         , Attribute.Float.schema
         , Attribute.GameMode.schema
+        , Attribute.GameServer.schema
         , Attribute.Int.schema
         , Attribute.Int64.schema
         , Attribute.Loadout.schema
diff --git a/src/lib/Rattletrap/Data.hs b/src/lib/Rattletrap/Data.hs
--- a/src/lib/Rattletrap/Data.hs
+++ b/src/lib/Rattletrap/Data.hs
@@ -273,7 +273,7 @@
   , ("Engine.ReplicatedActor_ORS:ReplicatedOwner", AttributeType.FlaggedInt)
   , ("Engine.TeamInfo:Score", AttributeType.Int)
   , ("ProjectX.GRI_X:bGameStarted", AttributeType.Boolean)
-  , ("ProjectX.GRI_X:GameServerID", AttributeType.QWord)
+  , ("ProjectX.GRI_X:GameServerID", AttributeType.GameServer)
   , ("ProjectX.GRI_X:MatchGuid", AttributeType.String)
   , ("ProjectX.GRI_X:MatchGUID", AttributeType.String)
   , ("ProjectX.GRI_X:ReplicatedGameMutatorIndex", AttributeType.Int)
diff --git a/src/lib/Rattletrap/Type/Attribute.hs b/src/lib/Rattletrap/Type/Attribute.hs
--- a/src/lib/Rattletrap/Type/Attribute.hs
+++ b/src/lib/Rattletrap/Type/Attribute.hs
@@ -54,20 +54,23 @@
 
 bitGet
   :: Version.Version
+  -> Maybe Str.Str
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
   -> CompressedWord.CompressedWord
   -> BitGet.BitGet Attribute
-bitGet version classes actors actor = BitGet.label "Attribute" $ do
-  attributes <- lookupAttributeMap classes actors actor
-  limit <- lookupAttributeIdLimit attributes actor
-  id <- BitGet.label "id" $ CompressedWord.bitGet limit
-  name <- lookupAttributeName classes attributes id
-  value <- BitGet.label "value" $ AttributeValue.bitGet
-    version
-    (ClassAttributeMap.objectMap classes)
-    name
-  pure Attribute { id, name, value }
+bitGet version buildVersion classes actors actor =
+  BitGet.label "Attribute" $ do
+    attributes <- lookupAttributeMap classes actors actor
+    limit <- lookupAttributeIdLimit attributes actor
+    id <- BitGet.label "id" $ CompressedWord.bitGet limit
+    name <- lookupAttributeName classes attributes id
+    value <- BitGet.label "value" $ AttributeValue.bitGet
+      version
+      buildVersion
+      (ClassAttributeMap.objectMap classes)
+      name
+    pure Attribute { id, name, value }
 
 lookupAttributeMap
   :: ClassAttributeMap.ClassAttributeMap
diff --git a/src/lib/Rattletrap/Type/Attribute/GameServer.hs b/src/lib/Rattletrap/Type/Attribute/GameServer.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Rattletrap/Type/Attribute/GameServer.hs
@@ -0,0 +1,40 @@
+module Rattletrap.Type.Attribute.GameServer where
+
+import qualified Data.Foldable as Foldable
+import qualified Rattletrap.BitGet as BitGet
+import qualified Rattletrap.BitPut as BitPut
+import qualified Rattletrap.Schema as Schema
+import qualified Rattletrap.Type.Attribute.QWord as QWord
+import qualified Rattletrap.Type.Str as Str
+import qualified Rattletrap.Utility.Json as Json
+
+data GameServer
+  = Old QWord.QWord
+  | New Str.Str
+  deriving (Eq, Show)
+
+instance Json.FromJSON GameServer where
+  parseJSON = Json.withObject "GameServer" $ \x -> Foldable.asum
+    [fmap Old $ Json.required x "old", fmap New $ Json.required x "new"]
+
+instance Json.ToJSON GameServer where
+  toJSON x = case x of
+    Old y -> Json.object [Json.pair "old" $ Json.toJSON y]
+    New y -> Json.object [Json.pair "new" $ Json.toJSON y]
+
+schema :: Schema.Schema
+schema = Schema.named "attribute-game-server" . Schema.oneOf $ fmap
+  (\(k, v) -> Schema.object [(Json.pair k v, True)])
+  [("old", Schema.ref QWord.schema), ("new", Schema.ref Str.schema)]
+
+bitPut :: GameServer -> BitPut.BitPut
+bitPut x = case x of
+  Old y -> QWord.bitPut y
+  New y -> Str.bitPut y
+
+bitGet :: Maybe Str.Str -> BitGet.BitGet GameServer
+bitGet buildVersion =
+  BitGet.label "GameServer"
+    $ if buildVersion >= Just (Str.fromString "221120.42953.406184")
+        then BitGet.label "New" $ fmap New Str.bitGet
+        else BitGet.label "Old" $ fmap Old QWord.bitGet
diff --git a/src/lib/Rattletrap/Type/AttributeType.hs b/src/lib/Rattletrap/Type/AttributeType.hs
--- a/src/lib/Rattletrap/Type/AttributeType.hs
+++ b/src/lib/Rattletrap/Type/AttributeType.hs
@@ -16,6 +16,7 @@
   | FlaggedByte
   | Float
   | GameMode
+  | GameServer
   | Int
   | Int64
   | Loadout
diff --git a/src/lib/Rattletrap/Type/AttributeValue.hs b/src/lib/Rattletrap/Type/AttributeValue.hs
--- a/src/lib/Rattletrap/Type/AttributeValue.hs
+++ b/src/lib/Rattletrap/Type/AttributeValue.hs
@@ -22,6 +22,7 @@
 import qualified Rattletrap.Type.Attribute.FlaggedInt as FlaggedInt
 import qualified Rattletrap.Type.Attribute.Float as Float
 import qualified Rattletrap.Type.Attribute.GameMode as GameMode
+import qualified Rattletrap.Type.Attribute.GameServer as GameServer
 import qualified Rattletrap.Type.Attribute.Int as Int
 import qualified Rattletrap.Type.Attribute.Int64 as Int64
 import qualified Rattletrap.Type.Attribute.Loadout as Loadout
@@ -69,6 +70,7 @@
   | FlaggedByte FlaggedByte.FlaggedByte
   | Float Float.Float
   | GameMode GameMode.GameMode
+  | GameServer GameServer.GameServer
   | Int Int.Int
   | Int64 Int64.Int64
   | Loadout Loadout.Loadout
@@ -113,6 +115,7 @@
     , fmap FlaggedInt $ Json.required object "flagged_int"
     , fmap Float $ Json.required object "float"
     , fmap GameMode $ Json.required object "game_mode"
+    , fmap GameServer $ Json.required object "game_server"
     , fmap Int $ Json.required object "int"
     , fmap Int64 $ Json.required object "int64"
     , fmap Loadout $ Json.required object "loadout"
@@ -157,6 +160,7 @@
     FlaggedInt y -> Json.object [Json.pair "flagged_int" y]
     Float y -> Json.object [Json.pair "float" y]
     GameMode y -> Json.object [Json.pair "game_mode" y]
+    GameServer y -> Json.object [Json.pair "game_server" y]
     Int y -> Json.object [Json.pair "int" y]
     Int64 y -> Json.object [Json.pair "int64" y]
     Loadout y -> Json.object [Json.pair "loadout" y]
@@ -202,6 +206,7 @@
   , ("flagged_int", FlaggedInt.schema)
   , ("float", Float.schema)
   , ("game_mode", GameMode.schema)
+  , ("game_server", GameServer.schema)
   , ("int", Int.schema)
   , ("int64", Int64.schema)
   , ("loadout_online", LoadoutOnline.schema)
@@ -246,6 +251,7 @@
   FlaggedByte x -> FlaggedByte.bitPut x
   Float x -> Float.bitPut x
   GameMode x -> GameMode.bitPut x
+  GameServer x -> GameServer.bitPut x
   Int x -> Int.bitPut x
   Int64 x -> Int64.putInt64Attribute x
   Loadout x -> Loadout.bitPut x
@@ -274,60 +280,68 @@
 
 bitGet
   :: Version.Version
+  -> Maybe Str.Str
   -> Map.Map U32.U32 Str.Str
   -> Str.Str
   -> BitGet.BitGet AttributeValue
-bitGet version objectMap name = BitGet.label "AttributeValue" $ do
-  constructor <- case Map.lookup (Str.toText name) Data.attributeTypes of
-    Nothing ->
-      BitGet.throw . UnknownAttribute.UnknownAttribute $ Str.toString name
-    Just x -> pure x
-  case constructor of
-    AttributeType.AppliedDamage ->
-      fmap AppliedDamage $ AppliedDamage.bitGet version
-    AttributeType.Boolean -> fmap Boolean Boolean.bitGet
-    AttributeType.Byte -> fmap Byte Byte.bitGet
-    AttributeType.CamSettings -> fmap CamSettings $ CamSettings.bitGet version
-    AttributeType.ClubColors -> fmap ClubColors ClubColors.bitGet
-    AttributeType.CustomDemolish ->
-      fmap CustomDemolish $ CustomDemolish.bitGet version
-    AttributeType.DamageState -> fmap DamageState $ DamageState.bitGet version
-    AttributeType.Demolish -> fmap Demolish $ Demolish.bitGet version
-    AttributeType.Enum -> fmap Enum Enum.bitGet
-    AttributeType.Explosion -> fmap Explosion $ Explosion.bitGet version
-    AttributeType.ExtendedExplosion ->
-      fmap ExtendedExplosion $ ExtendedExplosion.bitGet version
-    AttributeType.FlaggedInt -> fmap FlaggedInt FlaggedInt.bitGet
-    AttributeType.FlaggedByte -> fmap FlaggedByte FlaggedByte.bitGet
-    AttributeType.Float -> fmap Float Float.bitGet
-    AttributeType.GameMode -> fmap GameMode $ GameMode.bitGet version
-    AttributeType.Int -> fmap Int Int.bitGet
-    AttributeType.Int64 -> fmap Int64 Int64.bitGet
-    AttributeType.Loadout -> fmap Loadout Loadout.bitGet
-    AttributeType.LoadoutOnline ->
-      fmap LoadoutOnline $ LoadoutOnline.bitGet version objectMap
-    AttributeType.Loadouts -> fmap Loadouts Loadouts.bitGet
-    AttributeType.LoadoutsOnline ->
-      fmap LoadoutsOnline $ LoadoutsOnline.bitGet version objectMap
-    AttributeType.Location -> fmap Location $ Location.bitGet version
-    AttributeType.MusicStinger -> fmap MusicStinger MusicStinger.bitGet
-    AttributeType.PartyLeader -> fmap PartyLeader $ PartyLeader.bitGet version
-    AttributeType.Pickup -> fmap Pickup Pickup.bitGet
-    AttributeType.PickupInfo -> fmap PickupInfo PickupInfo.bitGet
-    AttributeType.PickupNew -> fmap PickupNew PickupNew.bitGet
-    AttributeType.PlayerHistoryKey ->
-      fmap PlayerHistoryKey PlayerHistoryKey.bitGet
-    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
-    AttributeType.Title -> fmap Title Title.bitGet
-    AttributeType.UniqueId -> fmap UniqueId $ UniqueId.bitGet version
-    AttributeType.WeldedInfo -> fmap WeldedInfo $ WeldedInfo.bitGet version
+bitGet version buildVersion objectMap name =
+  BitGet.label "AttributeValue" $ do
+    constructor <- case Map.lookup (Str.toText name) Data.attributeTypes of
+      Nothing ->
+        BitGet.throw . UnknownAttribute.UnknownAttribute $ Str.toString name
+      Just x -> pure x
+    case constructor of
+      AttributeType.AppliedDamage ->
+        fmap AppliedDamage $ AppliedDamage.bitGet version
+      AttributeType.Boolean -> fmap Boolean Boolean.bitGet
+      AttributeType.Byte -> fmap Byte Byte.bitGet
+      AttributeType.CamSettings ->
+        fmap CamSettings $ CamSettings.bitGet version
+      AttributeType.ClubColors -> fmap ClubColors ClubColors.bitGet
+      AttributeType.CustomDemolish ->
+        fmap CustomDemolish $ CustomDemolish.bitGet version
+      AttributeType.DamageState ->
+        fmap DamageState $ DamageState.bitGet version
+      AttributeType.Demolish -> fmap Demolish $ Demolish.bitGet version
+      AttributeType.Enum -> fmap Enum Enum.bitGet
+      AttributeType.Explosion -> fmap Explosion $ Explosion.bitGet version
+      AttributeType.ExtendedExplosion ->
+        fmap ExtendedExplosion $ ExtendedExplosion.bitGet version
+      AttributeType.FlaggedInt -> fmap FlaggedInt FlaggedInt.bitGet
+      AttributeType.FlaggedByte -> fmap FlaggedByte FlaggedByte.bitGet
+      AttributeType.Float -> fmap Float Float.bitGet
+      AttributeType.GameMode -> fmap GameMode $ GameMode.bitGet version
+      AttributeType.GameServer ->
+        fmap GameServer $ GameServer.bitGet buildVersion
+      AttributeType.Int -> fmap Int Int.bitGet
+      AttributeType.Int64 -> fmap Int64 Int64.bitGet
+      AttributeType.Loadout -> fmap Loadout Loadout.bitGet
+      AttributeType.LoadoutOnline ->
+        fmap LoadoutOnline $ LoadoutOnline.bitGet version objectMap
+      AttributeType.Loadouts -> fmap Loadouts Loadouts.bitGet
+      AttributeType.LoadoutsOnline ->
+        fmap LoadoutsOnline $ LoadoutsOnline.bitGet version objectMap
+      AttributeType.Location -> fmap Location $ Location.bitGet version
+      AttributeType.MusicStinger -> fmap MusicStinger MusicStinger.bitGet
+      AttributeType.PartyLeader ->
+        fmap PartyLeader $ PartyLeader.bitGet version
+      AttributeType.Pickup -> fmap Pickup Pickup.bitGet
+      AttributeType.PickupInfo -> fmap PickupInfo PickupInfo.bitGet
+      AttributeType.PickupNew -> fmap PickupNew PickupNew.bitGet
+      AttributeType.PlayerHistoryKey ->
+        fmap PlayerHistoryKey PlayerHistoryKey.bitGet
+      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
+      AttributeType.Title -> fmap Title Title.bitGet
+      AttributeType.UniqueId -> fmap UniqueId $ UniqueId.bitGet version
+      AttributeType.WeldedInfo -> fmap WeldedInfo $ WeldedInfo.bitGet version
diff --git a/src/lib/Rattletrap/Type/Content.hs b/src/lib/Rattletrap/Type/Content.hs
--- a/src/lib/Rattletrap/Type/Content.hs
+++ b/src/lib/Rattletrap/Type/Content.hs
@@ -188,45 +188,49 @@
   -> Word
   -- ^ The maximum number of channels in the stream, usually from
   -- 'Rattletrap.Header.getMaxChannels'.
+  -> Maybe Str.Str
+  -- ^ 'Rattletrap.Header.getBuildVersion'
   -> ByteGet.ByteGet Content
-byteGet matchType version numFrames maxChannels = ByteGet.label "Content" $ do
-  levels <- ByteGet.label "levels" $ List.byteGet Str.byteGet
-  keyframes <- ByteGet.label "keyframes" $ List.byteGet Keyframe.byteGet
-  streamSize <- ByteGet.label "streamSize" U32.byteGet
-  stream <-
-    ByteGet.label "stream" . ByteGet.byteString . fromIntegral $ U32.toWord32
-      streamSize
-  messages <- ByteGet.label "messages" $ List.byteGet Message.byteGet
-  marks <- ByteGet.label "marks" $ List.byteGet Mark.byteGet
-  packages <- ByteGet.label "packages" $ List.byteGet Str.byteGet
-  objects <- ByteGet.label "objects" $ List.byteGet Str.byteGet
-  names <- ByteGet.label "names" $ List.byteGet Str.byteGet
-  classMappings <- ByteGet.label "classMappings"
-    $ List.byteGet ClassMapping.byteGet
-  caches <- ByteGet.label "caches" $ List.byteGet Cache.byteGet
-  let
-    classAttributeMap =
-      ClassAttributeMap.make objects classMappings caches names
-    getFrames = BitGet.toByteGet $ Frame.decodeFramesBits
-      matchType
-      version
-      numFrames
-      maxChannels
-      classAttributeMap
-  frames <- ByteGet.label "frames" $ ByteGet.embed getFrames stream
-  unknown <- ByteGet.label "unknown"
-    $ fmap LazyByteString.unpack ByteGet.remaining
-  pure Content
-    { levels
-    , keyframes
-    , streamSize
-    , frames
-    , messages
-    , marks
-    , packages
-    , objects
-    , names
-    , classMappings
-    , caches
-    , unknown
-    }
+byteGet matchType version numFrames maxChannels buildVersion =
+  ByteGet.label "Content" $ do
+    levels <- ByteGet.label "levels" $ List.byteGet Str.byteGet
+    keyframes <- ByteGet.label "keyframes" $ List.byteGet Keyframe.byteGet
+    streamSize <- ByteGet.label "streamSize" U32.byteGet
+    stream <-
+      ByteGet.label "stream" . ByteGet.byteString . fromIntegral $ U32.toWord32
+        streamSize
+    messages <- ByteGet.label "messages" $ List.byteGet Message.byteGet
+    marks <- ByteGet.label "marks" $ List.byteGet Mark.byteGet
+    packages <- ByteGet.label "packages" $ List.byteGet Str.byteGet
+    objects <- ByteGet.label "objects" $ List.byteGet Str.byteGet
+    names <- ByteGet.label "names" $ List.byteGet Str.byteGet
+    classMappings <- ByteGet.label "classMappings"
+      $ List.byteGet ClassMapping.byteGet
+    caches <- ByteGet.label "caches" $ List.byteGet Cache.byteGet
+    let
+      classAttributeMap =
+        ClassAttributeMap.make objects classMappings caches names
+      getFrames = BitGet.toByteGet $ Frame.decodeFramesBits
+        matchType
+        version
+        buildVersion
+        numFrames
+        maxChannels
+        classAttributeMap
+    frames <- ByteGet.label "frames" $ ByteGet.embed getFrames stream
+    unknown <- ByteGet.label "unknown"
+      $ fmap LazyByteString.unpack ByteGet.remaining
+    pure Content
+      { levels
+      , keyframes
+      , streamSize
+      , frames
+      , messages
+      , marks
+      , packages
+      , objects
+      , names
+      , classMappings
+      , caches
+      , unknown
+      }
diff --git a/src/lib/Rattletrap/Type/Frame.hs b/src/lib/Rattletrap/Type/Frame.hs
--- a/src/lib/Rattletrap/Type/Frame.hs
+++ b/src/lib/Rattletrap/Type/Frame.hs
@@ -59,14 +59,16 @@
 decodeFramesBits
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Int
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
   -> BitGet.BitGet (List.List Frame)
-decodeFramesBits matchType version count limit classes =
+decodeFramesBits matchType version buildVersion count limit classes =
   fmap snd $ decodeFramesBitsWith
     matchType
     version
+    buildVersion
     count
     limit
     classes
@@ -77,6 +79,7 @@
 decodeFramesBitsWith
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Int
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
@@ -84,19 +87,22 @@
   -> Int
   -> [Frame]
   -> BitGet.BitGet
-       ( Map.Map CompressedWord.CompressedWord U32.U32
+       ( Map.Map
+           CompressedWord.CompressedWord
+           U32.U32
        , List.List Frame
        )
-decodeFramesBitsWith matchType version count limit classes actorMap index frames
+decodeFramesBitsWith matchType version buildVersion count limit classes actorMap index frames
   = if index >= count
     then pure (actorMap, List.fromList $ reverse frames)
     else do
       (newActorMap, frame) <-
         BitGet.label ("element (" <> show index <> ")")
-          $ bitGet matchType version limit classes actorMap
+          $ bitGet matchType version buildVersion limit classes actorMap
       decodeFramesBitsWith
           matchType
           version
+          buildVersion
           count
           limit
           classes
@@ -108,19 +114,22 @@
 bitGet
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
   -> BitGet.BitGet
        (Map.Map CompressedWord.CompressedWord U32.U32, Frame)
-bitGet matchType version limit classes actorMap = BitGet.label "Frame" $ do
-  time <- BitGet.label "time" F32.bitGet
-  delta <- BitGet.label "delta" F32.bitGet
-  (newActorMap, replications) <-
-    BitGet.label "replications" $ Replication.decodeReplicationsBits
-      matchType
-      version
-      limit
-      classes
-      actorMap
-  pure (newActorMap, Frame { time, delta, replications })
+bitGet matchType version buildVersion limit classes actorMap =
+  BitGet.label "Frame" $ do
+    time <- BitGet.label "time" F32.bitGet
+    delta <- BitGet.label "delta" F32.bitGet
+    (newActorMap, replications) <-
+      BitGet.label "replications" $ Replication.decodeReplicationsBits
+        matchType
+        version
+        buildVersion
+        limit
+        classes
+        actorMap
+    pure (newActorMap, Frame { time, delta, replications })
diff --git a/src/lib/Rattletrap/Type/Replay.hs b/src/lib/Rattletrap/Type/Replay.hs
--- a/src/lib/Rattletrap/Type/Replay.hs
+++ b/src/lib/Rattletrap/Type/Replay.hs
@@ -10,6 +10,7 @@
 import qualified Rattletrap.Type.Property as Property
 import qualified Rattletrap.Type.Property.Int as Property.Int
 import qualified Rattletrap.Type.Property.Name as Property.Name
+import qualified Rattletrap.Type.Property.Str as Property.Str
 import qualified Rattletrap.Type.PropertyValue as PropertyValue
 import qualified Rattletrap.Type.Section as Section
 import qualified Rattletrap.Type.Str as Str
@@ -86,6 +87,7 @@
   (Header.version h)
   (getNumFrames h)
   (getMaxChannels h)
+  (getBuildVersion h)
 
 getMatchType :: Header.Header -> Maybe Str.Str
 getMatchType header = do
@@ -117,3 +119,11 @@
         Just (Property.Property _ _ (PropertyValue.Int maxChannels)) ->
           fromIntegral (I32.toInt32 (Property.Int.toI32 maxChannels))
         _ -> 1023
+
+getBuildVersion :: Header.Header -> Maybe Str.Str
+getBuildVersion header = do
+  property <- Dictionary.lookup (Str.fromString "BuildVersion")
+    $ Header.properties header
+  case Property.value property of
+    PropertyValue.Str x -> Just $ Property.Str.toStr x
+    _ -> Nothing
diff --git a/src/lib/Rattletrap/Type/Replication.hs b/src/lib/Rattletrap/Type/Replication.hs
--- a/src/lib/Rattletrap/Type/Replication.hs
+++ b/src/lib/Rattletrap/Type/Replication.hs
@@ -47,6 +47,7 @@
 decodeReplicationsBits
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
@@ -54,12 +55,21 @@
        ( Map.Map CompressedWord.CompressedWord U32.U32
        , List.List Replication
        )
-decodeReplicationsBits matchType version limit classes actorMap =
-  decodeReplicationsBitsWith matchType version limit classes actorMap 0 []
+decodeReplicationsBits matchType version buildVersion limit classes actorMap =
+  decodeReplicationsBitsWith
+    matchType
+    version
+    buildVersion
+    limit
+    classes
+    actorMap
+    0
+    []
 
 decodeReplicationsBitsWith
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
@@ -69,17 +79,18 @@
        ( Map.Map CompressedWord.CompressedWord U32.U32
        , List.List Replication
        )
-decodeReplicationsBitsWith matchType version limit classes actorMap index replications
+decodeReplicationsBitsWith matchType version buildVersion limit classes actorMap index replications
   = do
     hasReplication <- BitGet.bool
     if hasReplication
       then do
         (newActorMap, replication) <-
           BitGet.label ("element (" <> show index <> ")")
-            $ bitGet matchType version limit classes actorMap
+            $ bitGet matchType version buildVersion limit classes actorMap
         decodeReplicationsBitsWith
             matchType
             version
+            buildVersion
             limit
             classes
             newActorMap
@@ -91,6 +102,7 @@
 bitGet
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> Word
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
@@ -98,9 +110,14 @@
        ( Map.Map CompressedWord.CompressedWord U32.U32
        , Replication
        )
-bitGet matchType version limit classes actorMap =
+bitGet matchType version buildVersion limit classes actorMap =
   BitGet.label "Replication" $ do
     actorId <- BitGet.label "actorId" $ CompressedWord.bitGet limit
-    (newActorMap, value) <- BitGet.label "value"
-      $ ReplicationValue.bitGet matchType version classes actorId actorMap
+    (newActorMap, value) <- BitGet.label "value" $ ReplicationValue.bitGet
+      matchType
+      version
+      buildVersion
+      classes
+      actorId
+      actorMap
     pure (newActorMap, Replication { actorId, value })
diff --git a/src/lib/Rattletrap/Type/Replication/Updated.hs b/src/lib/Rattletrap/Type/Replication/Updated.hs
--- a/src/lib/Rattletrap/Type/Replication/Updated.hs
+++ b/src/lib/Rattletrap/Type/Replication/Updated.hs
@@ -8,6 +8,7 @@
 import qualified Rattletrap.Type.ClassAttributeMap as ClassAttributeMap
 import qualified Rattletrap.Type.CompressedWord as CompressedWord
 import qualified Rattletrap.Type.List as List
+import qualified Rattletrap.Type.Str as Str
 import qualified Rattletrap.Type.U32 as U32
 import qualified Rattletrap.Type.Version as Version
 import qualified Rattletrap.Utility.Json as Json
@@ -36,11 +37,13 @@
 
 bitGet
   :: Version.Version
+  -> Maybe Str.Str
   -> ClassAttributeMap.ClassAttributeMap
   -> Map.Map CompressedWord.CompressedWord U32.U32
   -> CompressedWord.CompressedWord
   -> BitGet.BitGet Updated
-bitGet version classes actors actor =
+bitGet version buildVersion classes actors actor =
   BitGet.label "Updated" . fmap Updated . List.untilM $ do
     p <- BitGet.bool
-    Monad.whenMaybe p $ Attribute.bitGet version classes actors actor
+    Monad.whenMaybe p
+      $ Attribute.bitGet version buildVersion classes actors actor
diff --git a/src/lib/Rattletrap/Type/ReplicationValue.hs b/src/lib/Rattletrap/Type/ReplicationValue.hs
--- a/src/lib/Rattletrap/Type/ReplicationValue.hs
+++ b/src/lib/Rattletrap/Type/ReplicationValue.hs
@@ -54,6 +54,7 @@
 bitGet
   :: Maybe Str.Str
   -> Version.Version
+  -> Maybe Str.Str
   -> ClassAttributeMap.ClassAttributeMap
   -> CompressedWord.CompressedWord
   -> Map.Map CompressedWord.CompressedWord U32.U32
@@ -61,7 +62,7 @@
        ( Map.Map CompressedWord.CompressedWord U32.U32
        , ReplicationValue
        )
-bitGet matchType version classAttributeMap actorId actorMap =
+bitGet matchType version buildVersion classAttributeMap actorId actorMap =
   BitGet.label "ReplicationValue" $ do
     isOpen <- BitGet.bool
     if isOpen
@@ -79,6 +80,7 @@
           else do
             updated <- Updated.bitGet
               version
+              buildVersion
               classAttributeMap
               actorMap
               actorId
diff --git a/src/test/Main.hs b/src/test/Main.hs
--- a/src/test/Main.hs
+++ b/src/test/Main.hs
@@ -99,6 +99,7 @@
   , ("540d", "a demolish attribute") -- https://github.com/tfausak/rattletrap/commit/65ce033
   , ("54ae", "replicated car scale") -- https://github.com/nickbabcock/boxcars/pull/79
   , ("551c", "private match settings") -- https://github.com/tfausak/rattletrap/commit/5c9ebfc
+  , ("59d3", "v2.23") -- https://github.com/tfausak/rattletrap/issues/274
   , ("5a06", "esports items") -- https://github.com/tfausak/rattletrap/pull/114
   , ("5e0b", "max channels") -- https://github.com/tfausak/rattletrap/issues/254
   , ("6210", "different player history key") -- https://github.com/tfausak/rattletrap/pull/63
