-- The packet decoder Remote Spy downloads for "Capture via packets". -- Other files: ?file=client ?file=prelude ?file=tree ?file=decompile ?file=decompile_one ?file=inspect ?file=scanner -- Privacy and security: /docs/netbridge/getting-started/privacy --[[ NetBridge packet decoder: Remote Spy over RakNet. Reads remote calls straight out of Roblox's replication packets (0x83, ID_DATA), so FireServer / InvokeServer / OnClientEvent / OnClientInvoke traffic is seen without hooking a single Luau function. Maintained directly in this repository. The network schema and instance lookup are passed in through deps. Fallback enum names resolve on first use, so a removed enum cannot stop the file loading. The entry point returns parsed calls without rewriting packets. services/client/netbridge.lua fetches this from /netbridge/rakspy.lua only when "Capture via packets" is on in Remote Spy. Returns function(deps) -> { parse = function(buffer, isResponse) -> calls } deps.schema raknet.get_network_schema() when the executor has it deps.lookup function(peerId, instanceId) -> Instance? ]] return function(deps) local Codec = (function() local Codec = {} -- Network CFrame specials use the same sparse rot_id bytes as rbx-dom/attributes local CFRAME_SPECIALS: { [number]: { number } } = { [0x02] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }, [0x03] = { 1, 0, 0, 0, 0, -1, 0, 1, 0 }, [0x05] = { 1, 0, 0, 0, -1, 0, 0, 0, -1 }, [0x06] = { 1, 0, 0, 0, 0, 1, 0, -1, 0 }, [0x07] = { 0, 1, 0, 1, 0, 0, 0, 0, -1 }, [0x09] = { 0, 0, 1, 1, 0, 0, 0, 1, 0 }, [0x0A] = { 0, -1, 0, 1, 0, 0, 0, 0, 1 }, [0x0C] = { 0, 0, -1, 1, 0, 0, 0, -1, 0 }, [0x0D] = { 0, 1, 0, 0, 0, 1, 1, 0, 0 }, [0x0E] = { 0, 0, -1, 0, 1, 0, 1, 0, 0 }, [0x10] = { 0, -1, 0, 0, 0, -1, 1, 0, 0 }, [0x11] = { 0, 0, 1, 0, -1, 0, 1, 0, 0 }, [0x14] = { -1, 0, 0, 0, 1, 0, 0, 0, -1 }, [0x15] = { -1, 0, 0, 0, 0, 1, 0, 1, 0 }, [0x17] = { -1, 0, 0, 0, -1, 0, 0, 0, 1 }, [0x18] = { -1, 0, 0, 0, 0, -1, 0, -1, 0 }, [0x19] = { 0, 1, 0, -1, 0, 0, 0, 0, 1 }, [0x1B] = { 0, 0, -1, -1, 0, 0, 0, 1, 0 }, [0x1C] = { 0, -1, 0, -1, 0, 0, 0, 0, -1 }, [0x1E] = { 0, 0, 1, -1, 0, 0, 0, -1, 0 }, [0x1F] = { 0, 1, 0, 0, 0, -1, -1, 0, 0 }, [0x20] = { 0, 0, 1, 0, 1, 0, -1, 0, 0 }, [0x22] = { 0, -1, 0, 0, 0, 1, -1, 0, 0 }, [0x23] = { 0, 0, -1, 0, -1, 0, -1, 0, 0 }, } function Codec.LEB128(data: buffer, offset: number): (number, number) local result = 0 local shift = 0 for i = offset, offset + 8 do local byte = buffer.readu8(data, i) result = bit32.bor(result, bit32.lshift(bit32.band(byte, 0x7F), shift)) shift += 7 if bit32.band(byte, 0x80) == 0 then return result, i + 1 end end return result, offset + 9 end -- Unsigned LEB128 via number arithmetic (safe past 32-bit). function Codec.LEB128U(data: buffer, offset: number): (number, number) local result = 0 local mult = 1 for i = offset, offset + 9 do local byte = buffer.readu8(data, i) result += (byte % 128) * mult if byte < 128 then return result, i + 1 end mult *= 128 end return result, offset + 10 end -- Zigzag signed-int decode function Codec.ZzDec(n: number): number if n % 2 == 1 then return -(n // 2) - 1 else return n // 2 end end type NullRef = { is_null: true } type ValidRef = { is_null: false, peer_id: number, instance_id: number } export type InstanceRef = NullRef | ValidRef function Codec.ReadInstanceRef(data: buffer, offset: number): (InstanceRef, number) local peer_id, next_offset = Codec.LEB128(data, offset) if peer_id == 0 then return { is_null = true }, next_offset end local instance_id = buffer.readu32(data, next_offset) return { is_null = false, peer_id = peer_id, instance_id = instance_id, }, next_offset + 4 end local function makeCFrame(x: number, y: number, z: number, rot: { number }): CFrame return CFrame.new(x, y, z, rot[1], rot[2], rot[3], rot[4], rot[5], rot[6], rot[7], rot[8], rot[9]) end -- Custom network rotation: 48-bit smallest-three quaternion. -- Layout: c0:15 | pad:1 | c1:15 | c2:15 | maxIndex:2 -- Components are scaled by 0x3FFF*√2 and packed in XZY order relative to the -- ascending list of non-omitted axes. maxIndex is which of X,Y,Z,W was dropped. local ROT_SCALE = 0x3FFF * math.sqrt(2) local function readBits48( b0: number, b1: number, b2: number, b3: number, b4: number, b5: number, start: number, len: number ): number local bytes = { b0, b1, b2, b3, b4, b5 } local v = 0 for i = 0, len - 1 do local bitIndex = start + i local byteIndex = bitIndex // 8 + 1 local bitInByte = bitIndex % 8 if bit32.band(bytes[byteIndex], bit32.lshift(1, bitInByte)) ~= 0 then v += 2 ^ i end end return v end local function signExtend15(v: number): number if v >= 0x4000 then return v - 0x8000 end return v end function Codec.ReadCompressedRotation(data: buffer, offset: number): (number, number, number, number, number) local b0 = buffer.readu8(data, offset) local b1 = buffer.readu8(data, offset + 1) local b2 = buffer.readu8(data, offset + 2) local b3 = buffer.readu8(data, offset + 3) local b4 = buffer.readu8(data, offset + 4) local b5 = buffer.readu8(data, offset + 5) local c0 = signExtend15(readBits48(b0, b1, b2, b3, b4, b5, 0, 15)) local c1 = signExtend15(readBits48(b0, b1, b2, b3, b4, b5, 16, 15)) local c2 = signExtend15(readBits48(b0, b1, b2, b3, b4, b5, 31, 15)) local maxIndex = readBits48(b0, b1, b2, b3, b4, b5, 46, 2) -- Wire order is XZY relative to ascending non-omitted axes → undo to XYZ-order fill local ordered = { c0 / ROT_SCALE, c2 / ROT_SCALE, c1 / ROT_SCALE } local q = table.create(4) local vi = 1 for i = 0, 3 do if i == maxIndex then q[i + 1] = 0 else q[i + 1] = ordered[vi] vi += 1 end end local sumsq = 0 for i = 1, 4 do if i ~= maxIndex + 1 then sumsq += q[i] * q[i] end end q[maxIndex + 1] = math.sqrt(math.max(0, 1 - sumsq)) return q[1], q[2], q[3], q[4], offset + 6 end -- 0x1A / 0x1B: pos(3×f32) + rot_id(1b) [+ optional 6-byte quat] function Codec.ReadCFrameV1(data: buffer, offset: number): (CFrame, number) local x = buffer.readf32(data, offset) local y = buffer.readf32(data, offset + 4) local z = buffer.readf32(data, offset + 8) offset += 12 local rid = buffer.readu8(data, offset) offset += 1 if rid == 0 then local qx, qy, qz, qw qx, qy, qz, qw, offset = Codec.ReadCompressedRotation(data, offset) return CFrame.new(x, y, z, qx, qy, qz, qw), offset end local special = CFRAME_SPECIALS[rid] if special then return makeCFrame(x, y, z, special), offset end return CFrame.new(x, y, z), offset end -- 0x32 / 0x33: flags(1b); 0=nil; else rot (+ optional 6-byte quat) then pos(3×f32) function Codec.ReadOptCFrame(data: buffer, offset: number): (CFrame?, number) local fb = buffer.readu8(data, offset) offset += 1 if fb == 0 then return nil, offset end local rid = bit32.band(fb, 0x7F) local x: number, y: number, z: number if rid == 0 then local qx, qy, qz, qw qx, qy, qz, qw, offset = Codec.ReadCompressedRotation(data, offset) x = buffer.readf32(data, offset) y = buffer.readf32(data, offset + 4) z = buffer.readf32(data, offset + 8) return CFrame.new(x, y, z, qx, qy, qz, qw), offset + 12 end local rot = CFRAME_SPECIALS[rid] or { 1, 0, 0, 0, 1, 0, 0, 0, 1 } x = buffer.readf32(data, offset) y = buffer.readf32(data, offset + 4) z = buffer.readf32(data, offset + 8) return makeCFrame(x, y, z, rot), offset + 12 end function Codec.ReadUInt64(data: buffer, offset: number): (number, number) local lo = buffer.readu32(data, offset) local hi = buffer.readu32(data, offset + 4) return lo + hi * 0x100000000, offset + 8 end return Codec end)() local Constants = (function() local NetworkSchema = deps.schema or {} local NetworkTypeIds = NetworkSchema.type_ids local function ResolveType(Name: string, Fallback: number): number if NetworkTypeIds then local TypeId = NetworkTypeIds[Name] return TypeId or Fallback end return Fallback end -- Wire: u16(enumTypeId) + LEB128(value). -- typeId is an engine reflection id (NOT Enum:GetEnums() order). local TYPE_ID_TO_ENUM: { [number]: any } = { [0] = "AccessModifierType", [1] = "AccessoryType", [2] = "ActionType", [3] = "ActivePayerStatus", [4] = "ActuatorRelativeTo", [5] = "ActuatorType", [6] = "AdAvailabilityResult", [7] = "AdEventType", [8] = "AdFormat", [9] = "AdShape", [10] = "AdTeleportMethod", [11] = "AdUIEventType", [12] = "AdUIType", [13] = "AdUnitStatus", [14] = "AdornCullingMode", [15] = "AdornShading", [16] = "AgeCheckStatus", [17] = "AlignType", [18] = "AlphaMode", [19] = "AnalyticsCustomFieldKeys", [20] = "AnalyticsEconomyAction", [21] = "AnalyticsEconomyFlowType", [22] = "AnalyticsEconomyTransactionType", [23] = "AnalyticsLogLevel", [24] = "AnalyticsProgressionStatus", [25] = "AnalyticsProgressionType", [26] = "AnimationClipFromVideoStatus", [27] = "AnimationNodeBlend2DInputMode", [28] = "AnimationNodeInterruptible", [29] = "AnimationNodePhaseSync", [30] = "AnimationNodePlayMode", [31] = "AnimationNodeTransitionType", [32] = "AnimationNodeType", [33] = "AnimationNodeWaitFor", [34] = "AnimationPriority", [35] = "AnimatorRetargetingMode", [41] = "AntiAliasing", [42] = "AppLifecycleManagerState", [43] = "AppShellActionType", [44] = "AppShellFeature", [45] = "AppUpdateStatus", [46] = "ApplyStrokeMode", [47] = "AspectType", [48] = "AssetCreatorType", [49] = "AssetFetchStatus", [50] = "AssetRepresentation", [51] = "AssetType", [52] = "AssetTypeVerification", [53] = "AudioApiRollout", [55] = "AudioChannelLayout", [56] = "AudioFilterType", [57] = "AudioSimulationFidelity", [58] = "AudioSubType", [59] = "AudioWindowSize", [60] = "AuthorityMode", [61] = "AutomaticSize", [62] = "AvatarAssetType", [63] = "AvatarChatServiceFeature", [64] = "AvatarContextMenuOption", [65] = "AvatarGenerationError", [66] = "AvatarItemType", [67] = "AvatarPromptResult", [68] = "AvatarSettingsAccessoryLimitMethod", [69] = "AvatarSettingsAccessoryMode", [70] = "AvatarSettingsAnimationClipsMode", [71] = "AvatarSettingsAnimationPacksMode", [72] = "AvatarSettingsAppearanceMode", [73] = "AvatarSettingsBuildMode", [74] = "AvatarSettingsCharacterControllerMode", [75] = "AvatarSettingsClothingMode", [76] = "AvatarSettingsCollisionMode", [77] = "AvatarSettingsCustomAccessoryMode", [78] = "AvatarSettingsCustomBodyType", [79] = "AvatarSettingsCustomClothingMode", [80] = "AvatarSettingsHitAndTouchDetectionMode", [81] = "AvatarSettingsJumpMode", [82] = "AvatarSettingsLegacyCollisionMode", [83] = "AvatarSettingsScaleMode", [84] = "AvatarThumbnailCustomizationType", [85] = "AvatarUnificationMode", [86] = "Axis", [87] = "BenefitType", [88] = "BinType", [89] = "BodyPart", [90] = "BodyPartR15", [91] = "BorderMode", [92] = "BorderStrokePosition", [93] = "BreakReason", [94] = "BulkMoveMode", [95] = "BundleType", [96] = "Button", [97] = "ButtonStyle", [98] = "CageType", [99] = "CameraMode", [100] = "CameraPanMode", [101] = "CameraType", [103] = "CaptureGalleryPermission", [104] = "CaptureType", [105] = "CatalogCategoryFilter", [106] = "CatalogSortAggregation", [107] = "CatalogSortType", [108] = "CellBlock", [109] = "CellMaterial", [110] = "CellOrientation", [111] = "CenterDialogType", [112] = "CharacterControlMode", [113] = "ChatCallbackType", [114] = "ChatColor", [115] = "ChatMode", [116] = "ChatPrivacyMode", [117] = "ChatRestrictionStatus", [118] = "ChatStyle", [119] = "ChatVersion", [120] = "ClientAnimatorThrottlingMode", [121] = "CloseReason", [123] = "CollisionFidelity", [124] = "CommandPermission", [125] = "CompositeValueCurveType", [126] = "CompressionAlgorithm", [127] = "ComputerCameraMovementMode", [128] = "ComputerMovementMode", [129] = "ConfigSnapshotErrorState", [130] = "ConnectionError", [131] = "ConnectionState", [132] = "ContentSourceType", [133] = "ContextActionPriority", [134] = "ContextActionResult", [135] = "ControlMode", [136] = "CoreGuiType", [137] = "CreateAssetResult", [138] = "CreateContentResult", [139] = "CreateOutfitFailure", [140] = "CreatorType", [141] = "CreatorTypeFilter", [142] = "CurrencyType", [143] = "CustomCameraMode", [144] = "DataModelExtractorFileType", [145] = "DataStoreRequestType", [146] = "DebugBreakModeType", [147] = "DebuggerResumeType", [148] = "DevCameraOcclusionMode", [149] = "DevComputerCameraMovementMode", [150] = "DevComputerMovementMode", [151] = "DevTouchCameraMovementMode", [152] = "DevTouchMovementMode", [153] = "DeveloperMemoryTag", [154] = "DeviceFeatureType", [155] = "DeviceForm", [156] = "DeviceLevel", [157] = "DeviceType", [158] = "DialogBehaviorType", [159] = "DialogPurpose", [160] = "DialogTone", [161] = "DigitsRigDescriptionSide", [162] = "DiscountType", [163] = "DisplayScalingMode", [164] = "DisplaySize", [165] = "DomainType", [166] = "DominantAxis", [167] = "DragDetectorDragStyle", [168] = "DragDetectorPermissionPolicy", [169] = "DragDetectorResponseStyle", [170] = "DraggingScrollBar", [171] = "EasingDirection", [172] = "EasingStyle", [173] = "EditableStatus", [174] = "ElasticBehavior", [175] = "EmitterPositionType", [176] = "EngagementLevel", [177] = "EngineFolder", [178] = "EnviromentalPhysicsThrottle", [179] = "ExperienceActivationStatus", [180] = "ExperienceAuthScope", [181] = "ExperienceEventStatus", [182] = "ExperienceStateCaptureSelectionMode", [183] = "ExperienceStateRecordingLoadMode", [184] = "ExperienceStateRecordingLoadSourceType", [185] = "ExperienceStateRecordingPlaybackMode", [186] = "ExplosionType", [187] = "FACSDataLod", [188] = "FacialAgeEstimationResultType", [189] = "FacialAnimationStreamingState", [190] = "FacsActionUnit", [191] = "FeatureRestrictionAbuseVector", [192] = "FeedbackType", [193] = "FieldOfViewMode", [194] = "FillDirection", [195] = "FilterResult", [196] = "FilterType", [197] = "FinishRecordingOperation", [198] = "FluidFidelity", [199] = "FluidForces", [200] = "Font", [201] = "FontSize", [202] = "FontStyle", [203] = "FontWeight", [204] = "ForceLimitMode", [205] = "FormFactor", [206] = "FrameStyle", [207] = "FriendRequestEvent", [208] = "FriendStatus", [209] = "FunctionalTestResult", [210] = "GameAvatarType", [211] = "GamepadType", [212] = "GearGenreSetting", [213] = "GearType", [214] = "Genre", [215] = "GradientTileMode", [216] = "GradientType", [217] = "GraphicsOptimizationMode", [218] = "GroupMembershipStatus", [219] = "GuiState", [220] = "GuiType", [221] = "HandlesStyle", [222] = "HapticEffectType", [223] = "HashAlgorithm", [224] = "HighlightDepthMode", [225] = "HorizontalAlignment", [226] = "HttpCachePolicy", [227] = "HttpCompression", [228] = "HttpContentType", [229] = "HttpError", [230] = "HttpRequestType", [231] = "HumanoidCollisionType", [232] = "HumanoidDisplayDistanceType", [233] = "HumanoidHealthDisplayType", [234] = "HumanoidRigType", [235] = "HumanoidStateType", [236] = "IKCollisionsMode", [237] = "IKControlConstraintSupport", [238] = "IKControlType", [239] = "IXPLoadingStatus", [240] = "ImageAlphaType", [241] = "ImageCombineType", [242] = "InOut", [243] = "InfoType", [244] = "InitialDockState", [245] = "InputActionType", [246] = "InputBindingType", [247] = "InputSink", [248] = "InputType", [249] = "InstanceFileSyncStatus", [250] = "IntermediateMeshGenerationResult", [251] = "InternalVideoUsage", [252] = "InterpolationThrottlingMode", [253] = "InviteState", [254] = "ItemLineAlignment", [255] = "JoinSource", [256] = "JointCreationMode", [257] = "KeyCode", [258] = "KeyCodeStringFormat", [259] = "KeyInterpolationMode", [260] = "KeywordFilterType", [261] = "Language", [262] = "LeftRight", [263] = "LightingStyle", [264] = "Limb", [265] = "LineJoinMode", [266] = "ListenerLocation", [267] = "ListenerPositionType", [268] = "ListenerType", [271] = "LoadCharacterLayeredClothing", [272] = "LoadDynamicHeads", [273] = "LocationType", [274] = "LuauTypeCheckMode", [275] = "MakeupType", [276] = "MarketplaceBulkPurchasePromptStatus", [277] = "MarketplaceItemPurchaseStatus", [278] = "MarketplaceProductType", [279] = "MatchmakingType", [280] = "Material", [281] = "MaterialPattern", [282] = "MembershipType", [283] = "MeshPartHeadsAndAccessories", [285] = "MeshType", [286] = "MessageType", [287] = "ModelLevelOfDetail", [288] = "ModelStreamingBehavior", [289] = "ModelStreamingMode", [290] = "ModerationResultCategory", [291] = "ModerationResultLabel", [292] = "ModerationStatus", [293] = "ModifierKey", [294] = "MouseBehavior", [295] = "MoveState", [296] = "MuteState", [297] = "NameOcclusion", [298] = "NegateOperationHiddenHistory", [299] = "NetworkOwnership", [300] = "NetworkStatus", [301] = "NoiseType", [302] = "NormalId", [303] = "NotificationButtonType", [304] = "OperationType", [305] = "OrientationAlignmentMode", [306] = "OutfitSource", [307] = "OutfitType", [308] = "OverrideMouseIconBehavior", [309] = "PackagePermission", [310] = "PartType", [311] = "ParticleEmitterShape", [312] = "ParticleEmitterShapeInOut", [313] = "ParticleEmitterShapeStyle", [314] = "ParticleFlipbookLayout", [315] = "ParticleFlipbookMode", [316] = "ParticleFlipbookTextureCompatible", [317] = "ParticleOrientation", [318] = "PathStatus", [319] = "PathWaypointAction", [320] = "PathfindingUseImprovedSearch", [321] = "PeoplePageLayout", [323] = "PhysicsSimulationRate", [324] = "PhysicsSteppingMethod", [327] = "Platform", [328] = "PlaybackState", [329] = "PlayerActions", [330] = "PlayerCharacterDestroyBehavior", [331] = "PlayerChatType", [332] = "PlayerDataErrorState", [333] = "PlayerDataLoadFailureBehavior", [334] = "PlayerExitReason", [335] = "PlayerPlatformActivationStatus", [336] = "PlayerPlatformSpenderStatus", [337] = "PoseEasingDirection", [338] = "PoseEasingStyle", [339] = "PositionAlignmentMode", [340] = "PredictionMode", [341] = "PredictionStatus", [342] = "PreferredInput", [343] = "PreferredTextSize", [344] = "PrimalPhysicsSolver", [345] = "PrimitiveType", [346] = "PrivilegeType", [347] = "ProductLocationRestriction", [348] = "ProductPurchaseChannel", [349] = "ProductPurchaseDecision", [350] = "PromptCreateAssetResult", [351] = "PromptCreateAvatarResult", [352] = "PromptExperienceDetailsResult", [353] = "PromptLinkSharingResult", [354] = "PromptPublishAssetResult", [355] = "PropertyStatus", [356] = "ProximityPromptExclusivity", [357] = "ProximityPromptInputType", [358] = "ProximityPromptStyle", [359] = "PurchaseOption", [360] = "R15CollisionType", [361] = "RaycastFilterType", [362] = "ReadCapturesFromGalleryResult", [363] = "ReceiptDecision", [364] = "ReceiptType", [365] = "RecommendationActionType", [366] = "RecommendationDepartureIntent", [367] = "RecommendationImpressionType", [368] = "RecommendationItemContentType", [369] = "RecommendationItemVisibility", [370] = "RecommendationPreferenceTargetType", [371] = "RecommendationPreferenceType", [372] = "RejectCharacterDeletions", [373] = "RenderFidelity", [374] = "RenderPriority", [375] = "RenderingCacheOptimizationMode", [376] = "RenderingTestComparisonMethod", [377] = "ReplicateInstanceDestroySetting", [378] = "ResamplerMode", [379] = "ReservedHighlightId", [381] = "ReturnKeyType", [382] = "ReverbType", [383] = "ReviewableContentState", [384] = "RibbonTool", [385] = "RigLabel", [388] = "RollOffMode", [389] = "RolloutState", [390] = "RotationOrder", [391] = "RotationType", [392] = "RsvpStatus", [393] = "RtlTextSupport", [394] = "RunContext", [395] = "RunState", [396] = "RuntimeUndoBehavior", [397] = "SafeAreaCompatibility", [398] = "SalesTypeFilter", [399] = "SandboxedInstanceMode", [400] = "SaveAvatarThumbnailCustomizationFailure", [401] = "SaveFilter", [402] = "SavedQualitySetting", [403] = "ScaleType", [404] = "ScopeCheckResult", [405] = "ScreenInsets", [406] = "ScreenOrientation", [407] = "ScreenshotCaptureResult", [408] = "ScriptStoppedReason", [409] = "ScriptVariableScope", [410] = "ScrollBarInset", [411] = "ScrollingDirection", [412] = "SecurityCapability", [413] = "SelectionBehavior", [414] = "SelectionRenderMode", [415] = "SelfViewPosition", [416] = "SensorMode", [417] = "SensorUpdateType", [419] = "ShowAdResult", [420] = "SignalBehavior", [421] = "SimulationMode", [422] = "SizeConstraint", [423] = "SlimTintMode", -- [424] = "SolidPrimitiveType", [425] = "SolverConvergenceMetricType", [426] = "SolverConvergenceVisualizationMode", [427] = "SortDirection", [428] = "SortOrder", [429] = "SpecialKey", [430] = "StartCorner", [431] = "StateObjectFieldType", [432] = "Status", [433] = "StepFrequency", [434] = "StreamOutBehavior", [435] = "StreamingIntegrityMode", [436] = "StreamingPauseMode", [437] = "StrokeSizingMode", [438] = "StudioAction", [439] = "StudioCaptureScreenshotFormat", [440] = "StudioCloseMode", [441] = "StudioDataModelType", [442] = "StudioPlaceUpdateFailureReason", [443] = "Style", [444] = "SubscriptionExpirationReason", [445] = "SubscriptionPaymentStatus", [446] = "SubscriptionPeriod", [447] = "SubscriptionState", [448] = "SurfaceConstraint", [449] = "SurfaceGuiShape", [450] = "SurfaceGuiSizingMode", [451] = "SurfaceType", [452] = "SwipeDirection", [453] = "SystemThemeValue", [454] = "TableMajorAxis", [457] = "Technology", [458] = "TelemetryBackend", [459] = "TelemetryStandardizedField", [460] = "TeleportMethod", [461] = "TeleportResult", [462] = "TeleportState", [463] = "TeleportType", [464] = "TerrainAcquisitionMethod", [465] = "TerrainFace", [466] = "TerrainLiquidMergeOperation", [467] = "TerrainSolidMergeOperation", [468] = "TextChatMessageStatus", [469] = "TextDirection", [470] = "TextFilterContext", [471] = "TextInputType", [472] = "TextTruncate", [473] = "TextXAlignment", [474] = "TextYAlignment", [475] = "TextureMode", [476] = "TextureQueryType", [477] = "ThreadPoolConfig", [478] = "ThrottlingPriority", [479] = "ThumbnailSize", [480] = "ThumbnailType", [481] = "TickCountSampleMethod", [482] = "TonemapperPreset", [483] = "TopBottom", [484] = "TouchCameraMovementMode", [485] = "TouchMovementMode", [486] = "TrackerError", [487] = "TrackerExtrapolationFlagMode", [488] = "TrackerFaceTrackingStatus", [489] = "TrackerLodFlagMode", [490] = "TrackerLodValueMode", [491] = "TrackerMode", [492] = "TrackerPromptEvent", [493] = "TrackerType", [494] = "TriStateBoolean", [495] = "TweenStatus", [496] = "UICaptureMode", [497] = "UIDragDetectorBoundingBehavior", [498] = "UIDragDetectorDragRelativity", [499] = "UIDragDetectorDragSpace", [500] = "UIDragDetectorDragStyle", [501] = "UIDragDetectorResponseStyle", [502] = "UIDragSpeedAxisMapping", [503] = "UIFlexAlignment", [504] = "UIFlexMode", [505] = "UiMessageType", [506] = "UpdateState", [507] = "UploadCaptureResult", [508] = "UsageContext", [509] = "UserCFrame", [510] = "UserInputState", [511] = "UserInputType", [512] = "VRComfortSetting", [513] = "VRControllerModelMode", [514] = "VRDeviceType", [515] = "VRLaserPointerMode", [516] = "VRSafetyBubbleMode", [517] = "VRScaling", [518] = "VRSessionState", [519] = "VRTouchpad", [520] = "VRTouchpadMode", [521] = "VelocityConstraintMode", [522] = "VerticalAlignment", [523] = "VerticalScrollBarPosition", [524] = "VibrationMotor", [525] = "VideoCaptureResult", [526] = "VideoCaptureStartedResult", [527] = "VideoDeviceCaptureQuality", [528] = "VideoError", [529] = "VideoSampleSize", [530] = "VirtualCursorMode", [531] = "VoiceChatDistanceAttenuationType", [532] = "VoiceChatState", [533] = "VoiceClientLeaveReasons", [534] = "VoiceControlPath", [535] = "VoiceRccReconnectReason", [536] = "VolumetricAudio", [537] = "WaterDirection", [538] = "WaterForce", [539] = "WebSocketState", [540] = "WebStreamClientState", [541] = "WebStreamClientType", [542] = "WeldConstraintPreserve", [543] = "WhenUserFirstPlayed", [544] = "WhisperChatPrivacyMode", [545] = "WrapLayerAutoSkin", [546] = "WrapLayerDebugMode", [547] = "WrapTargetDebugMode", [548] = "ZIndexBehavior", } local VariantTypes = { Nil = 0x00, String = ResolveType("string", 0x02), NilAlt = 0x03, InstanceLegacy = 0x06, Enum = ResolveType("Enum", 0x07), BinaryString = ResolveType("BinaryString", 0x08), Bool = ResolveType("bool", 0x09), Int = ResolveType("int", 0x0A), Float = ResolveType("float", 0x0B), Double = ResolveType("double", 0x0C), UDim = ResolveType("UDim", 0x0D), UDim2 = ResolveType("UDim2", 0x0E), Ray = ResolveType("Ray", 0x0F), Faces = ResolveType("Faces", 0x10), Axes = ResolveType("Axes", 0x11), BrickColor = ResolveType("BrickColor", 0x12), Color3 = ResolveType("Color3", 0x13), Color3uint8 = ResolveType("Color3uint8", 0x14), Vector2 = ResolveType("Vector2", 0x15), Vector3 = ResolveType("Vector3", 0x16), Vector2int16 = ResolveType("Vector2int16", 0x18), Vector3int16 = ResolveType("Vector3int16", 0x19), CoordinateFrameLegacy = 0x1A, CoordinateFrame = ResolveType("CoordinateFrame", 0x1B), Instance = ResolveType("Instance", 0x1C), Tuple = ResolveType("Tuple", 0x1D), Array = ResolveType("Array", 0x1E), Dictionary = ResolveType("Dictionary", 0x1F), Map = ResolveType("Map", 0x20), ContentId = ResolveType("ContentId", 0x21), SystemAddress = ResolveType("SystemAddress", 0x22), NumberSequence = ResolveType("NumberSequence", 0x23), NumberSequenceKeypoint = ResolveType("NumberSequenceKeypoint", 0x24), NumberRange = ResolveType("NumberRange", 0x25), ColorSequence = ResolveType("ColorSequence", 0x26), ColorSequenceKeypoint = ResolveType("ColorSequenceKeypoint", 0x27), Rect2D = ResolveType("Rect2D", 0x28), PhysicalProperties = ResolveType("PhysicalProperties", 0x29), Region3 = ResolveType("Region3", 0x2A), Region3int16 = ResolveType("Region3int16", 0x2B), Int64 = ResolveType("int64", 0x2C), SharedString = ResolveType("SharedString", 0x2E), InstanceAlt = 0x2F, DateTime = ResolveType("DateTime", 0x30), FontLegacy = 0x31, OptionalCoordinateFrameLegacy = 0x32, OptionalCoordinateFrame = ResolveType("OptionalCoordinateFrame", 0x33), UniqueId = ResolveType("UniqueId", 0x34), PathWaypoint = ResolveType("PathWaypoint", 0x35), Font = ResolveType("Font", 0x36), SecurityCapabilities = ResolveType("SecurityCapabilities", 0x37), Buffer = ResolveType("buffer", 0x38), ReplicationPV = ResolveType("ReplicationPV", 0x39), FacsReplicationData = ResolveType("FacsReplicationData", 0x3A), NetAssetHandle = ResolveType("NetAssetHandle", 0x3B), NetAssetRef = ResolveType("NetAssetRef", 0x3C), Content = ResolveType("Content", 0x3D), SlimReplicationData = ResolveType("SlimReplicationData", 0x3F), AnimTrackVariant = 0x40, AnimTrackPlayState = ResolveType("AnimTrackPlayState", 0x41), AnimTrackMetadata = ResolveType("AnimTrackMetadata", 0x42), AnimTrackWeight = ResolveType("AnimTrackWeight", 0x43), } local Constants = { NetworkSchema = NetworkSchema, PacketIds = { ID_DATA = 0x83, }, ItemTypes = { ReliableEventInvocationItem = 0x07, UnreliableEventInvocationItem = 0x03, }, VariantTypes = VariantTypes, } Constants.EnumTypes = TYPE_ID_TO_ENUM local EnumNames = NetworkSchema.enum_names function Constants.GetEnum(EnumTypeId: number): Enum? local Success, EnumType = pcall(function() local EnumName = EnumNames[EnumTypeId] return Enum[EnumName] end) if not Success or not EnumType then local Fallback = TYPE_ID_TO_ENUM[EnumTypeId] if type(Fallback) == "string" then local Ok, Resolved = pcall(function() return Enum[Fallback] end) Fallback = Ok and Resolved or nil TYPE_ID_TO_ENUM[EnumTypeId] = Fallback end return Fallback end return EnumType end return Constants end)() local VariantTypes = Constants.VariantTypes local Lookup = { Instance = { ByRef = function(ref) if ref.is_null then return nil end return deps.lookup(ref.peer_id, ref.instance_id) end, }, } local DecoderModules = {} -- Decoder: animTrack DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local ANIM_TRACK_VARIANT_TYPE = VariantTypes.AnimTrackVariant local ANIM_TRACK_PLAY_STATE_TYPE = VariantTypes.AnimTrackPlayState local ANIM_TRACK_METADATA_TYPE = VariantTypes.AnimTrackMetadata local ANIM_TRACK_WEIGHT_TYPE = VariantTypes.AnimTrackWeight local DecoderModule = { Type = { ANIM_TRACK_VARIANT_TYPE, ANIM_TRACK_PLAY_STATE_TYPE, ANIM_TRACK_METADATA_TYPE, ANIM_TRACK_WEIGHT_TYPE, }, } local Router = { -- Speculative: zigzag LEB128 (play-state enum-ish) [ANIM_TRACK_VARIANT_TYPE] = function(data: buffer, offset: number) local n, newOffset = Codec.LEB128(data, offset) return { Result = { animTrackVariant = Codec.ZzDec(n) }, Offset = newOffset, } end, -- Speculative: flags(u8) + f32 + f64 = 13 bytes [ANIM_TRACK_PLAY_STATE_TYPE] = function(data: buffer, offset: number) return { Result = { flags = buffer.readu8(data, offset), f32 = buffer.readf32(data, offset + 1), f64 = buffer.readf64(data, offset + 5), }, Offset = offset + 13, } end, -- Speculative: u8 + i16 = 3 bytes [ANIM_TRACK_METADATA_TYPE] = function(data: buffer, offset: number) return { Result = { byte = buffer.readu8(data, offset), i16 = buffer.readi16(data, offset + 1), }, Offset = offset + 3, } end, -- Speculative: f32 + f32 + f64 = 16 bytes [ANIM_TRACK_WEIGHT_TYPE] = function(data: buffer, offset: number) return { Result = { f0 = buffer.readf32(data, offset), f1 = buffer.readf32(data, offset + 4), d = buffer.readf64(data, offset + 8), }, Offset = offset + 16, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: axes DecoderModules[#DecoderModules + 1] = (function() local AXIS_VALUES = { Enum.Axis.X, Enum.Axis.Y, Enum.Axis.Z, } local DecoderModule = { Type = VariantTypes.Axes, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local mask = buffer.readu32(data, offset) local axes = {} for i, axis in AXIS_VALUES do if bit32.band(mask, bit32.lshift(1, i - 1)) ~= 0 then table.insert(axes, axis) end end return { Result = Axes.new(table.unpack(axes)), Offset = offset + 4, } end return DecoderModule end)() -- Decoder: bool DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.Bool, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) return { Result = buffer.readu8(data, offset) ~= 0, Offset = offset + 1, } end return DecoderModule end)() -- Decoder: brickColor DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.BrickColor, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local number = buffer.readu16(data, offset) return { Result = BrickColor.new(number), Offset = offset + 2, } end return DecoderModule end)() -- Decoder: buffer DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.Buffer, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) -- u8(flags/reserved) + LEB128(len) + bytes offset += 1 local length, newOffset = Codec.LEB128(data, offset) local result = buffer.create(length) buffer.copy(result, 0, data, newOffset, length) return { Result = result, Offset = newOffset + length, } end return DecoderModule end)() -- Decoder: cframe DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local CFRAME_V1_TYPE = VariantTypes.CoordinateFrameLegacy local CFRAME_TYPE = VariantTypes.CoordinateFrame local DecoderModule = { Type = { CFRAME_V1_TYPE, CFRAME_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local cf, newOffset = Codec.ReadCFrameV1(data, offset) return { Result = cf, Offset = newOffset, } end return DecoderModule end)() -- Decoder: color3 DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local COLOR3_TYPE = VariantTypes.Color3 local COLOR3UINT8_TYPE = VariantTypes.Color3uint8 local DecoderModule = { Type = { COLOR3_TYPE, COLOR3UINT8_TYPE }, } local Router = { [COLOR3_TYPE] = function(data: buffer, offset: number) local r = buffer.readf32(data, offset) local g = buffer.readf32(data, offset + 4) local b = buffer.readf32(data, offset + 8) return { Result = Color3.new(r, g, b), Offset = offset + 12, } end, [COLOR3UINT8_TYPE] = function(data: buffer, offset: number) local r = buffer.readu8(data, offset) local g = buffer.readu8(data, offset + 1) local b = buffer.readu8(data, offset + 2) return { Result = Color3.fromRGB(r, g, b), Offset = offset + 3, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: colorSequence DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local COLOR_SEQUENCE_TYPE = VariantTypes.ColorSequence local COLOR_SEQUENCE_KEYPOINT_TYPE = VariantTypes.ColorSequenceKeypoint local DecoderModule = { Type = { COLOR_SEQUENCE_TYPE, COLOR_SEQUENCE_KEYPOINT_TYPE }, } local function readKeypoint(data: buffer, offset: number): (ColorSequenceKeypoint, number) local time = buffer.readf32(data, offset) local r = buffer.readf32(data, offset + 4) local g = buffer.readf32(data, offset + 8) local b = buffer.readf32(data, offset + 12) -- envelope at +16 is present on wire but unused by ColorSequenceKeypoint.new return ColorSequenceKeypoint.new(time, Color3.new(r, g, b)), offset + 20 end local Router = { [COLOR_SEQUENCE_TYPE] = function(data: buffer, offset: number) local count = buffer.readu32(data, offset) offset += 4 if count > 100 then return { Result = ColorSequence.new(Color3.new()), Offset = -1 } end local keypoints = table.create(count) for i = 1, count do local kp kp, offset = readKeypoint(data, offset) keypoints[i] = kp end if count == 0 then return { Result = ColorSequence.new(Color3.new()), Offset = offset } end return { Result = ColorSequence.new(keypoints), Offset = offset, } end, [COLOR_SEQUENCE_KEYPOINT_TYPE] = function(data: buffer, offset: number) local kp, newOffset = readKeypoint(data, offset) return { Result = kp, Offset = newOffset, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: content DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.Content, } local KIND_ID_URI: { [number]: (number) -> string } = { [3] = function(id) return "rbxassetid://" .. tostring(id) end, [11] = function(id) return "http://www.roblox.com/asset/?id=" .. tostring(id) end, [13] = function(id) return "https://www.roblox.com/asset/?id=" .. tostring(id) end, [21] = function(id) return "https://assetdelivery.roblox.com/v1/asset/?id=" .. tostring(id) end, } local KIND_PREFIX: { [number]: string } = { [4] = "rbxgameasset://", } local function readNulString(data: buffer, offset: number): (string, number) local len, nextOffset = Codec.LEB128U(data, offset) local s = buffer.readstring(data, nextOffset, len) nextOffset += len -- trailing NUL terminator nextOffset += 1 return s, nextOffset end function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local source = buffer.readu8(data, offset) offset += 1 if source == 0 then return { Result = Content.none, Offset = offset } end if source == 1 then local kind = buffer.readu8(data, offset) offset += 1 if kind == 0 then local uri uri, offset = readNulString(data, offset) return { Result = Content.fromUri(uri), Offset = offset } end local prefix = KIND_PREFIX[kind] if prefix then local suffix suffix, offset = readNulString(data, offset) return { Result = Content.fromUri(prefix .. suffix), Offset = offset } end local toUri = KIND_ID_URI[kind] if toUri then local zz, nextOffset = Codec.LEB128U(data, offset) local id = Codec.ZzDec(zz) return { Result = Content.fromUri(toUri(id)), Offset = nextOffset } end return { Result = { contentKind = kind }, Offset = offset, } end if source == 2 then local ref, refOffset = Codec.ReadInstanceRef(data, offset) if ref.is_null then return { Result = Content.none, Offset = refOffset } end local inst = Lookup.Instance.ByRef(ref) if inst then local ok, content = pcall(Content.fromObject, inst) if ok then return { Result = content, Offset = refOffset } end end return { Result = { contentObjectId = ref.instance_id, peerId = ref.peer_id }, Offset = refOffset, } end -- Opaque (3) or unknown — no extra payload observed return { Result = if source == 3 then Content.none else { contentSource = source }, Offset = offset, } end return DecoderModule end)() -- Decoder: dateTime DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.DateTime, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local ms, newOffset = Codec.ReadUInt64(data, offset) return { Result = DateTime.fromUnixTimestampMillis(ms), Offset = newOffset, } end return DecoderModule end)() -- Decoder: dictionary DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local DICTIONARY_TYPE = VariantTypes.Dictionary local MAP_TYPE = VariantTypes.Map local DecoderModule = { Type = { DICTIONARY_TYPE, MAP_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, parser: any, depth: number) local length = buffer.len(data) local count, nextOffset = Codec.LEB128(data, offset) local remainingBytes = length - nextOffset local maxPossibleCount = remainingBytes // 2 if count > maxPossibleCount then return { Result = {}, Offset = -1 } end local dict = {} offset = nextOffset for _ = 1, count do local keyLen, keyOffset = Codec.LEB128(data, offset) if keyOffset + keyLen > length then return { Result = dict, Offset = -1 } end local key = buffer.readstring(data, keyOffset, keyLen) local parsed = parser:Parse(keyOffset + keyLen, depth + 1) if parsed.Offset < 0 then return { Result = dict, Offset = -1 } end dict[key] = parsed.Result offset = parsed.Offset end return { Result = dict, Offset = offset, } end return DecoderModule end)() -- Decoder: enum DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = Constants.VariantTypes.Enum, } local function findByValue(enum: Enum, value: number): EnumItem? for _, item in enum:GetEnumItems() do if item.Value == value then return item end end return nil end local function findUniqueByValue(value: number): EnumItem? local match: EnumItem? = nil for _, enum in Enum:GetEnums() do for _, item in enum:GetEnumItems() do if item.Value ~= value then continue end if match then return nil -- ambiguous across enum families end match = item end end return match end function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local typeId = buffer.readu16(data, offset) local value, newOffset = Codec.LEB128(data, offset + 2) local enum = Constants.GetEnum(typeId) local item = if enum then findByValue(enum, value) else nil if not item then item = findUniqueByValue(value) if item and not Constants.EnumTypes[typeId] then Constants.EnumTypes[typeId] = item.EnumType end end return { Result = item or value, Offset = newOffset, } end return DecoderModule end)() -- Decoder: faces DecoderModules[#DecoderModules + 1] = (function() local FACE_NORMALS = { Enum.NormalId.Right, Enum.NormalId.Top, Enum.NormalId.Back, Enum.NormalId.Left, Enum.NormalId.Bottom, Enum.NormalId.Front, } local DecoderModule = { Type = VariantTypes.Faces, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local mask = buffer.readu32(data, offset) local faces = {} for i, normal in FACE_NORMALS do if bit32.band(mask, bit32.lshift(1, i - 1)) ~= 0 then table.insert(faces, normal) end end return { Result = Faces.new(table.unpack(faces)), Offset = offset + 4, } end return DecoderModule end)() -- Decoder: font DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local FONT31_TYPE = VariantTypes.FontLegacy local FONT36_TYPE = VariantTypes.Font local DecoderModule = { Type = { FONT31_TYPE, FONT36_TYPE }, } local function parseFont36(data: buffer, offset: number) local weight = buffer.readu16(data, offset) local style = buffer.readu8(data, offset + 2) offset += 3 local nameLen = buffer.readu32(data, offset) offset += 4 local name = buffer.readstring(data, offset, nameLen) offset += nameLen local fbLen = buffer.readu32(data, offset) offset += 4 -- CachedFaceId is on the wire (often empty); Font.new only takes family. offset += fbLen local fontStyle = if style == 1 then Enum.FontStyle.Italic else Enum.FontStyle.Normal local fontWeight = Enum.FontWeight.Regular for _, item in Enum.FontWeight:GetEnumItems() do if item.Value == weight then fontWeight = item break end end return { Result = Font.new(name, fontWeight, fontStyle), Offset = offset, } end -- Speculative: IDA schema lists a string/id font encoding at 0x31. -- Unobserved on remotes for Font values. local function parseFont31(data: buffer, offset: number) local hdr = buffer.readu8(data, offset) offset += 1 if hdr == 0xFF then local id, newOffset = Codec.LEB128(data, offset) return { Result = { fontId = id + 127 }, Offset = newOffset, } elseif bit32.band(hdr, 0x80) ~= 0 then return { Result = { fontId = bit32.band(hdr, 0x7F) + 127 }, Offset = offset, } elseif hdr == 0x7F then local nameLen, nameOffset = Codec.LEB128(data, offset) local name = buffer.readstring(data, nameOffset, nameLen) return { Result = Font.new(name), Offset = nameOffset + nameLen, } else local name = buffer.readstring(data, offset, hdr) return { Result = Font.new(name), Offset = offset + hdr, } end end local Router = { [FONT31_TYPE] = parseFont31, [FONT36_TYPE] = parseFont36, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: instance DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local INSTANCE_TYPE = VariantTypes.InstanceLegacy local INSTANCE_REF_TYPE = VariantTypes.Instance local INSTANCE_ALT_TYPE = VariantTypes.InstanceAlt local DecoderModule = { Type = { INSTANCE_TYPE, INSTANCE_REF_TYPE, INSTANCE_ALT_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local ref, newOffset = Codec.ReadInstanceRef(data, offset) if ref.is_null then return { Result = nil, Offset = newOffset, } end return { Result = Lookup.Instance.ByRef(ref), Offset = newOffset, } end return DecoderModule end)() -- Decoder: nil DecoderModules[#DecoderModules + 1] = (function() local NIL_TYPE = VariantTypes.Nil local NIL_ALT_TYPE = VariantTypes.NilAlt local DecoderModule = { Type = { NIL_TYPE, NIL_ALT_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) return { Result = nil, Offset = offset, } end return DecoderModule end)() -- Decoder: number DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local INT_TYPE = VariantTypes.Int local FLOAT_TYPE = VariantTypes.Float local DOUBLE_TYPE = VariantTypes.Double local INT64_TYPE = VariantTypes.Int64 local UINT_TYPE = VariantTypes.SystemAddress local DecoderModule = { Type = { INT_TYPE, FLOAT_TYPE, DOUBLE_TYPE, INT64_TYPE, UINT_TYPE }, } local Router = { [INT_TYPE] = function(data: buffer, offset: number) local n, newOffset = Codec.LEB128(data, offset) return { Result = Codec.ZzDec(n), Offset = newOffset, } end, [FLOAT_TYPE] = function(data: buffer, offset: number) return { Result = buffer.readf32(data, offset), Offset = offset + 4, } end, [DOUBLE_TYPE] = function(data: buffer, offset: number) return { Result = buffer.readf64(data, offset), Offset = offset + 8, } end, [INT64_TYPE] = function(data: buffer, offset: number) local n, newOffset = Codec.LEB128(data, offset) return { Result = Codec.ZzDec(n), Offset = newOffset, } end, [UINT_TYPE] = function(data: buffer, offset: number) local n, newOffset = Codec.LEB128(data, offset) return { Result = n, Offset = newOffset, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: numberRange DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.NumberRange, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local min = buffer.readf32(data, offset) local max = buffer.readf32(data, offset + 4) return { Result = NumberRange.new(min, max), Offset = offset + 8, } end return DecoderModule end)() -- Decoder: numberSequence DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local NUMBER_SEQUENCE_TYPE = VariantTypes.NumberSequence local NUMBER_SEQUENCE_KEYPOINT_TYPE = VariantTypes.NumberSequenceKeypoint local DecoderModule = { Type = { NUMBER_SEQUENCE_TYPE, NUMBER_SEQUENCE_KEYPOINT_TYPE }, } local function readKeypoint(data: buffer, offset: number): (NumberSequenceKeypoint, number) local time = buffer.readf32(data, offset) local value = buffer.readf32(data, offset + 4) local envelope = buffer.readf32(data, offset + 8) return NumberSequenceKeypoint.new(time, value, envelope), offset + 12 end local Router = { [NUMBER_SEQUENCE_TYPE] = function(data: buffer, offset: number) local count = buffer.readu32(data, offset) offset += 4 if count > 100 then return { Result = NumberSequence.new(0), Offset = -1 } end local keypoints = table.create(count) for i = 1, count do local kp kp, offset = readKeypoint(data, offset) keypoints[i] = kp end if count == 0 then return { Result = NumberSequence.new(0), Offset = offset } end return { Result = NumberSequence.new(keypoints), Offset = offset, } end, [NUMBER_SEQUENCE_KEYPOINT_TYPE] = function(data: buffer, offset: number) local kp, newOffset = readKeypoint(data, offset) return { Result = kp, Offset = newOffset, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: opaque DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local FACS_DATA_TYPE = VariantTypes.FacsReplicationData local NET_ASSET_HANDLE_TYPE = VariantTypes.NetAssetHandle local NET_ASSET_REF_TYPE = VariantTypes.NetAssetRef local SLIM_REPLICATION_DATA_TYPE = VariantTypes.SlimReplicationData local DecoderModule = { Type = { FACS_DATA_TYPE, NET_ASSET_HANDLE_TYPE, NET_ASSET_REF_TYPE, SLIM_REPLICATION_DATA_TYPE, }, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) local remaining = buffer.len(data) - offset local sliceLen = math.min(remaining, 8) local slice = buffer.create(sliceLen) if sliceLen > 0 then buffer.copy(slice, 0, data, offset, sliceLen) end return { Result = { typeId = t, bytes = slice, }, Offset = -1, } end return DecoderModule end)() -- Decoder: optionalCFrame DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local OPTIONAL_CFRAME_ALT_TYPE = VariantTypes.OptionalCoordinateFrameLegacy local OPTIONAL_CFRAME_TYPE = VariantTypes.OptionalCoordinateFrame local DecoderModule = { Type = { OPTIONAL_CFRAME_ALT_TYPE, OPTIONAL_CFRAME_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local cf, newOffset = Codec.ReadOptCFrame(data, offset) return { Result = cf, Offset = newOffset, } end return DecoderModule end)() -- Decoder: pathWaypoint DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.PathWaypoint, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local x = buffer.readf32(data, offset) local y = buffer.readf32(data, offset + 4) local z = buffer.readf32(data, offset + 8) local action = buffer.readu8(data, offset + 12) offset += 13 local labelLen = buffer.readu32(data, offset) offset += 4 local label = buffer.readstring(data, offset, labelLen) offset += labelLen local actionEnum = Enum.PathWaypointAction.Walk for _, item in Enum.PathWaypointAction:GetEnumItems() do if item.Value == action then actionEnum = item break end end return { Result = PathWaypoint.new(Vector3.new(x, y, z), actionEnum, label), Offset = offset, } end return DecoderModule end)() -- Decoder: physicalProperties DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.PhysicalProperties, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local hdr = buffer.readu8(data, offset) offset += 1 local isCustom = bit32.band(hdr, 0x01) ~= 0 local hasAA = bit32.band(hdr, 0x02) ~= 0 if not isCustom then -- Stand-in: no material id on wire for default. return { Result = PhysicalProperties.new(Enum.Material.Plastic), Offset = offset, } end local density = buffer.readf32(data, offset) local friction = buffer.readf32(data, offset + 4) local elasticity = buffer.readf32(data, offset + 8) local frictionWeight = buffer.readf32(data, offset + 12) local elasticityWeight = buffer.readf32(data, offset + 16) offset += 20 local acousticAbsorption = 1 if hasAA then acousticAbsorption = buffer.readf32(data, offset) offset += 4 end return { Result = PhysicalProperties.new( density, friction, elasticity, frictionWeight, elasticityWeight, acousticAbsorption ), Offset = offset, } end return DecoderModule end)() -- Decoder: ray DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.Ray, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local ox = buffer.readf32(data, offset) local oy = buffer.readf32(data, offset + 4) local oz = buffer.readf32(data, offset + 8) local dx = buffer.readf32(data, offset + 12) local dy = buffer.readf32(data, offset + 16) local dz = buffer.readf32(data, offset + 20) return { Result = Ray.new(Vector3.new(ox, oy, oz), Vector3.new(dx, dy, dz)), Offset = offset + 24, } end return DecoderModule end)() -- Decoder: rect DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.Rect2D, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local x0 = buffer.readf32(data, offset) local y0 = buffer.readf32(data, offset + 4) local x1 = buffer.readf32(data, offset + 8) local y1 = buffer.readf32(data, offset + 12) return { Result = Rect.new(x0, y0, x1, y1), Offset = offset + 16, } end return DecoderModule end)() -- Decoder: region3 DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local REGION3_TYPE = VariantTypes.Region3 local REGION3INT16_TYPE = VariantTypes.Region3int16 local DecoderModule = { Type = { REGION3_TYPE, REGION3INT16_TYPE }, } local Router = { [REGION3_TYPE] = function(data: buffer, offset: number) local x0 = buffer.readf32(data, offset) local y0 = buffer.readf32(data, offset + 4) local z0 = buffer.readf32(data, offset + 8) local x1 = buffer.readf32(data, offset + 12) local y1 = buffer.readf32(data, offset + 16) local z1 = buffer.readf32(data, offset + 20) return { Result = Region3.new(Vector3.new(x0, y0, z0), Vector3.new(x1, y1, z1)), Offset = offset + 24, } end, [REGION3INT16_TYPE] = function(data: buffer, offset: number) local x0 = buffer.readi16(data, offset) local y0 = buffer.readi16(data, offset + 2) local z0 = buffer.readi16(data, offset + 4) local x1 = buffer.readi16(data, offset + 6) local y1 = buffer.readi16(data, offset + 8) local z1 = buffer.readi16(data, offset + 10) return { Result = Region3int16.new(Vector3int16.new(x0, y0, z0), Vector3int16.new(x1, y1, z1)), Offset = offset + 12, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() -- Decoder: replicationPV DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.ReplicationPV, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local flags = buffer.readu8(data, offset) offset += 1 local result = { flags = flags, position = nil :: Vector3?, rotationId = bit32.band(flags, 0x1F), rotation = nil :: CFrame?, velocity = nil :: Vector3?, angularVelocity = nil :: Vector3?, } if flags >= 0x80 then local px = buffer.readf32(data, offset) local py = buffer.readf32(data, offset + 4) local pz = buffer.readf32(data, offset + 8) result.position = Vector3.new(px, py, pz) offset += 12 end if result.rotationId == 0 then local qx, qy, qz, qw qx, qy, qz, qw, offset = Codec.ReadCompressedRotation(data, offset) result.rotation = CFrame.new(0, 0, 0, qx, qy, qz, qw) end if bit32.band(flags, 0x40) == 0 then local vx = buffer.readf32(data, offset) local vy = buffer.readf32(data, offset + 4) local vz = buffer.readf32(data, offset + 8) result.velocity = Vector3.new(vx, vy, vz) offset += 12 end if bit32.band(flags, 0x20) == 0 then local ax = buffer.readf32(data, offset) local ay = buffer.readf32(data, offset + 4) local az = buffer.readf32(data, offset + 8) result.angularVelocity = Vector3.new(ax, ay, az) offset += 12 end return { Result = result, Offset = offset, } end return DecoderModule end)() -- Decoder: securityCapabilities DecoderModules[#DecoderModules + 1] = (function() -- Enum.SecurityCapability.Value is NOT the bit index — engine uses a sparse map. local BIT_TO_CAPABILITY: { [number]: string } = { [0] = "Plugin", [1] = "LocalUser", [2] = "WritePlayer", [3] = "RobloxScript", [4] = "RobloxEngine", [8] = "RunClientScript", [9] = "RunServerScript", [11] = "AccessOutsideWrite", [15] = "Unassigned", [16] = "LoadUnownedAsset", -- AssetRequire alias [17] = "LoadString", [18] = "ScriptGlobals", [19] = "CreateInstances", [20] = "Basic", [21] = "Audio", [22] = "DataStore", [23] = "Network", [24] = "Physics", [25] = "UI", [26] = "CSG", [27] = "Chat", [28] = "Animation", [29] = "AvatarAppearance", -- Avatar alias [30] = "Input", [31] = "Environment", [32] = "RemoteEvent", [33] = "LegacySound", [34] = "Players", [35] = "CapabilityControl", [36] = "AssetRead", [37] = "AssetManagement", [38] = "DynamicGeneration", [39] = "PlatformAvatarEditing", [40] = "AssetCreateUpdate", [41] = "Capture", [42] = "SensitiveInput", [43] = "Monetization", [44] = "LoadOwnedAsset", [45] = "Social", [46] = "ServerCommunication", [47] = "Logging", [48] = "PromptExternalPurchase", [49] = "Groups", [50] = "Teleport", [51] = "Consequences", [52] = "Material", [53] = "AvatarBehavior", [59] = "RemoteCommand", [60] = "InternalTest", [61] = "PluginOrOpenCloud", [62] = "Assistant", } local DecoderModule = { Type = VariantTypes.SecurityCapabilities, } local function fromBitmask(lo: number, hi: number): SecurityCapabilities local caps: { Enum.SecurityCapability } = {} for bit = 0, 63 do local word = if bit < 32 then lo else hi local bitInWord = bit % 32 if bit32.band(word, bit32.lshift(1, bitInWord)) ~= 0 then local name = BIT_TO_CAPABILITY[bit] if name then local ok, item = pcall(function() return (Enum.SecurityCapability :: any)[name] end) if ok and item then table.insert(caps, item) end end end end if #caps == 0 then return SecurityCapabilities.new() end return SecurityCapabilities.new(table.unpack(caps)) end function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local lo = buffer.readu32(data, offset) local hi = buffer.readu32(data, offset + 4) return { Result = fromBitmask(lo, hi), Offset = offset + 8, } end return DecoderModule end)() -- Decoder: sharedString DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.SharedString, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) return { Result = buffer.readstring(data, offset, 16), Offset = offset + 16, } end return DecoderModule end)() -- Decoder: string DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local STRING_TYPE = VariantTypes.String local BINARY_STRING_TYPE = VariantTypes.BinaryString local DecoderModule = { Type = { STRING_TYPE, BINARY_STRING_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local length, newOffset = Codec.LEB128(data, offset) return { Result = buffer.readstring(data, newOffset, length), Offset = newOffset + length, } end return DecoderModule end)() -- Decoder: tuple DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local TUPLE_TYPE = VariantTypes.Tuple local ARRAY_TYPE = VariantTypes.Array local DecoderModule = { Type = { TUPLE_TYPE, ARRAY_TYPE }, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, parser: any, depth: number) local length = buffer.len(data) local count, nextOffset = Codec.LEB128(data, offset) local remainingBytes = length - nextOffset if count > remainingBytes then return { Result = {}, Offset = -1 } end local values = table.create(count) offset = nextOffset for i = 1, count do local parsed = parser:Parse(offset, depth + 1) if parsed.Offset < 0 then return { Result = values, Offset = -1 } end values[i] = parsed.Result offset = parsed.Offset end return { Result = values, Offset = offset, } end return DecoderModule end)() -- Decoder: udim DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.UDim, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local scale = buffer.readf32(data, offset) local udimOffset = buffer.readi32(data, offset + 4) return { Result = UDim.new(scale, udimOffset), Offset = offset + 8, } end return DecoderModule end)() -- Decoder: udim2 DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.UDim2, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) local xs = buffer.readf32(data, offset) local xo = buffer.readi32(data, offset + 4) local ys = buffer.readf32(data, offset + 8) local yo = buffer.readi32(data, offset + 12) return { Result = UDim2.new(xs, xo, ys, yo), Offset = offset + 16, } end return DecoderModule end)() -- Decoder: uniqueId DecoderModules[#DecoderModules + 1] = (function() local DecoderModule = { Type = VariantTypes.UniqueId, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, _parser: any, _depth: number) return { Result = buffer.readstring(data, offset, 16), Offset = offset + 16, } end return DecoderModule end)() -- Decoder: variant DecoderModules[#DecoderModules + 1] = (function() local MAX_DEPTH = 3 local DecoderModule = { Type = VariantTypes.ContentId, } function DecoderModule.Parse(data: buffer, offset: number, _t: number, parser: any, depth: number) if depth >= MAX_DEPTH then return { Result = nil, Offset = offset } end return parser:Parse(offset, depth + 1) end return DecoderModule end)() -- Decoder: vector DecoderModules[#DecoderModules + 1] = (function() --// Constants \\-- local VECTOR2_TYPE = VariantTypes.Vector2 local VECTOR3_TYPE = VariantTypes.Vector3 local VECTOR2INT16_TYPE = VariantTypes.Vector2int16 local VECTOR3INT16_TYPE = VariantTypes.Vector3int16 local DecoderModule = { Type = { VECTOR2_TYPE, VECTOR3_TYPE, VECTOR2INT16_TYPE, VECTOR3INT16_TYPE }, } local Router = { [VECTOR2_TYPE] = function(data: buffer, offset: number) local x = buffer.readf32(data, offset) local y = buffer.readf32(data, offset + 4) return { Result = Vector2.new(x, y), Offset = offset + 8, } end, [VECTOR3_TYPE] = function(data: buffer, offset: number) local x = buffer.readf32(data, offset) local y = buffer.readf32(data, offset + 4) local z = buffer.readf32(data, offset + 8) return { Result = Vector3.new(x, y, z), Offset = offset + 12, } end, [VECTOR2INT16_TYPE] = function(data: buffer, offset: number) local x = buffer.readi16(data, offset) local y = buffer.readi16(data, offset + 2) return { Result = Vector2int16.new(x, y), Offset = offset + 4, } end, [VECTOR3INT16_TYPE] = function(data: buffer, offset: number) local x = buffer.readi16(data, offset) local y = buffer.readi16(data, offset + 2) local z = buffer.readi16(data, offset + 4) return { Result = Vector3int16.new(x, y, z), Offset = offset + 6, } end, } function DecoderModule.Parse(data: buffer, offset: number, t: number, _parser: any, _depth: number) return Router[t](data, offset) end return DecoderModule end)() local Parser = (function() --// Decoders \-- local Decoders local function LazilyLoadDecoders() if Decoders then return end Decoders = {} for _, Module in DecoderModules do local SanitizedType = {} if typeof(Module.Type) == "number" then table.insert(SanitizedType, Module.Type) elseif typeof(Module.Type) == "table" then SanitizedType = Module.Type end for _, t in SanitizedType do Decoders[t] = Module end end end --// Parser \\-- local Parser = {} Parser.__index = Parser local MAX_NESTING_DEPTH = 64 function Parser.new(data: buffer) LazilyLoadDecoders() assert(Decoders, "Could not load variant decoders.") local self = setmetatable({ Data = data, }, Parser) return self end function Parser:Parse(offset: number, depth: number?): { Result: any, Offset: number } depth = depth or 0 local length = buffer.len(self.Data) if offset >= length then return { Result = nil, Offset = -1 } end if depth > MAX_NESTING_DEPTH then return { Result = nil, Offset = -1 } end local typeId = buffer.readu8(self.Data, offset) offset += 1 local decoder = Decoders[typeId] if not decoder then return { Result = nil, Offset = -1 } end return decoder.Parse(self.Data, offset, typeId, self, depth) end export type Arguments = { Values: { any }, Count: number, } export type ParsedArguments = Arguments & { Offset: number, } function Parser:ParseArgs(offset: number, depth: number?): ParsedArguments depth = depth or 0 local length = buffer.len(self.Data) if offset >= length then return { Values = {}, Offset = offset, Count = 0 } end local count, nextOffset = Codec.LEB128(self.Data, offset) local remainingBytes = length - nextOffset if count > remainingBytes then return { Values = {}, Offset = -1, Count = 0 } end offset = nextOffset local args = table.create(count) for i = 1, count do local parsed = self:Parse(offset, depth) if parsed.Offset < 0 then return { Values = args, Offset = -1, Count = i - 1 } end args[i] = parsed.Result offset = parsed.Offset end return { Values = args, Offset = offset, Count = count } end export type RemoteFunctionPayload = { InvokeId: number, IsResponse: boolean, RequestRef: Codec.InstanceRef?, IsError: boolean, Error: string?, Arguments: Arguments, Offset: number, } function Parser:ParseRemoteFunction(offset: number, isResponse: boolean, depth: number?): RemoteFunctionPayload depth = depth or 0 local length = buffer.len(self.Data) local invokeId, nextOffset = Codec.LEB128(self.Data, offset) local requestRef = nil -- RemoteFunction requests carry an InstanceRef used by Roblox to correlate -- the invocation. Responses omit it and begin their return arguments here. if not isResponse then requestRef, nextOffset = Codec.ReadInstanceRef(self.Data, nextOffset) end if isResponse and nextOffset < length and buffer.readu8(self.Data, nextOffset) == 0x3D then local start = nextOffset + 1 local finish = start while finish < length and buffer.readu8(self.Data, finish) ~= 0 do finish += 1 end if finish >= length then return { InvokeId = invokeId, IsResponse = true, RequestRef = nil, IsError = true, Error = nil, Arguments = { Values = {}, Count = 0, }, Offset = -1, } end local message = if finish > start then buffer.readstring(self.Data, start, finish - start) else "" return { InvokeId = invokeId, IsResponse = isResponse, RequestRef = requestRef, IsError = true, Error = message, Arguments = { Values = {}, Count = 0, }, Offset = finish + 1, } end local parsed = self:ParseArgs(nextOffset, depth) return { InvokeId = invokeId, IsResponse = isResponse, RequestRef = requestRef, IsError = false, Error = nil, Arguments = { Values = parsed.Values, Count = parsed.Count, }, Offset = parsed.Offset, } end return Parser end)() local ItemTypes = Constants.ItemTypes local SupportedInstanceTypes = { RemoteEvent = true, RemoteFunction = true, UnreliableRemoteEvent = true, } local function IsInvocationItem(ItemType: number): boolean return ItemType == ItemTypes.ReliableEventInvocationItem or ItemType == ItemTypes.UnreliableEventInvocationItem end local function ParseItem( Data: buffer, Length: number, ItemStart: number, IsResponse: IsRemoteFunctionResponse? ) local Offset = ItemStart local ItemType = buffer.readu8(Data, Offset) Offset += 1 if not IsInvocationItem(ItemType) then return { Stop = true, Reason = "unsupported item type", ItemType = ItemType, CurrentOffset = Offset, } end local InstanceRef, NextOffset = Codec.ReadInstanceRef(Data, Offset) Offset = NextOffset if InstanceRef.is_null or Offset + 2 > Length then return { Stop = true, Reason = if InstanceRef.is_null then "null instance reference" else "scope id exceeds packet", ItemType = ItemType, InstanceRef = InstanceRef, CurrentOffset = Offset, } end local ScopeId = buffer.readu16(Data, Offset) Offset += 2 local Instance = Lookup.Instance.ByRef(InstanceRef) if not Instance or not SupportedInstanceTypes[Instance.ClassName] then return { Stop = true, Reason = "instance lookup failed", ItemType = ItemType, InstanceRef = InstanceRef, ScopeId = ScopeId, CurrentOffset = Offset, } end local PayloadParser = Parser.new(Data) local Payload: Payload if Instance:IsA("RemoteFunction") then local InvokeId = Codec.LEB128(Data, Offset) local Response = PayloadParser:ParseRemoteFunction( Offset, IsResponse ~= nil and IsResponse(Instance, InvokeId) ) Payload = { Type = "RemoteFunction", Response = Response, Offset = Response.Offset, } else local Arguments = PayloadParser:ParseArgs(Offset) Payload = { Type = "Arguments", Arguments = { Values = Arguments.Values, Count = Arguments.Count, }, Offset = Arguments.Offset, } end if Payload.Offset <= Offset or Payload.Offset > Length then return { Stop = true, Reason = "payload offset is invalid", ItemType = ItemType, InstanceRef = InstanceRef, ScopeId = ScopeId, Instance = Instance, Payload = Payload, PayloadStart = Offset, PacketLength = Length, CurrentOffset = Offset, } end return { ItemType = ItemType, InstanceRef = InstanceRef, ScopeId = ScopeId, Instance = Instance, Payload = Payload, PayloadStart = Offset, } end local function TryParseItem( Data: buffer, Length: number, ItemStart: number, IsResponse: IsRemoteFunctionResponse? ) local Success, Result = pcall(ParseItem, Data, Length, ItemStart, IsResponse) if not Success then return nil, { Stop = true, Reason = "item parser errored", Error = Result, CurrentOffset = ItemStart, } elseif Result.Stop then return nil, Result end return Result, nil end local function IsPlausibleBoundary(Data: buffer, Length: number, Offset: number): boolean if Offset >= Length then return true end local NextItemType = buffer.readu8(Data, Offset) return NextItemType == 0 or IsInvocationItem(NextItemType) end local function FindNextInvocation( Data: buffer, Length: number, StartOffset: number, IsResponse: IsRemoteFunctionResponse? ) for Candidate = StartOffset, Length - 1 do if not IsInvocationItem(buffer.readu8(Data, Candidate)) then continue end local Parsed = TryParseItem(Data, Length, Candidate, IsResponse) if Parsed and IsPlausibleBoundary(Data, Length, Parsed.Payload.Offset) then return Candidate, Parsed end end return nil, nil end --[[ Every remote call in one ID_DATA packet, in order: { { instance, kind = "event" | "function", args = {...}, n, invokeId?, isResponse?, error? }, ... } `isResponse(instance, invokeId)` tells a RemoteFunction reply from a request, which the bytes alone cannot. ]] local function parse(Data, IsResponse) local Length = buffer.len(Data) local Calls = {} if Length < 2 then return Calls end local Offset = 1 while Offset < Length do local Parsed = TryParseItem(Data, Length, Offset, IsResponse) if not Parsed then local RecoveredStart, Recovered = FindNextInvocation(Data, Length, Offset + 1, IsResponse) if RecoveredStart and Recovered then Parsed = Recovered else break end end local Payload = Parsed.Payload if Payload.Type == "RemoteFunction" then local R = Payload.Response Calls[#Calls + 1] = { instance = Parsed.Instance, kind = "function", args = R.Arguments.Values, n = R.Arguments.Count, invokeId = R.InvokeId, isResponse = R.IsResponse, error = R.IsError and (R.Error or "error") or nil, } else Calls[#Calls + 1] = { instance = Parsed.Instance, kind = "event", args = Payload.Arguments.Values, n = Payload.Arguments.Count, } end Offset = Payload.Offset end return Calls end return { parse = parse, PacketId = Constants.PacketIds.ID_DATA, ItemTypes = Constants.ItemTypes, VariantTypes = Constants.VariantTypes, Codec = Codec, } end