├── .gitignore ├── DemLock.Entities ├── ActiveField.cs ├── ClassDefinitions │ ├── CBodyComponentBaseAnimGraph.0.class.json │ ├── CBodyComponentBaseAnimGraph.1.class.json │ ├── CBodyComponentBaseAnimGraph.2.class.json │ ├── CBodyComponentBaseAnimGraph.3.class.json │ ├── CCitadelPlayerPawn.0.class.json │ ├── CEntityIdentity.0.class.json │ ├── FullSellPriceAbilityUpgrades_t.0.class.json │ └── ViewAngleServerChange_t.0.class.json ├── DefinedObjects │ ├── CGameSceneNodeHandle.cs │ ├── CNetworkedQuantizedFloat.cs │ ├── CUtlString.cs │ ├── CUtlStringToken.cs │ ├── CUtlSymbolLarge.cs │ ├── Color.cs │ ├── DFixedSizeArray.cs │ ├── GameTime.cs │ ├── HSequence.cs │ ├── QAngleDecoder.cs │ ├── SimulationTime.cs │ ├── Vector.cs │ ├── Vector2D.cs │ └── Vector4D.cs ├── DemLock.Entities.csproj ├── EntityDecoder.cs ├── FieldDecoder.cs ├── FieldDecoders │ ├── ChildDecoder.cs │ ├── FieldDecoder.cs │ └── FloatDecoder.cs ├── FieldEncodingInfo.cs ├── FieldPath.cs ├── FieldPathEncoding.cs ├── Generics │ ├── CHandle.cs │ ├── CNetworkUtlVectorBase.cs │ ├── CStrongHandle.cs │ ├── CUtlVector.cs │ ├── CUtlVectorEmbeddedNetworkVar.cs │ └── DGeneric.cs ├── HuffmanNode.cs ├── Primitives │ ├── DBool.cs │ ├── DFloat.cs │ ├── DGenericEnum.cs │ ├── DInt16.cs │ ├── DInt32.cs │ ├── DInt8.cs │ ├── DNull.cs │ ├── DPrimitive.cs │ ├── DString.cs │ ├── DUInt16.cs │ ├── DUInt32.cs │ ├── DUInt64.cs │ └── DUInt8.cs ├── QAngle.cs ├── QuantizedFloatEncoding.cs └── UpdateDelta.cs ├── DemLock.Parser ├── Configuration │ └── DemoParserConfig.cs ├── DemLock.Parser.csproj ├── DemoParser.cs ├── DemoParserContext.cs ├── DemoStream.cs ├── EntityManager.cs ├── Events │ ├── DemoFrames.cs │ ├── GameEvents.cs │ ├── NetMessages.cs │ ├── PacketMessages.cs │ ├── ServiceMessages.cs │ └── UserMessages.cs ├── FieldPathEncoding.cs ├── Fields │ └── FieldMapping.cs ├── FrameHandler.cs ├── HuffmanNode.cs ├── MessageHandler.cs ├── Models │ ├── DClass.cs │ ├── DField.cs │ ├── DFieldType.cs │ ├── DSerializer.cs │ ├── DemoFrameCommand.cs │ ├── DemoMessage.cs │ ├── FrameData.cs │ ├── GameTick.cs │ ├── GameTime.cs │ ├── MessageTypes.cs │ ├── StringTable.cs │ └── StringTableEntries │ │ └── StringTableEntry.cs ├── Monitoring.cs └── proto │ ├── BaseModifier.cs │ ├── CitadelGameevents.cs │ ├── CitadelGamemessages.cs │ ├── CitadelGcmessagesCommon.cs │ ├── CitadelUsermessages.cs │ ├── Demo.cs │ ├── Gameevents.cs │ ├── GcsdkGcmessages.cs │ ├── Netmessages.cs │ ├── Networkbasetypes.cs │ ├── Steammessages.cs │ ├── SteammessagesSteamlearnSteamworkssdk.cs │ ├── SteammessagesUnifiedBaseSteamworkssdk.cs │ ├── Te.cs │ ├── Usermessages.cs │ ├── Valveextensions.cs │ ├── base_modifier.proto │ ├── citadel_gameevents.proto │ ├── citadel_gamemessages.proto │ ├── citadel_gcmessages_common.proto │ ├── citadel_usermessages.proto │ ├── demo.proto │ ├── gameevents.proto │ ├── gcsdk_gcmessages.proto │ ├── google │ └── protobuf │ │ └── descriptor.proto │ ├── netmessages.proto │ ├── networkbasetypes.proto │ ├── steammessages.proto │ ├── steammessages_steamlearn.steamworkssdk.proto │ ├── steammessages_unified_base.steamworkssdk.proto │ ├── te.proto │ ├── usermessages.proto │ └── valveextensions.proto ├── DemLock.Tests ├── DemLock.Tests.csproj └── UnitTest1.cs ├── DemLock.Utils ├── BitBuffer.cs ├── BitStream.cs ├── DemLock.Utils.csproj └── StreamExtensions.cs ├── DemLock.sln ├── DemLock ├── DemLock.csproj └── Program.cs ├── Makefile └── ReadMe.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea 7 | DemLock.sln.DotSettings.user -------------------------------------------------------------------------------- /DemLock.Entities/ActiveField.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities; 4 | 5 | public record struct ActiveField(string FieldName, FieldDecoder Value); -------------------------------------------------------------------------------- /DemLock.Entities/ClassDefinitions/CBodyComponentBaseAnimGraph.2.class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "CBodyComponentBaseAnimGraph", 3 | "Version": 2, 4 | "Fields": { 5 | "m_skeletonInstance": { 6 | "m_vecOrigin": { 7 | "m_cellX": { 8 | "Path": 0, 9 | "Type": "UInt16", 10 | "NetworkType": "uint16" 11 | }, 12 | "m_cellY": { 13 | "Path": 1, 14 | "Type": "UInt16", 15 | "NetworkType": "uint16" 16 | }, 17 | "m_cellZ": { 18 | "Path": 2, 19 | "Type": "UInt16", 20 | "NetworkType": "uint16" 21 | }, 22 | "m_vecX": { 23 | "Path": 3, 24 | "Type": "float", 25 | "NetworkType": "CNetworkedQuantizedFloat" 26 | }, 27 | "m_vecY": { 28 | "Path": 4, 29 | "Type": "float", 30 | "NetworkType": "CNetworkedQuantizedFloat" 31 | }, 32 | "m_vecZ": { 33 | "Path": 5, 34 | "Type": "float", 35 | "NetworkType": "CNetworkedQuantizedFloat" 36 | }, 37 | "m_nOutsideWorld": { 38 | "Path": 47, 39 | "Type": "UInt16", 40 | "NetworkType": "uint16" 41 | } 42 | }, 43 | "m_hParent": { 44 | "Path": 6, 45 | "Type": "UInt32", 46 | "NetworkType": "CGameSceneNodeHandle" 47 | }, 48 | "m_angRotation": { 49 | "Path": 7, 50 | "Type": "QAngle", 51 | "NetworkType": "QAngle" 52 | }, 53 | "m_flScale": { 54 | "Path": 8, 55 | "Type": "float", 56 | "NetworkType": "float32" 57 | }, 58 | "m_name": { 59 | "Path": 13, 60 | "Type": "UInt32", 61 | "NetworkType": "CUtlStringToken" 62 | }, 63 | "m_hierarchyAttachName": { 64 | "Path": 14, 65 | "Type": "UInt32", 66 | "NetworkType": "CUtlStringToken" 67 | }, 68 | "m_modelState": { 69 | "m_hModel": { 70 | "Path": 15, 71 | "Type": "List>", 72 | "NetworkType": "CStrongHandle< InfoForResourceTypeCModel >" 73 | }, 74 | "m_bClientClothCreationSuppressed": { 75 | "Path": 16, 76 | "Type": "bool", 77 | "NetworkType": "bool" 78 | }, 79 | "m_MeshGroupMask": { 80 | "Path": 17, 81 | "Type": "UInt64", 82 | "NetworkType": "uint64" 83 | }, 84 | "m_nIdealMotionType": { 85 | "Path": 18, 86 | "Type": "SByte", 87 | "NetworkType": "int8" 88 | } 89 | }, 90 | "m_bIsAnimationEnabled": { 91 | "Path": 19, 92 | "Type": "bool", 93 | "NetworkType": "bool" 94 | }, 95 | "m_bUseParentRenderBounds": { 96 | "Path": 20, 97 | "Type": "bool", 98 | "NetworkType": "bool" 99 | }, 100 | "m_materialGroup": { 101 | "Path": 21, 102 | "Type": "UInt32", 103 | "NetworkType": "CUtlStringToken" 104 | }, 105 | "m_nHitboxSet": { 106 | "Path": 22, 107 | "Type": "Byte", 108 | "NetworkType": "uint8" 109 | } 110 | }, 111 | "m_animationController": { 112 | "m_hSequence": { 113 | "Path": 9, 114 | "Type": "UInt64", 115 | "NetworkType": "HSequence" 116 | }, 117 | "m_flSeqStartTime": { 118 | "Path": 10, 119 | "Type": "float", 120 | "NetworkType": "GameTime_t" 121 | }, 122 | "m_flSeqFixedCycle": { 123 | "Path": 11, 124 | "Type": "float", 125 | "NetworkType": "float32" 126 | }, 127 | "m_nAnimLoopMode": { 128 | "Path": 12, 129 | "Type": "Enum", 130 | "NetworkType": "AnimLoopMode_t" 131 | }, 132 | "m_animGraphNetworkedVars": { 133 | "m_PredBoolVariables": { 134 | "Path": 23, 135 | "Type": "List", 136 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 137 | }, 138 | "m_PredByteVariables": { 139 | "Path": 24, 140 | "Type": "List", 141 | "NetworkType": "CNetworkUtlVectorBase< uint8 >" 142 | }, 143 | "m_PredUInt16Variables": { 144 | "Path": 25, 145 | "Type": "List", 146 | "NetworkType": "CNetworkUtlVectorBase< uint16 >" 147 | }, 148 | "m_PredIntVariables": { 149 | "Path": 26, 150 | "Type": "List", 151 | "NetworkType": "CNetworkUtlVectorBase< int32 >" 152 | }, 153 | "m_PredUInt32Variables": { 154 | "Path": 27, 155 | "Type": "List", 156 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 157 | }, 158 | "m_PredUInt64Variables": { 159 | "Path": 28, 160 | "Type": "List", 161 | "NetworkType": "CNetworkUtlVectorBase< uint64 >" 162 | }, 163 | "m_PredFloatVariables": { 164 | "Path": 29, 165 | "Type": "List", 166 | "NetworkType": "CNetworkUtlVectorBase< float32 >" 167 | }, 168 | "m_PredVectorVariables": { 169 | "Path": 30, 170 | "Type": "List", 171 | "NetworkType": "CNetworkUtlVectorBase< Vector >" 172 | }, 173 | "m_PredQuaternionVariables": { 174 | "Path": 31, 175 | "Type": "List>", 176 | "NetworkType": "CNetworkUtlVectorBase< Quaternion >" 177 | }, 178 | "m_PredGlobalSymbolVariables": { 179 | "Path": 32, 180 | "Type": "List>", 181 | "NetworkType": "CNetworkUtlVectorBase< CGlobalSymbol >" 182 | }, 183 | "m_OwnerOnlyPredNetBoolVariables": { 184 | "Path": 33, 185 | "Type": "List", 186 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 187 | }, 188 | "m_OwnerOnlyPredNetByteVariables": { 189 | "Path": 34, 190 | "Type": "List", 191 | "NetworkType": "CNetworkUtlVectorBase< uint8 >" 192 | }, 193 | "m_OwnerOnlyPredNetUInt16Variables": { 194 | "Path": 35, 195 | "Type": "List", 196 | "NetworkType": "CNetworkUtlVectorBase< uint16 >" 197 | }, 198 | "m_OwnerOnlyPredNetIntVariables": { 199 | "Path": 36, 200 | "Type": "List", 201 | "NetworkType": "CNetworkUtlVectorBase< int32 >" 202 | }, 203 | "m_OwnerOnlyPredNetUInt32Variables": { 204 | "Path": 37, 205 | "Type": "List", 206 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 207 | }, 208 | "m_OwnerOnlyPredNetUInt64Variables": { 209 | "Path": 38, 210 | "Type": "List", 211 | "NetworkType": "CNetworkUtlVectorBase< uint64 >" 212 | }, 213 | "m_OwnerOnlyPredNetFloatVariables": { 214 | "Path": 39, 215 | "Type": "List", 216 | "NetworkType": "CNetworkUtlVectorBase< float32 >" 217 | }, 218 | "m_OwnerOnlyPredNetVectorVariables": { 219 | "Path": 40, 220 | "Type": "List", 221 | "NetworkType": "CNetworkUtlVectorBase< Vector >" 222 | }, 223 | "m_OwnerOnlyPredNetQuaternionVariables": { 224 | "Path": 41, 225 | "Type": "List>", 226 | "NetworkType": "CNetworkUtlVectorBase< Quaternion >" 227 | }, 228 | "m_OwnerOnlyPredNetGlobalSymbolVariables": { 229 | "Path": 42, 230 | "Type": "List>", 231 | "NetworkType": "CNetworkUtlVectorBase< CGlobalSymbol >" 232 | }, 233 | "m_nBoolVariablesCount": { 234 | "Path": 43, 235 | "Type": "Int32", 236 | "NetworkType": "int32" 237 | }, 238 | "m_nOwnerOnlyBoolVariablesCount": { 239 | "Path": 44, 240 | "Type": "Int32", 241 | "NetworkType": "int32" 242 | }, 243 | "m_nRandomSeedOffset": { 244 | "Path": 45, 245 | "Type": "Int32", 246 | "NetworkType": "int32" 247 | }, 248 | "m_flLastTeleportTime": { 249 | "Path": 46, 250 | "Type": "float", 251 | "NetworkType": "float32" 252 | } 253 | } 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /DemLock.Entities/ClassDefinitions/CBodyComponentBaseAnimGraph.3.class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "CBodyComponentBaseAnimGraph", 3 | "Version": 3, 4 | "Fields": { 5 | "m_skeletonInstance": { 6 | "m_vecOrigin": { 7 | "m_cellX": { 8 | "Path": 0, 9 | "Type": "UInt16", 10 | "NetworkType": "uint16" 11 | }, 12 | "m_cellY": { 13 | "Path": 1, 14 | "Type": "UInt16", 15 | "NetworkType": "uint16" 16 | }, 17 | "m_cellZ": { 18 | "Path": 2, 19 | "Type": "UInt16", 20 | "NetworkType": "uint16" 21 | }, 22 | "m_vecX": { 23 | "Path": 3, 24 | "Type": "float", 25 | "NetworkType": "CNetworkedQuantizedFloat" 26 | }, 27 | "m_vecY": { 28 | "Path": 4, 29 | "Type": "float", 30 | "NetworkType": "CNetworkedQuantizedFloat" 31 | }, 32 | "m_vecZ": { 33 | "Path": 5, 34 | "Type": "float", 35 | "NetworkType": "CNetworkedQuantizedFloat" 36 | }, 37 | "m_nOutsideWorld": { 38 | "Path": 46, 39 | "Type": "UInt16", 40 | "NetworkType": "uint16" 41 | } 42 | }, 43 | "m_hParent": { 44 | "Path": 6, 45 | "Type": "UInt32", 46 | "NetworkType": "CGameSceneNodeHandle" 47 | }, 48 | "m_angRotation": { 49 | "Path": 7, 50 | "Type": "QAngle", 51 | "NetworkType": "QAngle" 52 | }, 53 | "m_flScale": { 54 | "Path": 8, 55 | "Type": "float", 56 | "NetworkType": "float32" 57 | }, 58 | "m_name": { 59 | "Path": 13, 60 | "Type": "UInt32", 61 | "NetworkType": "CUtlStringToken" 62 | }, 63 | "m_hierarchyAttachName": { 64 | "Path": 14, 65 | "Type": "UInt32", 66 | "NetworkType": "CUtlStringToken" 67 | }, 68 | "m_modelState": { 69 | "m_bClientClothCreationSuppressed": { 70 | "Path": 15, 71 | "Type": "bool", 72 | "NetworkType": "bool" 73 | }, 74 | "m_MeshGroupMask": { 75 | "Path": 16, 76 | "Type": "UInt64", 77 | "NetworkType": "uint64" 78 | }, 79 | "m_nIdealMotionType": { 80 | "Path": 17, 81 | "Type": "SByte", 82 | "NetworkType": "int8" 83 | } 84 | }, 85 | "m_bIsAnimationEnabled": { 86 | "Path": 18, 87 | "Type": "bool", 88 | "NetworkType": "bool" 89 | }, 90 | "m_bUseParentRenderBounds": { 91 | "Path": 19, 92 | "Type": "bool", 93 | "NetworkType": "bool" 94 | }, 95 | "m_materialGroup": { 96 | "Path": 20, 97 | "Type": "UInt32", 98 | "NetworkType": "CUtlStringToken" 99 | }, 100 | "m_nHitboxSet": { 101 | "Path": 21, 102 | "Type": "Byte", 103 | "NetworkType": "uint8" 104 | } 105 | }, 106 | "m_animationController": { 107 | "m_hSequence": { 108 | "Path": 9, 109 | "Type": "UInt64", 110 | "NetworkType": "HSequence" 111 | }, 112 | "m_flSeqStartTime": { 113 | "Path": 10, 114 | "Type": "float", 115 | "NetworkType": "GameTime_t" 116 | }, 117 | "m_flSeqFixedCycle": { 118 | "Path": 11, 119 | "Type": "float", 120 | "NetworkType": "float32" 121 | }, 122 | "m_nAnimLoopMode": { 123 | "Path": 12, 124 | "Type": "Enum", 125 | "NetworkType": "AnimLoopMode_t" 126 | }, 127 | "m_animGraphNetworkedVars": { 128 | "m_PredBoolVariables": { 129 | "Path": 22, 130 | "Type": "List", 131 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 132 | }, 133 | "m_PredByteVariables": { 134 | "Path": 23, 135 | "Type": "List", 136 | "NetworkType": "CNetworkUtlVectorBase< uint8 >" 137 | }, 138 | "m_PredUInt16Variables": { 139 | "Path": 24, 140 | "Type": "List", 141 | "NetworkType": "CNetworkUtlVectorBase< uint16 >" 142 | }, 143 | "m_PredIntVariables": { 144 | "Path": 25, 145 | "Type": "List", 146 | "NetworkType": "CNetworkUtlVectorBase< int32 >" 147 | }, 148 | "m_PredUInt32Variables": { 149 | "Path": 26, 150 | "Type": "List", 151 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 152 | }, 153 | "m_PredUInt64Variables": { 154 | "Path": 27, 155 | "Type": "List", 156 | "NetworkType": "CNetworkUtlVectorBase< uint64 >" 157 | }, 158 | "m_PredFloatVariables": { 159 | "Path": 28, 160 | "Type": "List", 161 | "NetworkType": "CNetworkUtlVectorBase< float32 >" 162 | }, 163 | "m_PredVectorVariables": { 164 | "Path": 29, 165 | "Type": "List", 166 | "NetworkType": "CNetworkUtlVectorBase< Vector >" 167 | }, 168 | "m_PredQuaternionVariables": { 169 | "Path": 30, 170 | "Type": "List>", 171 | "NetworkType": "CNetworkUtlVectorBase< Quaternion >" 172 | }, 173 | "m_PredGlobalSymbolVariables": { 174 | "Path": 31, 175 | "Type": "List>", 176 | "NetworkType": "CNetworkUtlVectorBase< CGlobalSymbol >" 177 | }, 178 | "m_OwnerOnlyPredNetBoolVariables": { 179 | "Path": 32, 180 | "Type": "List", 181 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 182 | }, 183 | "m_OwnerOnlyPredNetByteVariables": { 184 | "Path": 33, 185 | "Type": "List", 186 | "NetworkType": "CNetworkUtlVectorBase< uint8 >" 187 | }, 188 | "m_OwnerOnlyPredNetUInt16Variables": { 189 | "Path": 34, 190 | "Type": "List", 191 | "NetworkType": "CNetworkUtlVectorBase< uint16 >" 192 | }, 193 | "m_OwnerOnlyPredNetIntVariables": { 194 | "Path": 35, 195 | "Type": "List", 196 | "NetworkType": "CNetworkUtlVectorBase< int32 >" 197 | }, 198 | "m_OwnerOnlyPredNetUInt32Variables": { 199 | "Path": 36, 200 | "Type": "List", 201 | "NetworkType": "CNetworkUtlVectorBase< uint32 >" 202 | }, 203 | "m_OwnerOnlyPredNetUInt64Variables": { 204 | "Path": 37, 205 | "Type": "List", 206 | "NetworkType": "CNetworkUtlVectorBase< uint64 >" 207 | }, 208 | "m_OwnerOnlyPredNetFloatVariables": { 209 | "Path": 38, 210 | "Type": "List", 211 | "NetworkType": "CNetworkUtlVectorBase< float32 >" 212 | }, 213 | "m_OwnerOnlyPredNetVectorVariables": { 214 | "Path": 39, 215 | "Type": "List", 216 | "NetworkType": "CNetworkUtlVectorBase< Vector >" 217 | }, 218 | "m_OwnerOnlyPredNetQuaternionVariables": { 219 | "Path": 40, 220 | "Type": "List>", 221 | "NetworkType": "CNetworkUtlVectorBase< Quaternion >" 222 | }, 223 | "m_OwnerOnlyPredNetGlobalSymbolVariables": { 224 | "Path": 41, 225 | "Type": "List>", 226 | "NetworkType": "CNetworkUtlVectorBase< CGlobalSymbol >" 227 | }, 228 | "m_nBoolVariablesCount": { 229 | "Path": 42, 230 | "Type": "Int32", 231 | "NetworkType": "int32" 232 | }, 233 | "m_nOwnerOnlyBoolVariablesCount": { 234 | "Path": 43, 235 | "Type": "Int32", 236 | "NetworkType": "int32" 237 | }, 238 | "m_nRandomSeedOffset": { 239 | "Path": 44, 240 | "Type": "Int32", 241 | "NetworkType": "int32" 242 | }, 243 | "m_flLastTeleportTime": { 244 | "Path": 45, 245 | "Type": "float", 246 | "NetworkType": "float32" 247 | } 248 | } 249 | } 250 | } 251 | } -------------------------------------------------------------------------------- /DemLock.Entities/ClassDefinitions/CEntityIdentity.0.class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "CEntityIdentity", 3 | "Version": 0, 4 | "Fields": { 5 | "m_nameStringableIndex": { 6 | "Path": 0, 7 | "Type": "Int32", 8 | "NetworkType": "int32" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /DemLock.Entities/ClassDefinitions/FullSellPriceAbilityUpgrades_t.0.class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "FullSellPriceAbilityUpgrades_t", 3 | "Version": 0, 4 | "Fields": { 5 | "m_strAbilityUpgrade": { 6 | "Path": 0, 7 | "Type": "String", 8 | "NetworkType": "CUtlString" 9 | }, 10 | "m_unGameTimePurchased": { 11 | "Path": 1, 12 | "Type": "float", 13 | "NetworkType": "GameTime_t" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /DemLock.Entities/ClassDefinitions/ViewAngleServerChange_t.0.class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "ViewAngleServerChange_t", 3 | "Version": 0, 4 | "Fields": { 5 | "nType": { 6 | "Path": 0, 7 | "Type": "Enum", 8 | "NetworkType": "FixAngleSet_t" 9 | }, 10 | "qAngle": { 11 | "Path": 1, 12 | "Type": "QAngle", 13 | "NetworkType": "QAngle" 14 | }, 15 | "nIndex": { 16 | "Path": 2, 17 | "Type": "UInt32", 18 | "NetworkType": "uint32" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/CGameSceneNodeHandle.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class CGameSceneNodeHandle: FieldDecoder 6 | { 7 | private UInt32 _value; 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | 16 | return bs.ReadVarUInt32(); 17 | } 18 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 19 | { 20 | return bs.ReadVarUInt32(); 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return $"[CGameSceneNodeHandle : {_value}]"; 26 | } 27 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/CNetworkedQuantizedFloat.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class CNetworkedQuantizedFloat: FieldDecoder 6 | { 7 | private FieldEncodingInfo _encodingInfo; 8 | private float _value; 9 | 10 | public CNetworkedQuantizedFloat(FieldEncodingInfo encodingInfo) 11 | { 12 | _encodingInfo = encodingInfo; 13 | } 14 | 15 | public override void SetValue(object value) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | public override object ReadValue(ref BitBuffer bs) 21 | { 22 | 23 | var encoding = QuantizedFloatEncoding.Create(_encodingInfo); 24 | return encoding.Decode(ref bs); 25 | } 26 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 27 | { 28 | var encoding = QuantizedFloatEncoding.Create(_encodingInfo); 29 | return encoding.Decode(ref bs); 30 | } 31 | 32 | 33 | public override string ToString() 34 | { 35 | return $"[CNetworkedQuantizedFloat : {_value}] "; 36 | } 37 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/CUtlString.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class CUtlString: FieldDecoder 6 | { 7 | public string Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | return bs.ReadStringUtf8(); 16 | } 17 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 18 | { 19 | return bs.ReadStringUtf8(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/CUtlStringToken.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class CUtlStringToken: FieldDecoder 6 | { 7 | public UInt32 Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | return bs.ReadVarUInt32(); 16 | } 17 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 18 | { 19 | return bs.ReadVarUInt32(); 20 | } 21 | 22 | 23 | 24 | public override string ToString() 25 | { 26 | return $"[CUtlStringToken : {Value}]"; 27 | } 28 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/CUtlSymbolLarge.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class CUtlSymbolLarge: FieldDecoder 6 | { 7 | public string Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | 16 | return bs.ReadStringUtf8(); 17 | } 18 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 19 | { 20 | return bs.ReadStringUtf8(); 21 | } 22 | 23 | 24 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/Color.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities.DefinedObjects; 5 | 6 | public class DColor: FieldDecoder 7 | { 8 | public Color Value { get; set; } 9 | public override void SetValue(object value) 10 | { 11 | throw new NotImplementedException(); 12 | } 13 | 14 | public override object ReadValue(ref BitBuffer bs) 15 | { 16 | 17 | IsSet = true; 18 | var rgba = bs.ReadVarUInt32(); 19 | uint rr = (rgba & 0xFF000000) >> 24; 20 | uint gg = (rgba & 0x00FF0000) >> 16; 21 | uint bb = (rgba & 0x0000FF00) >> 8; 22 | uint aa = (rgba & 0x000000FF); 23 | return Color.FromArgb((int)((aa << 24) | (rr << 16) | (gg << 8) | bb)); 24 | } 25 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 26 | { 27 | IsSet = true; 28 | var rgba = bs.ReadVarUInt32(); 29 | uint rr = (rgba & 0xFF000000) >> 24; 30 | uint gg = (rgba & 0x00FF0000) >> 16; 31 | uint bb = (rgba & 0x0000FF00) >> 8; 32 | uint aa = (rgba & 0x000000FF); 33 | return Color.FromArgb((int)((aa << 24) | (rr << 16) | (gg << 8) | bb)); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/DFixedSizeArray.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Nodes; 3 | using DemLock.Entities.Primitives; 4 | using DemLock.Utils; 5 | 6 | namespace DemLock.Entities.DefinedObjects; 7 | 8 | public class DFixedSizeArray:FieldDecoder 9 | { 10 | public string TypeName { get; set; } 11 | public int Length { get; set; } 12 | private FieldDecoder _childDecoder; 13 | /// 14 | /// Right now this is a byte array but this is very very incorrect and I just need to get the baseline parsed and I can 15 | /// then properly address this issue 16 | /// 17 | public FieldDecoder[] Data { get; set; } 18 | public DFixedSizeArray(string typeName, int length, FieldDecoder childDecoder) 19 | { 20 | _childDecoder = childDecoder; 21 | TypeName = typeName; 22 | Length = length; 23 | Data = new FieldDecoder[length]; 24 | } 25 | public override void SetValue(object value) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 31 | { 32 | if (path.Length == 0) return bs.ReadBit(); 33 | 34 | if (path.Length >= 1) 35 | { 36 | return Data[path[0]].SetValue(path[1..], ref bs); 37 | } 38 | 39 | return null; 40 | } 41 | 42 | public override object ReadValue(ref BitBuffer bs) 43 | { 44 | throw new Exception("You should not be calling this!"); 45 | } 46 | public override FieldDecoder GetFieldDecoder(ReadOnlySpan path) 47 | { 48 | if (path.Length == 0) return new DBool(); 49 | return _childDecoder.GetFieldDecoder(path[1..]); 50 | } 51 | 52 | 53 | 54 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/GameTime.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class GameTime: FieldDecoder 6 | { 7 | public float Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | return bs.ReadFloat(); 16 | } 17 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 18 | { 19 | IsSet = true; 20 | return bs.ReadFloat(); 21 | } 22 | public TimeSpan ToTimeSpan() => TimeSpan.FromSeconds(Value); 23 | public override string ToString() => ToTimeSpan().ToString(); 24 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/HSequence.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class HSequence: FieldDecoder 6 | { 7 | public ulong Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException("HSequence::SetValue(Object)"); 11 | } 12 | 13 | public override object ReadValue(ref BitBuffer bs) 14 | { 15 | return bs.ReadUVarInt64() - 1; 16 | } 17 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 18 | { 19 | return bs.ReadUVarInt64() - 1; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/QAngleDecoder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities.DefinedObjects; 5 | 6 | public class QAngleDecoder : FieldDecoder 7 | { 8 | public float Pitch { get; set; } 9 | public float Yaw { get; set; } 10 | public float Roll { get; set; } 11 | private FieldEncodingInfo _encodingInfo; 12 | 13 | public QAngleDecoder(FieldEncodingInfo info) 14 | { 15 | _encodingInfo = info; 16 | } 17 | 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | IsSet = true; 21 | bool hasPitch ; 22 | bool hasYaw ; 23 | bool hasRoll ; 24 | 25 | if (_encodingInfo.VarEncoder == "qangle_pitch_yaw") 26 | { 27 | Pitch = bs.ReadAngle(_encodingInfo.BitCount); 28 | Yaw = bs.ReadAngle(_encodingInfo.BitCount); 29 | Roll = 0.0f; 30 | return new QAngle(Pitch,Yaw, Roll); 31 | } 32 | 33 | if (_encodingInfo.VarEncoder == "qangle_precise") 34 | { 35 | hasPitch = bs.ReadOneBit(); 36 | hasYaw = bs.ReadOneBit(); 37 | hasRoll = bs.ReadOneBit(); 38 | Pitch = hasPitch ? bs.ReadCoordPrecise() : 0.0f; 39 | Yaw = hasYaw ? bs.ReadCoordPrecise() : 0.0f; 40 | Roll = hasRoll ? bs.ReadCoordPrecise() : 0.0f; 41 | return new QAngle(Pitch,Yaw, Roll); 42 | } 43 | 44 | if (_encodingInfo.BitCount != 0) 45 | { 46 | Pitch = bs.ReadAngle(_encodingInfo.BitCount); 47 | Yaw = bs.ReadAngle(_encodingInfo.BitCount); 48 | Roll = bs.ReadAngle(_encodingInfo.BitCount); 49 | return new QAngle(Pitch,Yaw, Roll); 50 | } 51 | 52 | hasPitch = bs.ReadOneBit(); 53 | hasYaw = bs.ReadOneBit(); 54 | hasRoll = bs.ReadOneBit(); 55 | Pitch = hasPitch ? bs.ReadCoord() : 0.0f; 56 | Yaw = hasYaw ? bs.ReadCoord() : 0.0f; 57 | Roll = hasRoll ? bs.ReadCoord() : 0.0f; 58 | return new QAngle(Pitch,Yaw, Roll); 59 | } 60 | 61 | public override void SetValue(object value) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 67 | { 68 | IsSet = true; 69 | bool hasPitch ; 70 | bool hasYaw ; 71 | bool hasRoll ; 72 | 73 | if (_encodingInfo.VarEncoder == "qangle_pitch_yaw") 74 | { 75 | Pitch = bs.ReadAngle(_encodingInfo.BitCount); 76 | Yaw = bs.ReadAngle(_encodingInfo.BitCount); 77 | Roll = 0.0f; 78 | return (pitch: Pitch, yaw: Yaw, roll: Roll); 79 | } 80 | 81 | if (_encodingInfo.VarEncoder == "qangle_precise") 82 | { 83 | hasPitch = bs.ReadOneBit(); 84 | hasYaw = bs.ReadOneBit(); 85 | hasRoll = bs.ReadOneBit(); 86 | Pitch = hasPitch ? bs.ReadCoordPrecise() : 0.0f; 87 | Yaw = hasYaw ? bs.ReadCoordPrecise() : 0.0f; 88 | Roll = hasRoll ? bs.ReadCoordPrecise() : 0.0f; 89 | return (pitch: Pitch, yaw: Yaw, roll: Roll); 90 | } 91 | 92 | if (_encodingInfo.BitCount != 0) 93 | { 94 | Pitch = bs.ReadAngle(_encodingInfo.BitCount); 95 | Yaw = bs.ReadAngle(_encodingInfo.BitCount); 96 | Roll = bs.ReadAngle(_encodingInfo.BitCount); 97 | return (pitch: Pitch, yaw: Yaw, roll: Roll); 98 | } 99 | 100 | hasPitch = bs.ReadOneBit(); 101 | hasYaw = bs.ReadOneBit(); 102 | hasRoll = bs.ReadOneBit(); 103 | Pitch = hasPitch ? bs.ReadCoord() : 0.0f; 104 | Yaw = hasYaw ? bs.ReadCoord() : 0.0f; 105 | Roll = hasRoll ? bs.ReadCoord() : 0.0f; 106 | return (pitch: Pitch, yaw: Yaw, roll: Roll); 107 | } 108 | 109 | public override string ToString() 110 | { 111 | return $"[QAngle : {{Pitch: {Pitch}, Yaw: {Yaw}, Roll: {Roll}}}]"; 112 | } 113 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/SimulationTime.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.DefinedObjects; 4 | 5 | public class SimulationTime: FieldDecoder 6 | { 7 | public float Value { get; set; } 8 | private float _tickInterval; 9 | 10 | public SimulationTime(float tickInterval) 11 | { 12 | _tickInterval = tickInterval; 13 | } 14 | 15 | public override void SetValue(object value) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | public override object ReadValue(ref BitBuffer bs) 21 | { 22 | IsSet = true; 23 | var ticks = bs.ReadVarUInt32(); 24 | return ticks * _tickInterval; 25 | } 26 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 27 | { 28 | IsSet = true; 29 | var ticks = bs.ReadVarUInt32(); 30 | return ticks * _tickInterval; 31 | } 32 | public TimeSpan ToTimeSpan() => TimeSpan.FromSeconds(Value); 33 | 34 | public override string ToString() => ToTimeSpan().ToString(); 35 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/Vector.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities.DefinedObjects; 5 | 6 | public class Vector : FieldDecoder 7 | { 8 | public float X { get; set; } 9 | public float Y { get; set; } 10 | public float Z { get; set; } 11 | 12 | private FieldEncodingInfo _encodingInfo; 13 | 14 | public override void SetValue(object value) 15 | { 16 | throw new NotImplementedException("Vector::SetValue(Object)"); 17 | } 18 | 19 | public Vector(FieldEncodingInfo encodingInfo) 20 | { 21 | _encodingInfo = encodingInfo; 22 | } 23 | 24 | public override object ReadValue(ref BitBuffer bs) 25 | { 26 | IsSet = true; 27 | if (_encodingInfo.VarEncoder == "normal") 28 | { 29 | return (bs.Read3BitNormal()); 30 | } 31 | 32 | X = ReadFloat(ref bs); 33 | Y = ReadFloat(ref bs); 34 | Z = ReadFloat(ref bs); 35 | return (X, Y, Z); 36 | } 37 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 38 | { 39 | IsSet = true; 40 | if (_encodingInfo.VarEncoder == "normal") 41 | { 42 | return (bs.Read3BitNormal()); 43 | } 44 | 45 | X = ReadFloat(ref bs); 46 | Y = ReadFloat(ref bs); 47 | Z = ReadFloat(ref bs); 48 | return (X, Y, Z); 49 | } 50 | 51 | private float ReadFloat(ref BitBuffer bits) 52 | { 53 | if (_encodingInfo != null) 54 | { 55 | switch (_encodingInfo.VarEncoder) 56 | { 57 | case "coord": 58 | return bits.ReadCoord(); 59 | case "simtime": 60 | return DecodeSimulationTime(ref bits); 61 | case "runetime": 62 | return DecodeRuneTime(ref bits); 63 | case null: 64 | break; 65 | default: 66 | throw new Exception($"Unknown float encoder: {_encodingInfo.VarEncoder}"); 67 | } 68 | } 69 | 70 | if (_encodingInfo.BitCount <= 0 || _encodingInfo.BitCount >= 32) 71 | return bits.ReadFloat(); 72 | 73 | var encoding = QuantizedFloatEncoding.Create(_encodingInfo); 74 | return encoding.Decode(ref bits); 75 | } 76 | 77 | private static float DecodeRuneTime(ref BitBuffer buffer) 78 | { 79 | var bits = buffer.ReadUInt(4); 80 | unsafe 81 | { 82 | return *(float*)&bits; 83 | } 84 | } 85 | 86 | internal static float DecodeSimulationTime(ref BitBuffer buffer) 87 | { 88 | // Assume a 64 tick server... this will need to be set somehow 89 | var ticks = buffer.ReadVarUInt32(); 90 | return ticks / 64.0f; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/Vector2D.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Entities.FieldDecoders; 3 | using DemLock.Utils; 4 | 5 | namespace DemLock.Entities.DefinedObjects; 6 | 7 | public class Vector2D : FieldDecoder 8 | { 9 | public float X { get; set; } 10 | public float Y { get; set; } 11 | 12 | private FieldEncodingInfo _encodingInfo; 13 | 14 | public Vector2D(FieldEncodingInfo encodingInfo) 15 | { 16 | _encodingInfo = encodingInfo; 17 | } 18 | 19 | public override void SetValue(object value) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | public override object ReadValue(ref BitBuffer bs) 25 | { 26 | 27 | X = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 28 | Y = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 29 | return (X, Y); 30 | } 31 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 32 | { 33 | X = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 34 | Y = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 35 | return (X, Y); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /DemLock.Entities/DefinedObjects/Vector4D.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Entities.FieldDecoders; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities.DefinedObjects; 5 | 6 | public class Vector4D : FieldDecoder 7 | { 8 | public float X { get; set; } 9 | public float Y { get; set; } 10 | public float Z { get; set; } 11 | public float W { get; set; } 12 | 13 | private FieldEncodingInfo _encodingInfo; 14 | 15 | public Vector4D(FieldEncodingInfo encodingInfo) 16 | { 17 | _encodingInfo = encodingInfo; 18 | } 19 | 20 | public override void SetValue(object value) 21 | { 22 | throw new NotImplementedException("Vector4D::SetValue"); 23 | } 24 | 25 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 26 | { 27 | X = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 28 | Y = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 29 | Z = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 30 | W = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 31 | 32 | return (X, Y, Z, W); 33 | } 34 | 35 | public override object ReadValue(ref BitBuffer bs) 36 | { 37 | X = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 38 | Y = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 39 | Z = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 40 | W = FloatDecoder.ReadFloat(ref bs, _encodingInfo); 41 | 42 | return (X, Y, Z, W); 43 | } 44 | } -------------------------------------------------------------------------------- /DemLock.Entities/DemLock.Entities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DemLock.Entities/EntityDecoder.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection.PortableExecutable; 3 | using System.Text; 4 | using System.Text.Json.Nodes; 5 | using DemLock.Entities.Generics; 6 | using DemLock.Entities.Primitives; 7 | using DemLock.Utils; 8 | 9 | namespace DemLock.Entities; 10 | 11 | /// 12 | /// The base entity, which can, on it's own contain all of the entity data gotten 13 | /// through the deserialization process, and can have specific versions derived 14 | /// from it to describe more common functionality 15 | /// 16 | public class EntityDecoder: FieldDecoder 17 | { 18 | /// 19 | /// The class name that this entity is derived from 20 | /// 21 | public string ClassName { get; set; } 22 | public uint Serial { get; set; } 23 | 24 | /// 25 | /// The list of instantiated fields that are attached to this entity 26 | /// 27 | private List _fields { get; set; } = new (); 28 | 29 | /// 30 | /// Dictionary that will contain a mapping of the field name to the field that it points to 31 | /// 32 | private List _fieldNames = new(); 33 | 34 | public List Fields { get => _fields; set => _fields = value; } 35 | public EntityDecoder() 36 | { } 37 | public void AddField(FieldDecoder value, string fieldName) 38 | { 39 | if (value is DNull) 40 | { 41 | throw new Exception($"Attempted to add DNull value to a field"); 42 | } 43 | // Getting the count before we add the value is akin to letting us get the index of the new value ahead of time 44 | _fieldNames.Add(fieldName); 45 | _fields.Add(value); 46 | } 47 | 48 | public override void SetValue(object value) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 53 | { 54 | if (path.Length >= 1) 55 | { 56 | var targetField = _fields[path[0]]; 57 | return targetField.SetValue(path[1..], ref bs); 58 | } 59 | // If path length is 0, then the entity setter is to check if the object is set or not 60 | if (path.Length == 0) 61 | { 62 | return bs.ReadBit(); 63 | } 64 | 65 | return null; 66 | } 67 | 68 | public override FieldDecoder GetFieldDecoder(ReadOnlySpan path) 69 | { 70 | if (path.Length >= 1) 71 | { 72 | var targetField = _fields[path[0]]; 73 | return targetField.GetFieldDecoder(path[1..]); 74 | } 75 | // If path length is 0, then the entity setter is to check if the object is set or not 76 | if (path.Length == 0) 77 | { 78 | return new DBool(); 79 | } 80 | 81 | return null; 82 | } 83 | 84 | public void SetValue(ReadOnlySpan path, ref BitBuffer bs, ref object entity) 85 | { 86 | if (path.Length >= 1) 87 | { 88 | var targetField = _fields[path[0]]; 89 | var d = targetField.SetValue(path[1..], ref bs); 90 | } 91 | // If path length is 0, then the entity setter is to check if the object is set or not 92 | if (path.Length == 0) 93 | { 94 | var v = bs.ReadBit(); 95 | } 96 | } 97 | 98 | public override object ReadValue(ref BitBuffer bs) 99 | { 100 | throw new NotImplementedException("You should not be reading a value of an entity like this!"); 101 | } 102 | 103 | public override void ReadFieldName(ReadOnlySpan path, ref string fieldName) 104 | { 105 | 106 | if (path.Length >= 1) 107 | { 108 | if(string.IsNullOrEmpty(fieldName)) fieldName = _fieldNames[path[0]]; 109 | else fieldName += "." + _fieldNames[path[0]]; 110 | 111 | _fields[path[0]].ReadFieldName(path[1..], ref fieldName); 112 | return; 113 | } 114 | base.ReadFieldName(path, ref fieldName); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /DemLock.Entities/FieldDecoder.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | using DemLock.Entities.DefinedObjects; 3 | using DemLock.Entities.Generics; 4 | using DemLock.Entities.Primitives; 5 | using DemLock.Utils; 6 | 7 | namespace DemLock.Entities; 8 | 9 | /// 10 | /// Demo object that is the most atomic object that can be instantiated 11 | /// as this can represent an entity or a base data type. 12 | /// 13 | public abstract class FieldDecoder 14 | { 15 | public bool IsSet { get; set; } = false; 16 | /// 17 | /// This will be called to set the value if it is an already decoded value 18 | /// 19 | /// 20 | public abstract void SetValue(object value); 21 | 22 | /// 23 | /// This will be used to decode the value from a bit stream if needed. 24 | /// There will be a callback function that is placed in the object at activation 25 | /// which will have all of the encoding information needed (just need to map that data 26 | /// to avoid costly closures) 27 | /// 28 | /// 29 | /// 30 | public abstract object SetValue(ReadOnlySpan path, ref BitBuffer bs); 31 | 32 | public virtual FieldDecoder GetFieldDecoder(ReadOnlySpan path) 33 | { 34 | return this; 35 | } 36 | public abstract object ReadValue(ref BitBuffer bs); 37 | 38 | public virtual void ReadFieldName(ReadOnlySpan path, ref string fieldName) 39 | { 40 | if (string.IsNullOrEmpty(fieldName)) 41 | fieldName = string.Empty; 42 | } 43 | public static FieldDecoder CreateFixedSizeArray(string typeName, int count, FieldDecoder childDecoder) 44 | { 45 | return new DFixedSizeArray(typeName, count, childDecoder); 46 | } 47 | /// 48 | /// Create a new object that's a generic, this is segmented as there will need to be some special handling for generics 49 | /// and it can easily be detected in the activator 50 | /// 51 | /// 52 | /// 53 | /// 54 | public static FieldDecoder CreateGenericObject(string typeName, string genericTypeName, FieldDecoder childDecoder) 55 | { 56 | if(typeName == "CNetworkUtlVectorBase") 57 | return new CNetworkUtlVectorBase(genericTypeName, childDecoder); 58 | if (typeName == "CHandle") 59 | return new DInt32(); 60 | if(typeName == "CStrongHandle") 61 | return new CStrongHandle(genericTypeName); 62 | if (typeName == "CUtlVector") 63 | return new CUtlVector(genericTypeName, childDecoder); 64 | if(typeName == "CUtlVectorEmbeddedNetworkVar") 65 | return new CUtlVectorEmbeddedNetworkVar(genericTypeName, childDecoder); 66 | 67 | throw new Exception($"Unmapped generic type {typeName}"); 68 | return new DNull(); 69 | } 70 | public static FieldDecoder CreateObject(string typeName, FieldEncodingInfo fieldEncodingInfo) 71 | { 72 | if (typeName == "float32") 73 | return new DFloat(fieldEncodingInfo); 74 | if(typeName == "uint16") 75 | return new DUInt16(); 76 | if(typeName == "int16") 77 | return new DInt16(); 78 | if (typeName == "CNetworkedQuantizedFloat") 79 | return new CNetworkedQuantizedFloat(fieldEncodingInfo); 80 | if (typeName == "CGameSceneNodeHandle") 81 | return new CGameSceneNodeHandle(); 82 | if (typeName == "QAngle") 83 | return new QAngleDecoder(fieldEncodingInfo); 84 | if (typeName == "CUtlStringToken") 85 | return new CUtlStringToken(); 86 | if (typeName == "bool") 87 | return new DBool(); 88 | if (typeName == "uint64") 89 | return new DUInt64(fieldEncodingInfo); 90 | if (typeName == "int8") 91 | return new DInt8(); 92 | if(typeName == "uint8") 93 | return new DUInt8(); 94 | if (typeName == "int32") 95 | return new DInt32(); 96 | if (typeName == "uint32") 97 | return new DUInt32(); 98 | 99 | if (typeName == "GameTime_t") 100 | return new GameTime(); 101 | if (typeName == "Color") 102 | return new DColor(); 103 | if (typeName == "Vector") 104 | return new Vector(fieldEncodingInfo); 105 | 106 | // Need to handle 107 | if (typeName == "Vector2D") 108 | return new Vector2D(fieldEncodingInfo); 109 | if (typeName == "Vector4D") 110 | return new Vector4D(fieldEncodingInfo); 111 | 112 | if(typeName == "HSequence") 113 | return new HSequence(); 114 | 115 | if (typeName == "CUtlSymbolLarge") 116 | return new CUtlSymbolLarge(); 117 | if (typeName == "CUtlString") 118 | return new CUtlString(); 119 | 120 | // Default to a UInt32 (basically just do what visit_ident is doing in haste) 121 | return new DUInt32(); 122 | } 123 | } -------------------------------------------------------------------------------- /DemLock.Entities/FieldDecoders/ChildDecoder.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities.FieldDecoders; 2 | 3 | public class ChildDecoder: FieldDecoder 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /DemLock.Entities/FieldDecoders/FieldDecoder.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities.FieldDecoders; 2 | 3 | public class FieldDecoder 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /DemLock.Entities/FieldDecoders/FloatDecoder.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.FieldDecoders; 4 | 5 | public static class FloatDecoder 6 | { 7 | public static float ReadFloat(ref BitBuffer bits, FieldEncodingInfo encodingInfo) 8 | { 9 | if (encodingInfo != null) 10 | { 11 | switch (encodingInfo.VarEncoder) 12 | { 13 | case "coord": 14 | return bits.ReadCoord(); 15 | case "simtime": 16 | return DecodeSimulationTime(ref bits); 17 | case "runetime": 18 | return DecodeRuneTime(ref bits); 19 | case null: 20 | break; 21 | default: 22 | throw new Exception($"Unknown float encoder: {encodingInfo.VarEncoder}"); 23 | } 24 | } 25 | 26 | if (encodingInfo.BitCount <= 0 || encodingInfo.BitCount >= 32) 27 | return bits.ReadFloat(); 28 | 29 | var encoding = QuantizedFloatEncoding.Create(encodingInfo); 30 | return encoding.Decode(ref bits); 31 | } 32 | private static float DecodeRuneTime(ref BitBuffer buffer) 33 | { 34 | var bits = buffer.ReadUInt(4); 35 | unsafe 36 | { 37 | return *(float*)&bits; 38 | } 39 | } 40 | private static float DecodeSimulationTime(ref BitBuffer buffer) 41 | { 42 | // Assume a 64 tick server... this will need to be set somehow 43 | var ticks = buffer.ReadVarUInt32(); 44 | return ticks / 64.0f; 45 | } 46 | } -------------------------------------------------------------------------------- /DemLock.Entities/FieldEncodingInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities; 2 | 3 | public class FieldEncodingInfo 4 | { 5 | public string? VarEncoder { get; set; } 6 | public int BitCount { get; set; } 7 | public int EncodeFlags { get; set; } 8 | public float? LowValue { get; set; } 9 | public float? HighValue { get; set; } 10 | } -------------------------------------------------------------------------------- /DemLock.Entities/FieldPath.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DemLock.Entities; 5 | 6 | /// 7 | /// A list of ints representing a path through nested fields on server classes. 8 | /// 9 | public struct FieldPath : IReadOnlyList 10 | { 11 | public static readonly FieldPath Default = new() {-1}; 12 | 13 | private int _path0; 14 | private int _path1; 15 | private int _path2; 16 | private int _path3; 17 | private int _path4; 18 | private int _path5; 19 | private int _path6; 20 | private int _size; 21 | 22 | public void Add(int item) 23 | { 24 | switch (_size) 25 | { 26 | case 0: _path0 = item; break; 27 | case 1: _path1 = item; break; 28 | case 2: _path2 = item; break; 29 | case 3: _path3 = item; break; 30 | case 4: _path4 = item; break; 31 | case 5: _path5 = item; break; 32 | case 6: _path6 = item; break; 33 | default: throw new InvalidOperationException("FieldPath is full"); 34 | } 35 | 36 | _size += 1; 37 | } 38 | 39 | public void Pop(int count) 40 | { 41 | if (count > _size) 42 | { 43 | throw new InvalidOperationException($"Cannot pop {count} items from a path of length {_size}"); 44 | } 45 | 46 | _size -= count; 47 | } 48 | 49 | public int this[int index] 50 | { 51 | readonly get => index >= 0 && index < _size 52 | ? index switch 53 | { 54 | 0 => _path0, 55 | 1 => _path1, 56 | 2 => _path2, 57 | 3 => _path3, 58 | 4 => _path4, 59 | 5 => _path5, 60 | 6 => _path6, 61 | _ => throw new Exception() 62 | } 63 | : throw new ArgumentOutOfRangeException(nameof(index), $"Cannot get item at index {index}, must be < {_size}"); 64 | set 65 | { 66 | if (index < 0 || index >= _size) 67 | throw new ArgumentOutOfRangeException(nameof(index), 68 | $"Cannot set item at index {index}, must be < {_size}"); 69 | 70 | switch (index) 71 | { 72 | case 0: _path0 = value; break; 73 | case 1: _path1 = value; break; 74 | case 2: _path2 = value; break; 75 | case 3: _path3 = value; break; 76 | case 4: _path4 = value; break; 77 | case 5: _path5 = value; break; 78 | case 6: _path6 = value; break; 79 | default: throw new Exception(); 80 | }; 81 | } 82 | } 83 | 84 | public readonly int Count => _size; 85 | 86 | public override string ToString() => _size == 0 87 | ? "(empty)" 88 | : "/" + string.Join('/', this); 89 | 90 | public readonly IEnumerator GetEnumerator() => new Enumerator(in this); 91 | 92 | IEnumerator IEnumerable.GetEnumerator() => new Enumerator(in this); 93 | public ulong GetHash() { 94 | // Random prime number we will start from to attempt a even distribution 95 | ulong hash = 3739894998211223; 96 | hash ^= 51414113 * (ulong)(_path0 << 2); 97 | hash ^= 51414113 * (ulong)(_path1 << 2); 98 | hash ^= 51414113 * (ulong)(_path2 << 2); 99 | hash ^= 51414113 * (ulong)(_path3 << 2); 100 | hash ^= 51414113 * (ulong)(_path4 << 2); 101 | hash ^= 51414113 * (ulong)(_path5 << 2); 102 | hash ^= 51414113 * (ulong)(_path6 << 2); 103 | hash ^= 51414113 * (ulong)_size; 104 | return hash; 105 | } 106 | 107 | public int GetHash(int classId) 108 | { 109 | return (_path0, _path1, _path2, _path3, _path4, _path5, _path6, classId).GetHashCode(); 110 | } 111 | public struct Enumerator : IEnumerator 112 | { 113 | private readonly FieldPath _fieldPath; 114 | private int _index; 115 | internal Enumerator(in FieldPath fieldPath) 116 | { 117 | _index = -1; 118 | _fieldPath = fieldPath; 119 | } 120 | 121 | public int Current => _fieldPath[_index]; 122 | 123 | object IEnumerator.Current => _fieldPath[_index]; 124 | 125 | public void Dispose() { _index = _fieldPath.Count; } 126 | 127 | public bool MoveNext() 128 | { 129 | _index++; 130 | return _index < _fieldPath.Count; 131 | } 132 | 133 | public void Reset() => _index = -1; 134 | } 135 | 136 | public ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref _path0, _size); 137 | } 138 | -------------------------------------------------------------------------------- /DemLock.Entities/Generics/CHandle.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Generics; 4 | 5 | public class CHandle: DGeneric 6 | { 7 | private UInt64 _value; 8 | 9 | public CHandle(string genericTypeName) : base(genericTypeName) 10 | { 11 | } 12 | 13 | public override void SetValue(object value) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | public override object ReadValue(ref BitBuffer bs) 18 | { 19 | return bs.ReadVarInt32(); 20 | } 21 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 22 | { 23 | return bs.ReadVarInt32(); 24 | } 25 | public override string ToString() 26 | { 27 | return $"[CBaseHandle {_value}]"; 28 | } 29 | } -------------------------------------------------------------------------------- /DemLock.Entities/Generics/CNetworkUtlVectorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Nodes; 3 | using DemLock.Entities.Primitives; 4 | using DemLock.Utils; 5 | 6 | namespace DemLock.Entities.Generics; 7 | 8 | public class CNetworkUtlVectorBase : DGeneric 9 | { 10 | private FieldDecoder _childDecoder; 11 | public CNetworkUtlVectorBase(string genericTypeName, FieldDecoder childDecoder) : base(genericTypeName) 12 | { 13 | _childDecoder = childDecoder; 14 | } 15 | [Obsolete] 16 | public override void SetValue(object value) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | [Obsolete] 21 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 22 | { 23 | throw new NotImplementedException("CNetworkUtlVectorBase.SetValue was called but shouldn't be called"); 24 | } 25 | 26 | public override object ReadValue(ref BitBuffer bs) 27 | { 28 | throw new NotImplementedException("CUtlVector should not be getting called here!"); 29 | } 30 | public override FieldDecoder GetFieldDecoder(ReadOnlySpan path) 31 | { 32 | if (path.Length == 0) 33 | return new DUInt32(); 34 | return _childDecoder.GetFieldDecoder(path[1..]); 35 | } 36 | } -------------------------------------------------------------------------------- /DemLock.Entities/Generics/CStrongHandle.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Generics; 4 | 5 | public class CStrongHandle: DGeneric 6 | { 7 | 8 | public UInt64 Value { get; set; } 9 | 10 | public CStrongHandle(string genericTypeName) : base(genericTypeName) 11 | { } 12 | public override void SetValue(object value) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | 17 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 18 | { 19 | IsSet = true; 20 | return bs.ReadUVarInt64(); 21 | } 22 | 23 | public override object ReadValue(ref BitBuffer bs) 24 | { 25 | return bs.ReadUVarInt64(); 26 | } 27 | 28 | public override string ToString() 29 | { 30 | return $"[CStrongHandle : {Value}]"; 31 | } 32 | } -------------------------------------------------------------------------------- /DemLock.Entities/Generics/CUtlVector.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Nodes; 3 | using DemLock.Entities.Primitives; 4 | using DemLock.Utils; 5 | 6 | namespace DemLock.Entities.Generics; 7 | 8 | public class CUtlVector: DGeneric 9 | { 10 | private readonly FieldDecoder _childDecoder; 11 | public Dictionary Data { get; set; } 12 | public CUtlVector(string genericTypeName,FieldDecoder childDecoder) : base(genericTypeName) 13 | { 14 | _childDecoder = childDecoder; 15 | } 16 | public override void SetValue(object value) 17 | { 18 | throw new NotImplementedException($"CUtlVector::SetValue(Object) is not implemented for {GenericTypeName}"); 19 | } 20 | 21 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 22 | { 23 | throw new NotImplementedException($"CUtlVector::SetValue(ReadOnlySpan) is not implemented for {GenericTypeName}"); 24 | 25 | } 26 | public override object ReadValue(ref BitBuffer bs) 27 | { 28 | throw new NotImplementedException("CUtlVector should not be getting called here!"); 29 | } 30 | 31 | public override FieldDecoder GetFieldDecoder(ReadOnlySpan path) 32 | { 33 | 34 | if (path.Length == 0) 35 | return new DUInt32(); 36 | 37 | return _childDecoder.GetFieldDecoder(path[1..]); 38 | } 39 | } -------------------------------------------------------------------------------- /DemLock.Entities/Generics/CUtlVectorEmbeddedNetworkVar.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Nodes; 3 | using DemLock.Entities.Primitives; 4 | using DemLock.Utils; 5 | 6 | namespace DemLock.Entities.Generics; 7 | 8 | public class CUtlVectorEmbeddedNetworkVar : DGeneric 9 | { 10 | public int Size { get; set; } 11 | public Dictionary Data { get; set; } 12 | private readonly FieldDecoder _childDecoder; 13 | 14 | public CUtlVectorEmbeddedNetworkVar(string genericTypeName) : base(genericTypeName) 15 | { 16 | Data = new (); 17 | GenericTypeName = genericTypeName; 18 | } 19 | 20 | public CUtlVectorEmbeddedNetworkVar(string genericTypeName, FieldDecoder childDecoder) : base(genericTypeName) 21 | { 22 | Data = new (); 23 | GenericTypeName = genericTypeName; 24 | _childDecoder = childDecoder; 25 | } 26 | 27 | public override void SetValue(object value) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 33 | { 34 | throw new NotImplementedException("CUtlVectorEmbeddedNetworkVar should not be getting called here!"); 35 | } 36 | 37 | public override object ReadValue(ref BitBuffer bs) 38 | { 39 | throw new NotImplementedException("CUtlVectorEmbeddedNetworkVar should not be getting called here!"); 40 | } 41 | 42 | public override FieldDecoder GetFieldDecoder(ReadOnlySpan path) 43 | { 44 | if (path.Length == 0) 45 | return new DUInt32(); 46 | return _childDecoder.GetFieldDecoder(path[1..]); 47 | } 48 | } -------------------------------------------------------------------------------- /DemLock.Entities/Generics/DGeneric.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Generics; 4 | 5 | /// 6 | /// Base class that marks an object as a generic, 7 | /// For now there really is not to mcuh need for this besides 8 | /// flagging it for debugging, but easy to group it until it becomes a problem 9 | /// 10 | public abstract class DGeneric: FieldDecoder 11 | { 12 | public string GenericTypeName { get; set; } 13 | // TODO: This needs to somehow handle applying binding so that it knows how to handle the generic type 14 | public DGeneric(string genericTypeName) 15 | { 16 | GenericTypeName = genericTypeName; 17 | } 18 | } -------------------------------------------------------------------------------- /DemLock.Entities/HuffmanNode.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities; 2 | 3 | 4 | internal record HuffmanNode(T? Symbol, int Frequency, HuffmanNode? Left, HuffmanNode? Right) 5 | { 6 | public override string ToString() => Symbol is { } symbol 7 | ? $"{symbol} ({Frequency})" 8 | : $" ({Frequency})"; 9 | 10 | public static HuffmanNode Build(IEnumerable> symbolFreqs) 11 | { 12 | var queue = new PriorityQueue, NodePriority>(symbolFreqs 13 | .Select(kvp => KeyValuePair.Create(kvp.Key, Math.Max(1, kvp.Value))) 14 | .Select((kvp, i) => (new HuffmanNode(kvp.Key, kvp.Value, null, null), new NodePriority(kvp.Value, i)))); 15 | 16 | var i = queue.Count; 17 | while (queue.Count > 1) 18 | { 19 | var left = queue.Dequeue(); 20 | var right = queue.Dequeue(); 21 | var parent = new HuffmanNode(default, left.Frequency + right.Frequency, left, right); 22 | var priority = new NodePriority(left.Frequency + right.Frequency, i++); 23 | queue.Enqueue(parent, priority); 24 | } 25 | 26 | return queue.Dequeue(); 27 | } 28 | } 29 | 30 | internal readonly record struct NodePriority(int Weight, int Value) : IComparable 31 | { 32 | public int CompareTo(NodePriority other) => Weight == other.Weight 33 | ? other.Value.CompareTo(Value) 34 | : Weight.CompareTo(other.Weight); 35 | } 36 | -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DBool.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DBool: DPrimitive 6 | { 7 | public bool Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return bs.ReadBit(); 16 | } 17 | 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | return bs.ReadBit(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DFloat.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | /// 6 | /// Represents a float32 in the entity space 7 | /// 8 | public class DFloat : DPrimitive 9 | { 10 | /// 11 | /// The network name for the field type for checking what serializer to use 12 | /// 13 | public const string NetworkName = "float32"; 14 | 15 | public float Value { get; set; } 16 | private FieldEncodingInfo _encodingInfo; 17 | 18 | public DFloat(FieldEncodingInfo encodingInfo) 19 | { 20 | _encodingInfo = encodingInfo; 21 | } 22 | 23 | public override void SetValue(object value) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 29 | { 30 | if (_encodingInfo != null) 31 | { 32 | switch (_encodingInfo.VarEncoder) 33 | { 34 | case "coord": 35 | return bs.ReadCoord(); 36 | case "simtime": 37 | return (DecodeSimulationTime(ref bs)); 38 | case "runetime": 39 | return DecodeRuneTime(ref bs); 40 | case null: 41 | break; 42 | default: 43 | throw new Exception($"Unknown float encoder: {_encodingInfo.VarEncoder}"); 44 | } 45 | } 46 | 47 | if (_encodingInfo.BitCount <= 0 || _encodingInfo.BitCount >= 32) 48 | { 49 | return bs.ReadFloat(); 50 | } 51 | 52 | var encoding = QuantizedFloatEncoding.Create(_encodingInfo); 53 | return encoding.Decode(ref bs); 54 | } 55 | 56 | public override object ReadValue(ref BitBuffer bs) 57 | { 58 | if (_encodingInfo != null) 59 | { 60 | switch (_encodingInfo.VarEncoder) 61 | { 62 | case "coord": 63 | return bs.ReadCoord(); 64 | case "simtime": 65 | return (DecodeSimulationTime(ref bs)); 66 | case "runetime": 67 | return DecodeRuneTime(ref bs); 68 | case null: 69 | break; 70 | default: 71 | throw new Exception($"Unknown float encoder: {_encodingInfo.VarEncoder}"); 72 | } 73 | } 74 | 75 | if (_encodingInfo.BitCount <= 0 || _encodingInfo.BitCount >= 32) 76 | { 77 | return bs.ReadFloat(); 78 | } 79 | 80 | var encoding = QuantizedFloatEncoding.Create(_encodingInfo); 81 | return encoding.Decode(ref bs); 82 | } 83 | 84 | internal static float DecodeSimulationTime(ref BitBuffer buffer) 85 | { 86 | // Assume a 64 tick server... this will need to be set somehow 87 | var ticks = buffer.ReadVarUInt32(); 88 | return ticks / 64.0f; 89 | } 90 | 91 | private static float DecodeRuneTime(ref BitBuffer buffer) 92 | { 93 | var bits = buffer.ReadUInt(4); 94 | unsafe 95 | { 96 | return *(float*)&bits; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DGenericEnum.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DGenericEnum: DPrimitive 6 | { 7 | public UInt64 Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return bs.ReadVarInt32(); 16 | } 17 | 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | return bs.ReadVarInt32(); 21 | } 22 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DInt16.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DInt16: DPrimitive 6 | { 7 | public short Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException($"DInt16::SetValue(Object) is not implemented"); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return (short)bs.ReadVarInt32(); 16 | } 17 | public override object ReadValue(ref BitBuffer bs) 18 | { 19 | return (short)bs.ReadVarInt32(); 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DInt32.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DInt32: DPrimitive 6 | { 7 | public Int32 Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return bs.ReadVarInt32(); 16 | } 17 | 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | return bs.ReadVarInt32(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DInt8.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DInt8: DPrimitive 6 | { 7 | public sbyte Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return (sbyte)bs.ReadVarInt32(); 16 | } 17 | 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | return (sbyte)bs.ReadVarInt32(); 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return $"[DInt8 : {Value}]"; 26 | } 27 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DNull.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | /// 6 | /// Represents the null primitive 7 | /// 8 | public class DNull: DPrimitive 9 | { 10 | public override void SetValue(object value) 11 | { 12 | throw new NotImplementedException(); 13 | } 14 | 15 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 16 | { 17 | throw new Exception("Tried to update a null value field"); 18 | } 19 | public override object ReadValue(ref BitBuffer bs) 20 | { 21 | throw new Exception("Honestly not sure how you got here"); 22 | } 23 | 24 | 25 | 26 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DPrimitive.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | /// 6 | /// Represents a primitive piece of data 7 | /// 8 | public abstract class DPrimitive: FieldDecoder 9 | { 10 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DString.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DString: DPrimitive 6 | { 7 | public string Value { get; set; } 8 | private int _maxLength; 9 | public DString() 10 | { 11 | _maxLength = 4096; 12 | 13 | } 14 | public DString(int maxLength) 15 | { 16 | _maxLength = maxLength; 17 | } 18 | public override void SetValue(object value) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 24 | { 25 | // Not taking into account max length or anything yet, since it should be fine but will want to later for safety 26 | return bs.ReadStringUtf8(); 27 | } 28 | public override object ReadValue(ref BitBuffer bs) 29 | { 30 | return bs.ReadStringUtf8(); 31 | } 32 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DUInt16.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DUInt16: DPrimitive 6 | { 7 | private UInt32 _value; 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException($"DUInt16::SetValue(Object) is not implemented."); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return bs.ReadVarUInt32(); 16 | } 17 | public override object ReadValue(ref BitBuffer bs) 18 | { 19 | return bs.ReadVarUInt32(); 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return $"[UInt32 : {_value}]"; 25 | } 26 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DUInt32.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DUInt32: DPrimitive 6 | { 7 | public UInt32 Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | return bs.ReadVarUInt32(); 16 | } 17 | public override object ReadValue(ref BitBuffer bs) 18 | { 19 | return bs.ReadVarUInt32(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DUInt64.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Binary; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities.Primitives; 5 | 6 | public class DUInt64 : DPrimitive 7 | { 8 | public UInt64 Value { get; set; } 9 | private FieldEncodingInfo _encodingInfo; 10 | 11 | public DUInt64(FieldEncodingInfo encodingInfo) 12 | { 13 | _encodingInfo = encodingInfo; 14 | } 15 | 16 | public override void SetValue(object value) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | 21 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 22 | { 23 | IsSet = true; 24 | if (_encodingInfo.VarEncoder == "fixed64") 25 | { 26 | return DecodeFixed64(ref bs); 27 | } 28 | else if (_encodingInfo.VarEncoder != null) 29 | { 30 | throw new Exception($"Unknown uint64 encoder: {_encodingInfo.VarEncoder}"); 31 | } 32 | else 33 | { 34 | return bs.ReadUVarInt64(); 35 | } 36 | return null; 37 | } 38 | 39 | public override object ReadValue(ref BitBuffer bs) 40 | { 41 | IsSet = true; 42 | if (_encodingInfo.VarEncoder == "fixed64") 43 | return DecodeFixed64(ref bs); 44 | else if (_encodingInfo.VarEncoder != null) 45 | throw new Exception($"Unknown uint64 encoder: {_encodingInfo.VarEncoder}"); 46 | else 47 | return bs.ReadUVarInt64(); 48 | } 49 | 50 | 51 | private static ulong DecodeFixed64(ref BitBuffer buffer) 52 | { 53 | Span bytes = stackalloc byte[8]; 54 | buffer.ReadBytes(bytes); 55 | return BinaryPrimitives.ReadUInt64LittleEndian(bytes); 56 | } 57 | } -------------------------------------------------------------------------------- /DemLock.Entities/Primitives/DUInt8.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Utils; 2 | 3 | namespace DemLock.Entities.Primitives; 4 | 5 | public class DUInt8: DPrimitive 6 | { 7 | public byte Value { get; set; } 8 | public override void SetValue(object value) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override object SetValue(ReadOnlySpan path, ref BitBuffer bs) 14 | { 15 | IsSet = true; 16 | return (byte)bs.ReadVarUInt32(); 17 | } 18 | public override object ReadValue(ref BitBuffer bs) 19 | { 20 | return bs.ReadVarUInt32(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /DemLock.Entities/QAngle.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities; 2 | 3 | public class QAngle 4 | { 5 | public QAngle(float pitch,float yaw, float roll) 6 | { 7 | Roll = roll; 8 | Yaw = yaw; 9 | Pitch = pitch; 10 | } 11 | public QAngle() 12 | { } 13 | 14 | public float Pitch { get; set; } 15 | public float Yaw { get; set; } 16 | public float Roll { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return $"( {Pitch}, {Yaw}, {Roll} )"; 21 | } 22 | } -------------------------------------------------------------------------------- /DemLock.Entities/QuantizedFloatEncoding.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Entities; 5 | 6 | [Flags] 7 | internal enum QuantizedFloatFlags 8 | { 9 | Unset = 0, 10 | RoundDown = 1 << 0, 11 | RoundUp = 1 << 1, 12 | EncodeZero = 1 << 2, 13 | EncodeIntegers = 1 << 3 14 | } 15 | 16 | internal readonly record struct QuantizedFloatEncoding( 17 | float Low, 18 | float High, 19 | float DecMul, 20 | int BitCount, 21 | QuantizedFloatFlags Flags) 22 | { 23 | public static QuantizedFloatEncoding Create(FieldEncodingInfo fieldEncodingInfo) 24 | { 25 | // Set common properties 26 | if (fieldEncodingInfo.BitCount == 0 || fieldEncodingInfo.BitCount >= 32) 27 | { 28 | // todo: figure out why this is firing - the decoded floats will always be 0 29 | //Debug.Assert(false, "Unexpected quantized float encoding!"); 30 | return new QuantizedFloatEncoding( 31 | Low: 0.0f, 32 | High: 0.0f, 33 | DecMul: 0.0f, 34 | BitCount: 32, 35 | Flags: QuantizedFloatFlags.Unset); 36 | } 37 | 38 | var low = fieldEncodingInfo.LowValue.GetValueOrDefault(0.0f); 39 | var high = fieldEncodingInfo.HighValue.GetValueOrDefault(1.0f); 40 | 41 | // Validate flags 42 | var flags = ValidateFlags( 43 | (QuantizedFloatFlags)fieldEncodingInfo.EncodeFlags, 44 | low, 45 | high); 46 | 47 | // Handle Round Up, Round Down 48 | var bitCount = fieldEncodingInfo.BitCount; 49 | Debug.Assert(bitCount > 0); 50 | var steps = 1 << bitCount; 51 | 52 | var offset = 0.0f; 53 | if (flags.HasFlag(QuantizedFloatFlags.RoundDown)) 54 | { 55 | offset = (high - low) / steps; 56 | high -= offset; 57 | } 58 | else if (flags.HasFlag(QuantizedFloatFlags.RoundUp)) 59 | { 60 | offset = (high - low) / steps; 61 | low += offset; 62 | } 63 | 64 | // Handle integer encoding flag 65 | if (flags.HasFlag(QuantizedFloatFlags.EncodeIntegers)) 66 | { 67 | var delta = Math.Max(1.0f, high - low); 68 | 69 | var deltaLog2 = (int)Math.Ceiling(Math.Log2(delta)); 70 | var range = 1 << deltaLog2; 71 | 72 | bitCount = Math.Max(bitCount, deltaLog2); 73 | steps = 1 << bitCount; 74 | offset = range / (float)steps; 75 | high = low + range - offset; 76 | } 77 | 78 | // Assign multipliers 79 | var highLowMul = CalculateHighLowMul(high - low, bitCount, steps); 80 | var decMul = 1.0f / (steps - 1); 81 | 82 | float Quantize(float value) 83 | { 84 | if (value < low) 85 | { 86 | if (!flags.HasFlag(QuantizedFloatFlags.RoundUp)) 87 | throw new Exception("Field tried to quantize an out of range value"); 88 | 89 | return low; 90 | } 91 | 92 | if (value > high) 93 | { 94 | if (!flags.HasFlag(QuantizedFloatFlags.RoundDown)) 95 | throw new Exception("Field tried to quantize an out of range value"); 96 | 97 | return high; 98 | } 99 | 100 | var i = (uint)((value - low) * highLowMul); 101 | return low + (high - low) * (i * decMul); 102 | } 103 | 104 | // Remove unnecessary flags 105 | if (flags.HasFlag(QuantizedFloatFlags.RoundDown) && Quantize(low) == low) 106 | { 107 | flags &= ~QuantizedFloatFlags.RoundDown; 108 | } 109 | if (flags.HasFlag(QuantizedFloatFlags.RoundUp) && Quantize(high) == high) 110 | { 111 | flags &= ~QuantizedFloatFlags.RoundUp; 112 | } 113 | if (flags.HasFlag(QuantizedFloatFlags.EncodeZero) && Quantize(0.0f) == 0.0f) 114 | { 115 | flags &= ~QuantizedFloatFlags.EncodeZero; 116 | } 117 | 118 | return new QuantizedFloatEncoding( 119 | Low: low, 120 | High: high, 121 | DecMul: decMul, 122 | BitCount: bitCount, 123 | Flags: flags); 124 | } 125 | 126 | private static QuantizedFloatFlags ValidateFlags( 127 | QuantizedFloatFlags flags, 128 | float low, 129 | float high) 130 | { 131 | if (flags == QuantizedFloatFlags.Unset) 132 | return QuantizedFloatFlags.Unset; 133 | 134 | // Discard zero flag when encoding min / max set to 0 135 | if (low == 0.0 && flags.HasFlag(QuantizedFloatFlags.RoundDown) 136 | || (high == 0.0 && flags.HasFlag(QuantizedFloatFlags.RoundUp))) 137 | { 138 | flags &= ~QuantizedFloatFlags.EncodeZero; 139 | } 140 | 141 | // If min / max is zero when encoding zero, switch to round up / round down instead 142 | if (low == 0.0 && flags.HasFlag(QuantizedFloatFlags.EncodeZero)) 143 | { 144 | flags |= QuantizedFloatFlags.RoundDown; 145 | flags &= ~QuantizedFloatFlags.EncodeZero; 146 | } 147 | 148 | if (high == 0.0 && flags.HasFlag(QuantizedFloatFlags.EncodeZero)) 149 | { 150 | flags |= QuantizedFloatFlags.RoundUp; 151 | flags &= ~QuantizedFloatFlags.EncodeZero; 152 | } 153 | 154 | // If the range doesn't span zero, we don't need to encode it 155 | if (low > 0.0 || high < 0.0) 156 | { 157 | flags &= ~QuantizedFloatFlags.EncodeZero; 158 | } 159 | 160 | if (flags.HasFlag(QuantizedFloatFlags.EncodeIntegers)) 161 | { 162 | flags = QuantizedFloatFlags.EncodeIntegers; 163 | } 164 | 165 | if (flags.HasFlag(QuantizedFloatFlags.RoundDown) 166 | && flags.HasFlag(QuantizedFloatFlags.RoundUp)) 167 | { 168 | throw new Exception("RoundUp / RoundDown are mutually exclusive"); 169 | } 170 | 171 | return flags; 172 | } 173 | 174 | private static float CalculateHighLowMul(float range, int bitCount, int steps) 175 | { 176 | var high = bitCount == 32 177 | ? 0xFFFFFFFE 178 | : (1u << bitCount) - 1; 179 | 180 | var highMul = range == 0.0f ? high : high / range; 181 | 182 | // Adjust precision 183 | // todo: make this checked? original code also checked float64 184 | if (highMul * range > high) 185 | { 186 | var multipliers = new[] { 0.9999f, 0.99f, 0.9f, 0.8f, 0.7f }; 187 | foreach (var multiplier in multipliers) 188 | { 189 | highMul = high / range * multiplier; 190 | if (highMul * range <= high) 191 | break; 192 | } 193 | } 194 | 195 | Debug.Assert(highMul != 0.0f); 196 | return highMul; 197 | } 198 | 199 | public float Decode(ref BitBuffer buffer) 200 | { 201 | if (Flags.HasFlag(QuantizedFloatFlags.RoundDown) && buffer.ReadBit()) 202 | return Low; 203 | 204 | if (Flags.HasFlag(QuantizedFloatFlags.RoundUp) && buffer.ReadBit()) 205 | return High; 206 | 207 | if (Flags.HasFlag(QuantizedFloatFlags.EncodeZero) && buffer.ReadBit()) 208 | return 0.0f; 209 | 210 | return Low + (High - Low) * buffer.ReadUInt(BitCount) * DecMul; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /DemLock.Entities/UpdateDelta.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Entities; 2 | 3 | public class UpdateDelta 4 | { 5 | public string Field { get; set; } 6 | public object Value { get; set; } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /DemLock.Parser/Configuration/DemoParserConfig.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser; 4 | 5 | /// 6 | /// Configuration schema for the demo parser that will be read in from the JSON settings 7 | /// to control certain things in the parser 8 | /// 9 | public class DemoParserConfig 10 | { 11 | public List IgnoredFrames { get; set; } = new(); 12 | public List IgnoredMessages { get; set; } = new(); 13 | 14 | public bool LogReadFrames { get; set; } 15 | public bool LogMessageReads { get; set; } 16 | 17 | } -------------------------------------------------------------------------------- /DemLock.Parser/DemLock.Parser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /DemLock.Parser/DemoParser.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Events; 2 | using DemLock.Parser.Models; 3 | using DemLock.Utils; 4 | 5 | namespace DemLock.Parser; 6 | 7 | /// 8 | /// The main demo parser that will be used to set up the event listeners, and then send in a file to be 9 | /// processed 10 | /// 11 | public class DemoParser 12 | { 13 | public NetMessages NetMessages { get; init; } 14 | public GameEvents GameEvents { get; init; } 15 | public UserMessages UserMessages { get; init; } 16 | public ServiceMessages ServiceMessages { get; init; } 17 | public PacketMessages PacketMessages { get; init; } 18 | public DemoFrames DemoFrames { get; init; } 19 | 20 | private FrameHandler _frameHandler; 21 | private MessageHandler _messageHandler; 22 | private DemoParserContext _context; 23 | private DemoParserConfig _config; 24 | 25 | public DemoParser(DemoParserConfig config) 26 | { 27 | _config = config; 28 | GameEvents = new GameEvents(); 29 | ServiceMessages = new ServiceMessages(); 30 | UserMessages = new UserMessages(); 31 | NetMessages = new NetMessages(); 32 | DemoFrames = new DemoFrames(); 33 | PacketMessages = new PacketMessages(); 34 | 35 | _context = new DemoParserContext(_config); 36 | _messageHandler = new MessageHandler( _context, GameEvents, NetMessages, UserMessages, ServiceMessages); 37 | _frameHandler = new FrameHandler( _messageHandler, _context); 38 | 39 | DemoFrames.OnPacket += packet => 40 | { 41 | BitStream bs = new BitStream(packet.Data.ToByteArray()); 42 | while (bs.BitsRemaining > 8) 43 | { 44 | var msgtype = (MessageTypes)bs.ReadUBit(); 45 | var msgSize = bs.ReadVarUInt32(); 46 | byte[] msgData = bs.ReadBytes(msgSize); 47 | PacketMessages.ProcessMessage(msgtype, msgData); 48 | } 49 | }; 50 | 51 | DemoFrames.OnSignonPacket += packet => 52 | { 53 | BitStream bs = new BitStream(packet.Data.ToByteArray()); 54 | while (bs.BitsRemaining > 8) 55 | { 56 | var msgtype = (MessageTypes)bs.ReadUBit(); 57 | var msgSize = bs.ReadVarUInt32(); 58 | byte[] msgData = bs.ReadBytes(msgSize); 59 | PacketMessages.ProcessMessage(msgtype, msgData); 60 | } 61 | }; 62 | 63 | DemoFrames.OnFullPacket += packet => 64 | { 65 | BitStream bs = new BitStream(packet.Packet.Data.ToByteArray()); 66 | while (bs.BitsRemaining > 8) 67 | { 68 | var msgtype = (MessageTypes)bs.ReadUBit(); 69 | var msgSize = bs.ReadVarUInt32(); 70 | byte[] msgData = bs.ReadBytes(msgSize); 71 | PacketMessages.ProcessMessage(msgtype, msgData); 72 | } 73 | }; 74 | 75 | PacketMessages.OnNetMessage += NetMessages.HandleNetMessage; 76 | PacketMessages.OnGameEvent += GameEvents.HandleGameEventMessage; 77 | PacketMessages.OnServiceMessage += ServiceMessages.HandleServiceMessage; 78 | PacketMessages.OnUserMessage += UserMessages.HandleUserMessage; 79 | } 80 | 81 | /// 82 | /// Process a demo file, emitting events to any registered listeners when a derived event 83 | /// is calculated, which will contain data about the event (such as file info being parsed, 84 | /// 85 | /// 86 | public void ProcessDemo(string fileName) 87 | { 88 | // Make sure we clear our context to start fresh 89 | _context.ClearContext(); 90 | using DemoStream demo = DemoStream.FromFilePath(fileName); 91 | FrameData frameData; 92 | int i = 0; 93 | do 94 | { 95 | frameData = demo.ReadFrame(); 96 | _context.CurrentTick = frameData.Tick; 97 | DemoFrames.ProcessFrame(frameData); 98 | i++; 99 | } while (frameData.Command != DemoFrameCommand.DEM_Stop); 100 | } 101 | 102 | public void DumpClassDefinitions(string fileName, string outputDirectory) 103 | { 104 | // Make sure we clear our context to start fresh 105 | _context.ClearContext(); 106 | using DemoStream demo = DemoStream.FromFilePath(fileName); 107 | FrameData frameData; 108 | int i = 0; 109 | do 110 | { 111 | frameData = demo.ReadFrame(); 112 | _context.CurrentTick = frameData.Tick; 113 | if (frameData.Command == DemoFrameCommand.DEM_SendTables || 114 | frameData.Command == DemoFrameCommand.DEM_ClassInfo) 115 | { 116 | _frameHandler.HandleFrame(frameData); 117 | } 118 | 119 | i++; 120 | } while (frameData.Command != DemoFrameCommand.DEM_Stop); 121 | 122 | if (!Directory.Exists(outputDirectory)) 123 | { 124 | Directory.CreateDirectory(outputDirectory); 125 | } 126 | 127 | 128 | _context.DumpClassDefinitions(outputDirectory); 129 | } 130 | } -------------------------------------------------------------------------------- /DemLock.Parser/DemoStream.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Parser.Models; 3 | using DemLock.Utils; 4 | using Snappier; 5 | 6 | namespace DemLock.Parser 7 | { 8 | /// 9 | /// Represents a singular demo stream, and encapsulates all of the processing and 10 | /// parsing that is being done on it. 11 | /// 12 | internal class DemoStream : IDisposable 13 | { 14 | public readonly string FileHeader; 15 | private readonly Stream _stream; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// A instance of IO.Stream. 21 | public DemoStream(Stream stream) 22 | { 23 | _stream = stream; 24 | FileHeader = ReadFileHeader(); 25 | } 26 | 27 | public static DemoStream FromFilePath(string filePath) 28 | { 29 | Stream stream = File.Open(filePath, FileMode.Open, FileAccess.Read); 30 | return new DemoStream(stream); 31 | } 32 | 33 | 34 | private string ReadFileHeader() 35 | { 36 | byte[] stampBytes = new byte[16]; 37 | return _stream.Read(stampBytes, 0, 16) != 16 38 | ? throw new ArgumentException("Invalid demo file stream!") 39 | : Encoding.ASCII.GetString(stampBytes); 40 | } 41 | 42 | public FrameData ReadFrame() 43 | { 44 | var frame = new FrameData(); 45 | uint rawCmd = _stream.ReadVarUInt32(); 46 | frame.Command = (DemoFrameCommand)(rawCmd & ~64); 47 | frame.Tick = _stream.ReadVarUInt32(); 48 | if (frame.Tick == 4294967295) 49 | { 50 | frame.Tick = 0; 51 | } 52 | 53 | // Read the size 54 | frame.Size = _stream.ReadVarUInt32(); 55 | 56 | // Read the raw data in 57 | frame.Data = new byte[frame.Size]; 58 | var bytesRead = _stream.Read(frame.Data, 0, frame.Data.Length); 59 | 60 | // If the frame is compressed we will need to decompress it 61 | if ((rawCmd & 64) == 64) 62 | { 63 | frame.Data = Snappy.DecompressToArray(frame.Data); 64 | } 65 | 66 | return frame; 67 | } 68 | 69 | public void Dispose() 70 | { 71 | _stream.Dispose(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DemLock.Parser/EntityManager.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Entities; 2 | using DemLock.Parser.Models; 3 | using DemLock.Utils; 4 | 5 | namespace DemLock.Parser; 6 | 7 | public class EntityFieldData 8 | { 9 | public EntityFieldData(string fieldName, object fieldValue) 10 | { 11 | FieldName = fieldName; 12 | FieldValue = fieldValue; 13 | } 14 | 15 | public string FieldName { get; set; } 16 | public object FieldValue { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return $"{FieldName}::{FieldValue}"; 21 | } 22 | } 23 | 24 | public class EntityMetaData 25 | { 26 | public string ClassName { get; set; } 27 | public int ClassId { get; set; } 28 | } 29 | 30 | /// 31 | /// Manager for entities to consolidate all of the logic needed to instantiating, and updating 32 | /// the objects 33 | /// 34 | public class EntityManager 35 | { 36 | private DemoParserContext _context; 37 | 38 | /// 39 | /// The entities that are being tracked by the system, a simple array would probably work here, 40 | /// however I have opted for a dictionary, as this makes it easier to reason about, and deals with 41 | /// cases where there might end up being gaps, which again could be handled but this is easier 42 | /// just to get things running 43 | /// 44 | private Dictionary> _entities; 45 | 46 | private Dictionary _metaData; 47 | private Dictionary _deserializerMap; 48 | 49 | private Dictionary _witness; 50 | private Dictionary _fieldDecoders; 51 | 52 | public EntityManager(DemoParserContext context) 53 | { 54 | _witness = new(); 55 | _context = context; 56 | _metaData = new(); 57 | _fieldDecoders = new Dictionary(); 58 | _deserializerMap = new Dictionary(); 59 | _entities = new(); 60 | } 61 | 62 | public void AddNewEntity(int index, DClass serverClass, uint serial) 63 | { 64 | var entity = _context.GetSerializerByClassName(serverClass.ClassName)?.Instantiate(serial); 65 | _deserializerMap[index] = entity; 66 | _entities[index] = new Dictionary(); 67 | if (serverClass.ClassName == "CCitadelPlayerPawn") 68 | { 69 | //_mappedEntities[index] = new CCitadelPlayerPawn(); 70 | } 71 | 72 | _metaData[index] = new EntityMetaData() 73 | { 74 | ClassName = serverClass.ClassName 75 | }; 76 | } 77 | 78 | public void DeleteEntity(int index) 79 | { 80 | _entities[index] = null!; 81 | _deserializerMap[index] = null!; 82 | _metaData[index] = null!; 83 | } 84 | 85 | public void UpdateAtIndex(int index, byte[] entityData) 86 | { 87 | var bb = new BitBuffer(entityData); 88 | UpdateAtIndex(index, ref bb); 89 | } 90 | 91 | public object UpdateAtIndex(int index, ref BitBuffer entityData) 92 | { 93 | List entityDataList = new(); 94 | var metaData = _metaData[index]; 95 | 96 | Span fieldPaths = stackalloc FieldPath[512]; 97 | var fp = FieldPath.Default; 98 | // Keep reading field paths until we reach an op with a null reader. 99 | // The null reader signifies `FieldPathEncodeFinish`. 100 | var fpi = 0; 101 | while (FieldPathEncoding.ReadFieldPathOp(ref entityData) is { Reader: { } reader }) 102 | { 103 | if (fpi == fieldPaths.Length) 104 | { 105 | var newArray = new FieldPath[fieldPaths.Length * 2]; 106 | fieldPaths.CopyTo(newArray); 107 | fieldPaths = newArray; 108 | } 109 | 110 | reader.Invoke(ref entityData, ref fp); 111 | fieldPaths[fpi++] = fp; 112 | } 113 | 114 | fieldPaths = fieldPaths[..fpi]; 115 | 116 | 117 | 118 | for (var idx = 0; idx < fieldPaths.Length; idx++) 119 | { 120 | var fieldPath = fieldPaths[idx]; 121 | var fieldHash = fieldPath.GetHash(metaData.ClassId); 122 | 123 | var pathSpan = fieldPath.AsSpan(); 124 | 125 | var deserializer = _deserializerMap[index]; 126 | FieldDecoder decoder = deserializer.GetFieldDecoder(pathSpan); 127 | var value = decoder.ReadValue(ref entityData); 128 | 129 | 130 | continue; 131 | if (metaData.ClassName == "CCitadelPlayerPawn") 132 | { 133 | var hash = fieldPath.GetHash(); 134 | string fieldName = null; 135 | EntityFieldData fieldData; 136 | if (_entities[index].TryGetValue(hash, out fieldData)) 137 | { 138 | if (string.IsNullOrEmpty(fieldData.FieldName)) 139 | deserializer.ReadFieldName(pathSpan, ref fieldName); 140 | fieldData.FieldValue = value; 141 | } 142 | else 143 | { 144 | deserializer.ReadFieldName(pathSpan, ref fieldName); 145 | } 146 | 147 | Console.WriteLine($"\t[{string.Join(",", pathSpan.ToArray())}] ({decoder}){fieldName}::{value}"); 148 | } 149 | } 150 | 151 | return null; 152 | } 153 | } -------------------------------------------------------------------------------- /DemLock.Parser/Events/DemoFrames.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser; 4 | 5 | public class DemoFrames 6 | { 7 | public Action? OnStop; 8 | public Action? OnFileHeader; 9 | public Action? OnClassInfo; 10 | public Action? OnSendTables; 11 | public Action? OnPacket; 12 | public Action? OnSignonPacket; 13 | public Action? OnFullPacket; 14 | 15 | internal DemoFrames() 16 | { 17 | } 18 | 19 | internal void ProcessFrame(FrameData frame) 20 | { 21 | switch (frame.Command) 22 | { 23 | case DemoFrameCommand.DEM_Stop: 24 | OnStop?.Invoke(); 25 | break; 26 | case DemoFrameCommand.DEM_FileHeader: 27 | OnFileHeader?.Invoke(CDemoFileHeader.Parser.ParseFrom(frame.Data)); 28 | break; 29 | case DemoFrameCommand.DEM_ClassInfo: 30 | OnClassInfo?.Invoke(CDemoClassInfo.Parser.ParseFrom(frame.Data)); 31 | break; 32 | case DemoFrameCommand.DEM_SendTables: 33 | OnSendTables?.Invoke(CDemoSendTables.Parser.ParseFrom(frame.Data)); 34 | break; 35 | case DemoFrameCommand.DEM_Packet: 36 | OnPacket?.Invoke(CDemoPacket.Parser.ParseFrom(frame.Data)); 37 | break; 38 | case DemoFrameCommand.DEM_SignonPacket: 39 | OnSignonPacket?.Invoke(CDemoPacket.Parser.ParseFrom(frame.Data)); 40 | break; 41 | case DemoFrameCommand.DEM_FullPacket: 42 | OnFullPacket?.Invoke(CDemoFullPacket.Parser.ParseFrom(frame.Data)); 43 | break; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /DemLock.Parser/Events/GameEvents.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser.Events; 4 | 5 | public class GameEvents 6 | { 7 | public Action? OnFireBullets; 8 | public Action? OnSource1LegacyGameEventList; 9 | public Action? OnSource1LegacyGameEvent; 10 | public Action? OnSosStartSoundEvent; 11 | public Action? OnSosStopSoundEvent; 12 | public Action? OnSosSetSoundEventParams; 13 | public Action? OnSosStopSoundEventHash; 14 | public Action? OnPlayerAnimEvent; 15 | public Action? OnParticleSystemManager; 16 | public Action? OnScreenTextPretty; 17 | public Action? OnServerRequestedTracer; 18 | public Action? OnBulletImpact; 19 | public Action? OnEnableSatVolumesEvent; 20 | public Action? OnPlaceSatVolumeEvent; 21 | public Action? OnDisableSatVolumesEvent; 22 | public Action? OnRemoveSatVolumeEvent; 23 | 24 | internal GameEvents(){} 25 | internal void HandleGameEventMessage(MessageTypes type, byte[] data) 26 | { 27 | switch (type) 28 | { 29 | case MessageTypes.GE_FireBullets: 30 | if (OnFireBullets != null) 31 | { 32 | var eventData = CMsgFireBullets.Parser.ParseFrom(data); 33 | OnFireBullets.Invoke(eventData); 34 | } 35 | break; 36 | case MessageTypes.GE_Source1LegacyGameEventList: 37 | if (OnSource1LegacyGameEventList != null) 38 | { 39 | var eventData = CMsgSource1LegacyGameEventList.Parser.ParseFrom(data); 40 | OnSource1LegacyGameEventList.Invoke( eventData); 41 | } 42 | break; 43 | case MessageTypes.GE_Source1LegacyGameEvent: 44 | if (OnSource1LegacyGameEvent != null) 45 | { 46 | var eventData = CMsgSource1LegacyGameEvent.Parser.ParseFrom(data); 47 | OnSource1LegacyGameEvent.Invoke(eventData); 48 | } 49 | break; 50 | case MessageTypes.GE_SosStartSoundEvent: 51 | if (OnSosStartSoundEvent != null) 52 | { 53 | var eventData = CMsgSosStartSoundEvent.Parser.ParseFrom(data); 54 | OnSosStartSoundEvent.Invoke(eventData); 55 | } 56 | break; 57 | case MessageTypes.GE_SosStopSoundEvent: 58 | if (OnSosStopSoundEvent != null) 59 | { 60 | var eventData = CMsgSosStopSoundEvent.Parser.ParseFrom(data); 61 | OnSosStopSoundEvent.Invoke(eventData); 62 | } 63 | break; 64 | case MessageTypes.GE_SosSetSoundEventParams: 65 | if (OnSosSetSoundEventParams != null) 66 | { 67 | var eventData = CMsgSosSetSoundEventParams.Parser.ParseFrom(data); 68 | OnSosSetSoundEventParams.Invoke(eventData); 69 | } 70 | break; 71 | case MessageTypes.GE_SosStopSoundEventHash: 72 | if (OnSosStopSoundEventHash != null) 73 | { 74 | var eventData = CMsgSosStopSoundEventHash.Parser.ParseFrom(data); 75 | OnSosStopSoundEventHash.Invoke(eventData); 76 | } 77 | break; 78 | case MessageTypes.GE_PlayerAnimEvent: 79 | if (OnPlayerAnimEvent != null) 80 | { 81 | var eventData = CMsgPlayerAnimEvent.Parser.ParseFrom(data); 82 | OnPlayerAnimEvent.Invoke(eventData); 83 | } 84 | break; 85 | case MessageTypes.GE_ParticleSystemManager: 86 | if (OnParticleSystemManager != null) 87 | { 88 | var eventData = CMsgParticleSystemManager.Parser.ParseFrom(data); 89 | OnParticleSystemManager.Invoke(eventData); 90 | } 91 | break; 92 | case MessageTypes.GE_ScreenTextPretty: 93 | if (OnScreenTextPretty != null) 94 | { 95 | var eventData = CMsgScreenTextPretty.Parser.ParseFrom(data); 96 | OnScreenTextPretty.Invoke(eventData); 97 | } 98 | break; 99 | case MessageTypes.GE_ServerRequestedTracer: 100 | if (OnServerRequestedTracer != null) 101 | { 102 | var eventData = CMsgServerRequestedTracer.Parser.ParseFrom(data); 103 | OnServerRequestedTracer.Invoke(eventData); 104 | } 105 | break; 106 | case MessageTypes.GE_BulletImpact: 107 | if (OnBulletImpact != null) 108 | { 109 | var eventData = CMsgBulletImpact.Parser.ParseFrom(data); 110 | OnBulletImpact.Invoke(eventData); 111 | } 112 | break; 113 | case MessageTypes.GE_EnableSatVolumesEvent: 114 | if (OnEnableSatVolumesEvent != null) 115 | { 116 | var eventData = CMsgEnableSatVolumesEvent.Parser.ParseFrom(data); 117 | OnEnableSatVolumesEvent.Invoke(eventData); 118 | } 119 | break; 120 | case MessageTypes.GE_PlaceSatVolumeEvent: 121 | if (OnPlaceSatVolumeEvent != null) 122 | { 123 | var eventData = CMsgPlaceSatVolumeEvent.Parser.ParseFrom(data); 124 | OnPlaceSatVolumeEvent.Invoke(eventData); 125 | } 126 | break; 127 | case MessageTypes.GE_DisableSatVolumesEvent: 128 | if (OnDisableSatVolumesEvent != null) 129 | { 130 | var eventData = CMsgDisableSatVolumesEvent.Parser.ParseFrom(data); 131 | OnDisableSatVolumesEvent.Invoke(eventData); 132 | } 133 | break; 134 | case MessageTypes.GE_RemoveSatVolumeEvent: 135 | if (OnRemoveSatVolumeEvent != null) 136 | { 137 | var eventData = CMsgRemoveSatVolumeEvent.Parser.ParseFrom(data); 138 | OnRemoveSatVolumeEvent.Invoke(eventData); 139 | } 140 | break; 141 | } 142 | } 143 | 144 | 145 | 146 | } -------------------------------------------------------------------------------- /DemLock.Parser/Events/NetMessages.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser.Events; 4 | 5 | public class NetMessages 6 | { 7 | 8 | public Action? OnTick; 9 | public Action? OnSetConVar; 10 | public Action? OnSignonState; 11 | public Action? OnSpawnGroupLoad; 12 | public Action? OnSpawnGroupSetCreationTick; 13 | internal NetMessages(){} 14 | internal void HandleNetMessage(MessageTypes type, byte[] data) 15 | { 16 | switch (type) 17 | { 18 | case MessageTypes.net_Tick: 19 | OnTick?.Invoke(CNETMsg_Tick.Parser.ParseFrom(data)); 20 | break; 21 | case MessageTypes.net_SetConVar: 22 | OnSetConVar?.Invoke(CNETMsg_SetConVar.Parser.ParseFrom(data)); 23 | break; 24 | case MessageTypes.net_SignonState: 25 | OnSignonState?.Invoke(CNETMsg_SignonState.Parser.ParseFrom(data)); 26 | break; 27 | case MessageTypes.net_SpawnGroup_Load: 28 | OnSpawnGroupLoad?.Invoke(CNETMsg_SpawnGroup_Load.Parser.ParseFrom(data)); 29 | break; 30 | case MessageTypes.net_SpawnGroup_SetCreationTick: 31 | OnSpawnGroupSetCreationTick?.Invoke(CNETMsg_SpawnGroup_SetCreationTick.Parser.ParseFrom(data)); 32 | break; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /DemLock.Parser/Events/PacketMessages.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser; 4 | 5 | public class PacketMessages 6 | { 7 | public Action? OnGameEvent; 8 | public Action? OnNetMessage; 9 | public Action? OnServiceMessage; 10 | public Action? OnUserMessage; 11 | 12 | /// 13 | /// Take in a byte array containing the data of a packet frame that has a series of messages in it 14 | /// that need to be processed 15 | /// 16 | /// 17 | internal void ProcessMessage(MessageTypes type, byte[] data) 18 | { 19 | switch (type) 20 | { 21 | case MessageTypes.net_Tick: 22 | case MessageTypes.net_SetConVar: 23 | case MessageTypes.net_SignonState: 24 | case MessageTypes.net_SpawnGroup_Load: 25 | case MessageTypes.net_SpawnGroup_SetCreationTick: 26 | OnNetMessage?.Invoke(type, data); 27 | break; 28 | 29 | case MessageTypes.svc_ServerInfo: 30 | case MessageTypes.svc_ClassInfo: 31 | case MessageTypes.svc_CreateStringTable: 32 | case MessageTypes.svc_UpdateStringTable: 33 | case MessageTypes.svc_VoiceInit: 34 | case MessageTypes.svc_ClearAllStringTables: 35 | case MessageTypes.svc_PacketEntities: 36 | case MessageTypes.svc_HLTVStatus: 37 | OnServiceMessage?.Invoke(type, data); 38 | break; 39 | 40 | case MessageTypes.UM_ParticleManager : 41 | case MessageTypes.UM_PlayResponseConditional: 42 | break; 43 | 44 | case MessageTypes.GE_Source1LegacyGameEventList: 45 | case MessageTypes.GE_Source1LegacyGameEvent: 46 | case MessageTypes.GE_SosStartSoundEvent: 47 | case MessageTypes.GE_SosStopSoundEvent: 48 | case MessageTypes.GE_SosSetSoundEventParams: 49 | case MessageTypes.GE_SosStopSoundEventHash: 50 | case MessageTypes.GE_FireBullets: 51 | case MessageTypes.GE_PlayerAnimEvent: 52 | case MessageTypes.GE_ParticleSystemManager: 53 | case MessageTypes.GE_ScreenTextPretty: 54 | case MessageTypes.GE_ServerRequestedTracer: 55 | case MessageTypes.GE_BulletImpact: 56 | case MessageTypes.GE_EnableSatVolumesEvent: 57 | case MessageTypes.GE_PlaceSatVolumeEvent: 58 | case MessageTypes.GE_DisableSatVolumesEvent: 59 | case MessageTypes.GE_RemoveSatVolumeEvent: 60 | OnGameEvent?.Invoke(type, data); 61 | break; 62 | 63 | case MessageTypes.k_EUserMsg_Damage: 64 | case MessageTypes.k_EUserMsg_MapPing: 65 | case MessageTypes.k_EUserMsg_TeamRewards: 66 | case MessageTypes.k_EUserMsg_AbilityFailed: 67 | case MessageTypes.k_EUserMsg_TriggerDamageFlash: 68 | case MessageTypes.k_EUserMsg_AbilitiesChanged: 69 | case MessageTypes.k_EUserMsg_RecentDamageSummary: 70 | case MessageTypes.k_EUserMsg_SpectatorTeamChanged: 71 | case MessageTypes.k_EUserMsg_ChatWheel: 72 | case MessageTypes.k_EUserMsg_GoldHistory: 73 | case MessageTypes.k_EUserMsg_ChatMsg: 74 | case MessageTypes.k_EUserMsg_QuickResponse: 75 | case MessageTypes.k_EUserMsg_PostMatchDetails: 76 | case MessageTypes.k_EUserMsg_ChatEvent: 77 | case MessageTypes.k_EUserMsg_AbilityInterrupted: 78 | case MessageTypes.k_EUserMsg_HeroKilled: 79 | case MessageTypes.k_EUserMsg_ReturnIdol: 80 | case MessageTypes.k_EUserMsg_SetClientCameraAngles: 81 | case MessageTypes.k_EUserMsg_MapLine: 82 | case MessageTypes.k_EUserMsg_BulletHit: 83 | case MessageTypes.k_EUserMsg_ObjectiveMask: 84 | case MessageTypes.k_EUserMsg_ModifierApplied: 85 | case MessageTypes.k_EUserMsg_CameraController: 86 | case MessageTypes.k_EUserMsg_AuraModifierApplied: 87 | case MessageTypes.k_EUserMsg_ObstructedShotFired: 88 | case MessageTypes.k_EUserMsg_AbilityLateFailure: 89 | case MessageTypes.k_EUserMsg_AbilityPing: 90 | case MessageTypes.k_EUserMsg_PostProcessingAnim: 91 | case MessageTypes.k_EUserMsg_DeathReplayData: 92 | case MessageTypes.k_EUserMsg_PlayerLifetimeStatInfo: 93 | case MessageTypes.k_EUserMsg_ForceShopClosed: 94 | case MessageTypes.k_EUserMsg_StaminaDrained: 95 | case MessageTypes.k_EUserMsg_AbilityNotify: 96 | case MessageTypes.k_EUserMsg_GetDamageStatsResponse: 97 | case MessageTypes.k_EUserMsg_ParticipantStartSoundEvent: 98 | case MessageTypes.k_EUserMsg_ParticipantStopSoundEvent: 99 | case MessageTypes.k_EUserMsg_ParticipantStopSoundEventHash: 100 | case MessageTypes.k_EUserMsg_ParticipantSetSoundEventParams: 101 | case MessageTypes.k_EUserMsg_ParticipantSetLibraryStackFields: 102 | case MessageTypes.k_EUserMsg_CurrencyChanged: 103 | case MessageTypes.k_EUserMsg_GameOver: 104 | case MessageTypes.k_EUserMsg_BossKilled: 105 | OnUserMessage?.Invoke(type, data); 106 | break; 107 | 108 | case MessageTypes.k_EEntityMsg_BreakablePropSpawnDebris: 109 | break; 110 | 111 | case MessageTypes.TE_EffectDispatchId: 112 | case MessageTypes.TE_ArmorRicochetId: 113 | case MessageTypes.TE_BeamEntPointId: 114 | case MessageTypes.TE_BeamEntsId: 115 | case MessageTypes.TE_BeamPointsId: 116 | case MessageTypes.TE_BeamRingId: 117 | case MessageTypes.TE_BSPDecalId: 118 | case MessageTypes.TE_BubblesId: 119 | case MessageTypes.TE_BubbleTrailId: 120 | case MessageTypes.TE_DecalId: 121 | case MessageTypes.TE_WorldDecalId: 122 | case MessageTypes.TE_EnergySplashId: 123 | case MessageTypes.TE_FizzId: 124 | case MessageTypes.TE_ShatterSurfaceId: 125 | case MessageTypes.TE_GlowSpriteId: 126 | case MessageTypes.TE_ImpactId: 127 | case MessageTypes.TE_MuzzleFlashId: 128 | case MessageTypes.TE_BloodStreamId: 129 | case MessageTypes.TE_ExplosionId: 130 | case MessageTypes.TE_DustId: 131 | case MessageTypes.TE_LargeFunnelId: 132 | case MessageTypes.TE_SparksId: 133 | case MessageTypes.TE_PhysicsPropId: 134 | case MessageTypes.TE_PlayerDecalId: 135 | case MessageTypes.TE_ProjectedDecalId: 136 | case MessageTypes.TE_SmokeId: 137 | break; 138 | default: 139 | Console.WriteLine($"\tUnhandled Message Type [{type}::{data.Length}]"); 140 | break; 141 | } 142 | }} -------------------------------------------------------------------------------- /DemLock.Parser/Events/ServiceMessages.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Parser.Events; 4 | 5 | public class ServiceMessages 6 | { 7 | 8 | 9 | public Action? OnServerInfo; 10 | public Action? OnClassInfo; 11 | public Action? OnCreateStringTable; 12 | public Action? OnUpdateStringTable; 13 | public Action? OnVoiceInit; 14 | public Action? OnClearAllStringTables; 15 | public Action? OnPacketEntities; 16 | public Action? OnHLTVStatus; 17 | 18 | internal ServiceMessages(){} 19 | 20 | internal void HandleServiceMessage(MessageTypes type, byte[] data) 21 | { 22 | switch (type) 23 | { 24 | case MessageTypes.svc_ServerInfo: 25 | break; 26 | case MessageTypes.svc_ClassInfo: 27 | break; 28 | case MessageTypes.svc_CreateStringTable: 29 | break; 30 | case MessageTypes.svc_UpdateStringTable: 31 | break; 32 | case MessageTypes.svc_VoiceInit: 33 | break; 34 | case MessageTypes.svc_ClearAllStringTables: 35 | break; 36 | case MessageTypes.svc_PacketEntities: 37 | break; 38 | case MessageTypes.svc_HLTVStatus: 39 | break; 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /DemLock.Parser/Fields/FieldMapping.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Fields; 2 | 3 | public abstract class MappedEntity 4 | { 5 | public abstract void BindFields(List fields, ref TResult target); 6 | 7 | } -------------------------------------------------------------------------------- /DemLock.Parser/FrameHandler.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Entities; 2 | using DemLock.Parser.Models; 3 | using DemLock.Utils; 4 | 5 | namespace DemLock.Parser; 6 | 7 | /// 8 | /// Wrapper for all frame processing operations within the demo parsing process 9 | /// 10 | public class FrameHandler 11 | { 12 | private readonly MessageHandler _messageHandler; 13 | private readonly DemoParserContext _context; 14 | 15 | public FrameHandler(MessageHandler messageHandler, DemoParserContext context) 16 | { 17 | _context = context; 18 | _messageHandler = messageHandler; 19 | } 20 | 21 | public void HandleFrame(FrameData frameData) 22 | { 23 | if(_context.Config.IgnoredFrames.Contains(frameData.Command)) return; 24 | 25 | switch (frameData.Command) 26 | { 27 | case DemoFrameCommand.DEM_Stop: return; 28 | case DemoFrameCommand.DEM_FileHeader: 29 | HandleFileHeader(frameData); 30 | break; 31 | case DemoFrameCommand.DEM_ClassInfo: 32 | HandleClassInfo(frameData); 33 | break; 34 | case DemoFrameCommand.DEM_SendTables: 35 | HandleSendTables(frameData); 36 | break; 37 | case DemoFrameCommand.DEM_Packet: 38 | HandlePacket(frameData); 39 | break; 40 | case DemoFrameCommand.DEM_SignonPacket: 41 | HandleSignonPacket(frameData); 42 | break; 43 | case DemoFrameCommand.DEM_FullPacket: 44 | HandleFullPacket(frameData); 45 | break; 46 | } 47 | } 48 | 49 | 50 | /// 51 | /// Handle a Full packet frame being received, this could likely be merged with handle packet, 52 | /// but for clarity wanted to keep them explicit 53 | /// 54 | /// The frame which contains the data to process 55 | private void HandleFullPacket(FrameData frameData) 56 | { 57 | var packet = CDemoFullPacket.Parser.ParseFrom(frameData.Data); 58 | BitStream bs = new BitStream(packet.Packet.Data.ToByteArray()); 59 | while (bs.BitsRemaining > 8) 60 | { 61 | var msgtype = (MessageTypes)bs.ReadUBit(); 62 | var msgSize = bs.ReadVarUInt32(); 63 | byte[] msgData = bs.ReadBytes(msgSize); 64 | _messageHandler.ProcessMessage(msgtype, msgData); 65 | } 66 | } 67 | /// 68 | /// Handle a signon packet frame ebing received, this could likely be merged with handle packet, 69 | /// but for clarity wanted to keep them explicit 70 | /// 71 | /// The frame which contains the data to process 72 | private void HandleSignonPacket(FrameData frameData) 73 | { 74 | var packet = CDemoPacket.Parser.ParseFrom(frameData.Data); 75 | BitStream bs = new BitStream(packet.Data.ToByteArray()); 76 | while (bs.BitsRemaining > 8) 77 | { 78 | var msgtype = (MessageTypes)bs.ReadUBit(); 79 | var msgSize = bs.ReadVarUInt32(); 80 | byte[] msgData = bs.ReadBytes(msgSize); 81 | _messageHandler.ProcessMessage(msgtype, msgData); 82 | } 83 | } 84 | 85 | private void HandleFileHeader(FrameData frameData) 86 | { 87 | var fileHeader = CDemoFileHeader.Parser.ParseFrom(frameData.Data); 88 | } 89 | 90 | private void HandlePacket(FrameData frameData) 91 | { 92 | var packet = CDemoPacket.Parser.ParseFrom(frameData.Data); 93 | BitStream bs = new BitStream(packet.Data.ToByteArray()); 94 | while (bs.BitsRemaining > 8) 95 | { 96 | var msgtype = (MessageTypes)bs.ReadUBit(); 97 | var msgSize = bs.ReadVarUInt32(); 98 | byte[] msgData = bs.ReadBytes(msgSize); 99 | _messageHandler.ProcessMessage(msgtype, msgData); 100 | } 101 | } 102 | 103 | /// 104 | /// Handle the class info message frame and update the corresponding tables 105 | /// 106 | /// 107 | private void HandleClassInfo(FrameData frameData) 108 | { 109 | CDemoClassInfo demClassInfo = CDemoClassInfo.Parser.ParseFrom(frameData.Data); 110 | 111 | foreach (var v in demClassInfo.Classes) 112 | { 113 | _context.AddClass(new DClass() 114 | { 115 | ClassId = v.ClassId, 116 | ClassName = v.NetworkName, 117 | }); 118 | } 119 | } 120 | 121 | private void HandleSendTables(FrameData frameData) 122 | { 123 | CDemoSendTables sendTable = CDemoSendTables.Parser.ParseFrom(frameData.Data); 124 | 125 | BitBuffer bs = new BitBuffer(sendTable.Data.ToByteArray()); 126 | 127 | Span byteArr = new Span(new byte[bs.ReadVarUInt32()]); 128 | bs.ReadBytes(byteArr); 129 | 130 | var msg = CSVCMsg_FlattenedSerializer.Parser.ParseFrom(byteArr); 131 | 132 | // Create the fields objects 133 | var symbols = msg.Symbols; 134 | var fields = msg.Fields.Select(field => 135 | { 136 | DField newField = new DField(_context); 137 | newField.SerializerVersion = field.FieldSerializerVersion; 138 | newField.Name = symbols[field.VarNameSym]; 139 | var fieldType = DFieldType.Parse(symbols[field.VarTypeSym]); 140 | newField.FieldType = fieldType; 141 | _context.AddFieldType(fieldType); 142 | newField.SendNode = symbols[field.SendNodeSym]; 143 | var varEncoder = field.HasVarEncoderSym 144 | ? symbols[field.VarEncoderSym] 145 | : null; 146 | if (field.HasFieldSerializerNameSym) 147 | newField.SerializerName = symbols[field.FieldSerializerNameSym]; 148 | else newField.SerializerName = string.Empty; 149 | newField.EncodingInfo = new FieldEncodingInfo() 150 | { 151 | VarEncoder = varEncoder, 152 | BitCount = field.BitCount, 153 | EncodeFlags = field.EncodeFlags, 154 | LowValue = field.HasLowValue ? field.LowValue : default(float?), 155 | HighValue = field.HasHighValue ? field.HighValue : default(float?) 156 | }; 157 | _context.AddField(newField); 158 | return newField; 159 | }).ToArray(); 160 | 161 | List serializers = msg.Serializers 162 | .Select(sz => 163 | { 164 | return new DSerializer() 165 | { 166 | Name = msg.Symbols[sz.SerializerNameSym], 167 | Version = sz.SerializerVersion, 168 | Fields = sz.FieldsIndex.Select(i => fields[i]).ToArray() 169 | }; 170 | }).ToList(); 171 | _context.AddSerializerRange(serializers); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /DemLock.Parser/HuffmanNode.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser; 2 | 3 | 4 | internal record HuffmanNode(T? Symbol, int Frequency, HuffmanNode? Left, HuffmanNode? Right) 5 | { 6 | public override string ToString() => Symbol is { } symbol 7 | ? $"{symbol} ({Frequency})" 8 | : $" ({Frequency})"; 9 | 10 | public static HuffmanNode Build(IEnumerable> symbolFreqs) 11 | { 12 | var queue = new PriorityQueue, NodePriority>(symbolFreqs 13 | .Select(kvp => KeyValuePair.Create(kvp.Key, Math.Max(1, kvp.Value))) 14 | .Select((kvp, i) => (new HuffmanNode(kvp.Key, kvp.Value, null, null), new NodePriority(kvp.Value, i)))); 15 | 16 | var i = queue.Count; 17 | while (queue.Count > 1) 18 | { 19 | var left = queue.Dequeue(); 20 | var right = queue.Dequeue(); 21 | var parent = new HuffmanNode(default, left.Frequency + right.Frequency, left, right); 22 | var priority = new NodePriority(left.Frequency + right.Frequency, i++); 23 | queue.Enqueue(parent, priority); 24 | } 25 | 26 | return queue.Dequeue(); 27 | } 28 | } 29 | 30 | internal readonly record struct NodePriority(int Weight, int Value) : IComparable 31 | { 32 | public int CompareTo(NodePriority other) => Weight == other.Weight 33 | ? other.Value.CompareTo(Value) 34 | : Weight.CompareTo(other.Weight); 35 | } 36 | -------------------------------------------------------------------------------- /DemLock.Parser/Models/DClass.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Entities; 2 | using DemLock.Utils; 3 | 4 | namespace DemLock.Parser.Models; 5 | 6 | /// 7 | /// Represents a class in the context of a demo file, it is given 8 | /// the D prefix simply to indicate this is vastly different from 9 | /// the built in class types 10 | /// 11 | public class DClass 12 | { 13 | public string? ClassName { get; set; } 14 | public int ClassId { get; set; } 15 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/DField.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Drawing; 3 | using DemLock.Entities; 4 | using DemLock.Entities.DefinedObjects; 5 | using DemLock.Entities.Primitives; 6 | 7 | namespace DemLock.Parser.Models; 8 | 9 | /// 10 | /// Represents a field on a demo class, and is used in the 11 | /// serializer to define how we are going to parse the entity 12 | /// based on it's class type 13 | /// 14 | public class DField 15 | { 16 | private DemoParserContext _context; 17 | 18 | public DField(DemoParserContext context) 19 | { 20 | _context = context; 21 | } 22 | 23 | public string? Name { get; set; } 24 | public DFieldType FieldType { get; set; } 25 | public string? SendNode { get; set; } 26 | public FieldEncodingInfo EncodingInfo { get; set; } 27 | public string SerializerName { get; set; } 28 | public int SerializerVersion { get; set; } 29 | 30 | private static int tmp = 0; 31 | 32 | public string MappingRule() 33 | { 34 | if (FieldType.GenericType != null) 35 | return "Class"; 36 | return $"Value"; 37 | } 38 | public string PropertyType() 39 | { 40 | if (FieldType.Name == "CHandle") 41 | return nameof(UInt32); 42 | 43 | if (FieldType.Count > 0 && FieldType.Name == "char") 44 | return nameof(String); 45 | 46 | // Manual override for simulation time data type, not sure why they don't have a type specified but this is how others do it 47 | if (Name == "m_flSimulationTime" || Name == "m_flAnimTime") 48 | return "float"; 49 | 50 | if (FieldType.Count > 0) 51 | return $"{MapTypeName(FieldType.Name)}[{FieldType.Count}] "; 52 | 53 | // If this has a generic, we will need to handle special cases 54 | if (FieldType.GenericType != null) 55 | { 56 | var childSerializer = _context.GetSerializerByClassName(FieldType.GenericType.Name); 57 | if (childSerializer == null) 58 | return $"List<{MapTypeName(FieldType.GenericType.Name)}>"; 59 | 60 | return $"List<{childSerializer.Name}>"; 61 | } 62 | // Generics will come with the serializer set to the serializer for their generic type 63 | // This will need to be packed and sen to the generic at some point to get generics working fully 64 | // For now we will just check that it's a generic to get through initial processing and come back to it later 65 | 66 | // If the serializer is named this is a nested entity we need to activate 67 | if (!string.IsNullOrWhiteSpace(SerializerName)) 68 | return $"{SerializerName}"; 69 | 70 | return MapTypeName(FieldType.Name); 71 | } 72 | 73 | public string MapTypeName(string typeName) 74 | { 75 | if (typeName == "float32") 76 | return "float"; 77 | if (typeName == "CNetworkedQuantizedFloat") 78 | return "float"; 79 | if (typeName == "uint16") 80 | return nameof(UInt16); 81 | if (typeName == "int16") 82 | return nameof(Int16); 83 | if (typeName == "CGameSceneNodeHandle") 84 | return nameof(UInt32); 85 | if (typeName == "QAngle") 86 | return "QAngle"; 87 | if (typeName == "CUtlStringToken") 88 | return nameof(UInt32); 89 | if (typeName == "bool") 90 | return "bool"; 91 | if (typeName == "uint64") 92 | return nameof(UInt64); 93 | if (typeName == "int8") 94 | return nameof(SByte); 95 | if (typeName == "uint8") 96 | return nameof(Byte); 97 | if (typeName == "int32") 98 | return nameof(Int32); 99 | if (typeName == "uint32") 100 | return nameof(UInt32); 101 | if (typeName == "Color") 102 | return nameof(Color); 103 | if (typeName == "GameTime_t") 104 | return "float"; 105 | if (typeName == "Vector2D") 106 | return "Vector2"; 107 | if (typeName == "Vector") 108 | return "Vector3"; 109 | if (typeName == "Vector4D") 110 | return "Vector4"; 111 | if (typeName == "HSequence") 112 | return nameof(UInt64); 113 | if (typeName == "CUtlSymbolLarge") 114 | return nameof(String); 115 | if (typeName == "CUtlString") 116 | return nameof(String); 117 | if(typeName == "CHandle") 118 | return nameof(UInt32); 119 | 120 | // Default to a UInt32 (basically just do what visit_ident is doing in haste) 121 | return $"Enum<{typeName}>"; 122 | } 123 | 124 | 125 | 126 | /// 127 | /// Get the activated field for this field template 128 | /// 129 | /// 130 | public FieldDecoder Activate() 131 | { 132 | if (FieldType.Name == "CHandle") 133 | return new DUInt32(); 134 | 135 | if (FieldType.Count > 0 && FieldType.Name == "char") 136 | return new DString(FieldType.Count); 137 | 138 | // Manual override for simulation time data type, not sure why they don't have a type specified but this is how others do it 139 | if (Name == "m_flSimulationTime" || Name == "m_flAnimTime") 140 | return new SimulationTime(_context.TickInterval); 141 | 142 | if (FieldType.Count > 0) 143 | return FieldDecoder.CreateFixedSizeArray(FieldType.Name, FieldType.Count, 144 | FieldDecoder.CreateObject(FieldType.Name, EncodingInfo)); 145 | 146 | 147 | // If this has a generic, we will need to handle special cases 148 | if (FieldType.GenericType != null) 149 | { 150 | var childSerializer = _context.GetSerializerByClassName(FieldType.GenericType.Name); 151 | if (childSerializer == null) 152 | return FieldDecoder.CreateGenericObject(FieldType.Name, FieldType.GenericType.Name, 153 | FieldDecoder.CreateObject(FieldType.GenericType.Name, EncodingInfo)); 154 | 155 | return FieldDecoder.CreateGenericObject(FieldType.Name, FieldType.GenericType.Name, 156 | childSerializer.Instantiate()); 157 | } 158 | // Generics will come with the serializer set to the serializer for their generic type 159 | // This will need to be packed and sen to the generic at some point to get generics working fully 160 | // For now we will just check that it's a generic to get through initial processing and come back to it later 161 | 162 | // If the serializer is named this is a nested entity we need to activate 163 | if (!string.IsNullOrWhiteSpace(SerializerName)) 164 | return _context.GetSerializerByClassName(SerializerName, SerializerVersion).Instantiate(); 165 | 166 | 167 | return FieldDecoder.CreateObject(FieldType.Name, EncodingInfo); 168 | } 169 | 170 | public override string ToString() 171 | { 172 | return $"{Name}::{SerializerName}"; 173 | } 174 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/DFieldType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace DemLock.Parser.Models; 6 | 7 | /// 8 | /// This is the field type containing information that will be used 9 | /// when constructing serializers, it is sent as part of the 10 | /// 11 | public class DFieldType 12 | { 13 | private static readonly ConcurrentDictionary _fieldTypeCache = new(); 14 | 15 | public string? Name { get; set; } 16 | public DFieldType? GenericType { get; set; } 17 | public bool IsPointer { get; set; } 18 | public int Count { get; set; } 19 | 20 | 21 | 22 | public static DFieldType Parse(string typeName) 23 | { 24 | if (_fieldTypeCache.TryGetValue(typeName, out var fieldType)) 25 | return fieldType; 26 | 27 | fieldType = ParseInternal(typeName); 28 | _fieldTypeCache[typeName] = fieldType; 29 | return fieldType; 30 | } 31 | 32 | private static DFieldType ParseInternal(string typeName) 33 | { 34 | Regex regex = new Regex(@"^(?[^\<\[\*]+)(\<\s(?.*)\s\>)?(?\*)?(\[(?.*)\])?$"); 35 | 36 | var match = regex.Match(typeName); 37 | if (!match.Success) 38 | throw new Exception($"Invalid field type: {typeName}"); 39 | 40 | var name = match.Groups["name"].Value; 41 | var genericParam = match.Groups["generic"] is { Success: true, Value: var genericName } 42 | ? Parse(genericName) 43 | : null; 44 | 45 | var isPointer = match.Groups["ptr"].Success; 46 | 47 | var count = match.Groups["count"] is { Success: true, Value: var countStr } 48 | ? int.Parse(countStr) 49 | : 0; 50 | 51 | return new DFieldType() 52 | { 53 | Name = name, 54 | GenericType = genericParam, 55 | IsPointer = isPointer, 56 | Count = count 57 | }; 58 | } 59 | 60 | public override string ToString() 61 | { 62 | StringBuilder sb = new StringBuilder(); 63 | sb.Append(Name); 64 | if(GenericType != null) 65 | sb.Append($"< {GenericType} >"); 66 | if (Count > 0) 67 | sb.Append($"[{Count}]"); 68 | return sb.ToString(); 69 | } 70 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/DSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Entities; 3 | 4 | namespace DemLock.Parser.Models; 5 | 6 | /// 7 | /// Represents a serializer that is sent in a packet on the demo file. 8 | /// 9 | /// This means that it is NOT meant to be a serializer for JSON of proto data. 10 | /// It defines how we should consume data that was sent for entity and class updates. 11 | /// 12 | public class DSerializer 13 | { 14 | public string Name { get; set; } 15 | public int Version { get; set; } 16 | public DField[] Fields { get; set; } 17 | private static Dictionary<(string, int), EntityDecoder> _serializerCache = new(); 18 | 19 | public EntityDecoder Instantiate() 20 | { 21 | if (_serializerCache.ContainsKey((Name, Version))) 22 | { 23 | return _serializerCache[(Name, Version)]; 24 | } 25 | EntityDecoder newEntityDecoder = new EntityDecoder(); 26 | newEntityDecoder.ClassName = this.Name; 27 | foreach (var field in this.Fields) 28 | { 29 | newEntityDecoder.AddField(field.Activate(),field.Name); 30 | } 31 | _serializerCache.Add((Name, Version), newEntityDecoder); 32 | return newEntityDecoder; 33 | } 34 | 35 | public EntityDecoder Instantiate(uint serial) 36 | { 37 | if (_serializerCache.ContainsKey((Name, Version))) 38 | { 39 | return _serializerCache[(Name, Version)]; 40 | } 41 | 42 | EntityDecoder newEntityDecoder = new EntityDecoder(); 43 | newEntityDecoder.ClassName = Name; 44 | newEntityDecoder.Serial = serial; 45 | foreach (var field in this.Fields) 46 | { 47 | newEntityDecoder.AddField(field.Activate(),field.Name); 48 | } 49 | _serializerCache.Add((Name, Version), newEntityDecoder); 50 | return newEntityDecoder; 51 | } 52 | public override string ToString() 53 | { 54 | return $"{Name}:{Version}"; 55 | } 56 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/DemoFrameCommand.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models; 2 | 3 | /// 4 | /// Enum for mapping the command for a demo frame to a known type for further sorting 5 | /// and processing later from the extracted command 6 | /// 7 | public enum DemoFrameCommand 8 | { 9 | // ReSharper disable InconsistentNaming 10 | DEM_Error = -1, 11 | DEM_Stop = 0, 12 | DEM_FileHeader = 1, 13 | DEM_FileInfo = 2, 14 | DEM_SyncTick = 3, 15 | DEM_SendTables = 4, 16 | /// 17 | /// ID for the class info packet. 18 | /// 19 | /// packet contains information about the classes that we will need to 20 | /// deserialize as part of entity processing. 21 | /// 22 | /// That is to say, we are given a bunch of type definitions so that we 23 | /// do not need to reference changing versions of the game files 24 | /// when we process the demo. 25 | /// 26 | DEM_ClassInfo = 5, 27 | DEM_StringTables = 6, 28 | DEM_Packet = 7, 29 | DEM_SignonPacket = 8, 30 | DEM_ConsoleCmd = 9, 31 | DEM_CustomData = 10, 32 | DEM_CustomDataCallbacks = 11, 33 | DEM_UserCmd = 12, 34 | DEM_FullPacket = 13, 35 | DEM_SaveGame = 14, 36 | DEM_SpawnGroups = 15, 37 | DEM_AnimationData = 16, 38 | DEM_AnimationHeader = 17, 39 | DEM_Max = 18, 40 | DEM_IsCompressed = 64 41 | // ReSharper restore InconsistentNaming 42 | 43 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/DemoMessage.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models; 2 | 3 | /// 4 | /// Represents a message from the demo, this is prior to converting 5 | /// it to the deserialized type and is the raw message extracted from the 6 | /// frame containing it 7 | /// 8 | public class DemoMessage 9 | { 10 | 11 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/FrameData.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models; 2 | 3 | /// 4 | /// Represents an extracted frame from the demo file 5 | /// 6 | public class FrameData 7 | { 8 | public DemoFrameCommand Command { get; set; } 9 | public bool IsCompressed { get; set; } 10 | public uint Tick { get; set; } 11 | public uint Size { get; set; } 12 | public byte[] Data { get; set; } 13 | 14 | } -------------------------------------------------------------------------------- /DemLock.Parser/Models/GameTick.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace DemLock.Parser.Models; 4 | 5 | /// 6 | /// Ticks represent the smallest simulation step of the game. 7 | /// CS2 is hardcoded to 64 ticks per second (tickrate). 8 | /// 9 | /// Tick number. 10 | /// 11 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 12 | public readonly record struct GameTick(uint Value) : IComparable 13 | { 14 | // CS2 is hardcoded as 64-tick 15 | public GameTime ToGameTime() => new(Value / 64.0f); 16 | 17 | public int CompareTo(GameTick other) => Value.CompareTo(other.Value); 18 | 19 | public override string ToString() => Value.ToString(); 20 | 21 | private string DebuggerDisplay => $"Tick {Value} ({ToGameTime()})"; 22 | 23 | public static GameTick operator +(GameTick tick, int ticks) => new((uint)(tick.Value + ticks)); 24 | public static GameTick operator -(GameTick tick, int ticks) => new((uint)(tick.Value - ticks)); 25 | 26 | public static GameTick operator +(GameTick tick, TimeSpan duration) => new((uint)(tick.Value + duration.TotalSeconds / 64.0)); 27 | public static GameTick operator -(GameTick tick, TimeSpan duration) => new((uint)(tick.Value - duration.TotalSeconds / 64.0)); 28 | 29 | public static GameTick operator +(GameTick tick, GameTick other) => new(tick.Value + other.Value); 30 | public static GameTick operator -(GameTick tick, GameTick other) => new(tick.Value - other.Value); 31 | 32 | public static bool operator <(GameTick left, GameTick right) => left.Value < right.Value; 33 | public static bool operator <=(GameTick left, GameTick right) => left.Value <= right.Value; 34 | public static bool operator >(GameTick left, GameTick right) => left.Value > right.Value; 35 | public static bool operator >=(GameTick left, GameTick right) => left.Value >= right.Value; 36 | } 37 | -------------------------------------------------------------------------------- /DemLock.Parser/Models/GameTime.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models; 2 | 3 | public readonly record struct GameTime(float Value) : IComparable 4 | { 5 | public TimeSpan ToTimeSpan() => TimeSpan.FromSeconds(Value); 6 | 7 | public int CompareTo(GameTime other) => Value.CompareTo(other.Value); 8 | 9 | public override string ToString() => ToTimeSpan().ToString(); 10 | 11 | public static GameTime operator +(GameTime time, TimeSpan duration) => new(time.Value + (float)duration.TotalSeconds); 12 | public static GameTime operator -(GameTime time, TimeSpan duration) => new(time.Value - (float)duration.TotalSeconds); 13 | } 14 | -------------------------------------------------------------------------------- /DemLock.Parser/Models/MessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models; 2 | 3 | /// 4 | /// Enum containing the mapping for ints to the message type so that 5 | /// parsed messages can be properly mapped to the handler function 6 | /// 7 | public enum MessageTypes 8 | { 9 | // ReSharper disable InconsistentNaming 10 | 11 | 12 | net_Tick = 4, 13 | net_SetConVar = 6, 14 | net_SignonState = 7, 15 | net_SpawnGroup_Load = 8, 16 | net_SpawnGroup_SetCreationTick = 11, 17 | 18 | svc_ServerInfo = 40, 19 | svc_ClassInfo = 42, 20 | svc_CreateStringTable = 44, 21 | svc_UpdateStringTable = 45, 22 | svc_VoiceInit = 46, 23 | svc_ClearAllStringTables = 51, 24 | svc_PacketEntities = 55, 25 | svc_HLTVStatus = 62, 26 | 27 | UM_ParticleManager = 145, 28 | UM_PlayResponseConditional = 166, 29 | 30 | GE_Source1LegacyGameEventList = 205, 31 | GE_Source1LegacyGameEvent = 207, 32 | GE_SosStartSoundEvent = 208, 33 | GE_SosStopSoundEvent = 209, 34 | GE_SosSetSoundEventParams = 210, 35 | GE_SosStopSoundEventHash = 212, 36 | GE_FireBullets = 450, 37 | GE_PlayerAnimEvent = 451, 38 | GE_ParticleSystemManager = 458, 39 | GE_ScreenTextPretty = 459, 40 | GE_ServerRequestedTracer = 460, 41 | GE_BulletImpact = 461, 42 | GE_EnableSatVolumesEvent = 462, 43 | GE_PlaceSatVolumeEvent = 463, 44 | GE_DisableSatVolumesEvent = 464, 45 | GE_RemoveSatVolumeEvent = 465, 46 | 47 | k_EUserMsg_Damage = 300, 48 | k_EUserMsg_MapPing = 303, 49 | k_EUserMsg_TeamRewards = 304, 50 | k_EUserMsg_AbilityFailed = 306, 51 | k_EUserMsg_TriggerDamageFlash = 308, 52 | k_EUserMsg_AbilitiesChanged = 309, 53 | k_EUserMsg_RecentDamageSummary = 310, 54 | k_EUserMsg_SpectatorTeamChanged = 311, 55 | k_EUserMsg_ChatWheel = 312, 56 | k_EUserMsg_GoldHistory = 313, 57 | k_EUserMsg_ChatMsg = 314, 58 | k_EUserMsg_QuickResponse = 315, 59 | k_EUserMsg_PostMatchDetails = 316, 60 | k_EUserMsg_ChatEvent = 317, 61 | k_EUserMsg_AbilityInterrupted = 318, 62 | k_EUserMsg_HeroKilled = 319, 63 | k_EUserMsg_ReturnIdol = 320, 64 | k_EUserMsg_SetClientCameraAngles = 321, 65 | k_EUserMsg_MapLine = 322, 66 | k_EUserMsg_BulletHit = 323, 67 | k_EUserMsg_ObjectiveMask = 324, 68 | k_EUserMsg_ModifierApplied = 325, 69 | k_EUserMsg_CameraController = 326, 70 | k_EUserMsg_AuraModifierApplied = 327, 71 | k_EUserMsg_ObstructedShotFired = 329, 72 | k_EUserMsg_AbilityLateFailure = 330, 73 | k_EUserMsg_AbilityPing = 331, 74 | k_EUserMsg_PostProcessingAnim = 332, 75 | k_EUserMsg_DeathReplayData = 333, 76 | k_EUserMsg_PlayerLifetimeStatInfo = 334, 77 | k_EUserMsg_ForceShopClosed = 336, 78 | k_EUserMsg_StaminaDrained = 337, 79 | k_EUserMsg_AbilityNotify = 338, 80 | k_EUserMsg_GetDamageStatsResponse = 339, 81 | k_EUserMsg_ParticipantStartSoundEvent = 340, 82 | k_EUserMsg_ParticipantStopSoundEvent = 341, 83 | k_EUserMsg_ParticipantStopSoundEventHash = 342, 84 | k_EUserMsg_ParticipantSetSoundEventParams = 343, 85 | k_EUserMsg_ParticipantSetLibraryStackFields = 344, 86 | k_EUserMsg_CurrencyChanged = 345, 87 | k_EUserMsg_GameOver = 346, 88 | k_EUserMsg_BossKilled = 347, 89 | k_EEntityMsg_BreakablePropSpawnDebris = 500, 90 | 91 | TE_EffectDispatchId = 400, 92 | TE_ArmorRicochetId = 401, 93 | TE_BeamEntPointId = 402, 94 | TE_BeamEntsId = 403, 95 | TE_BeamPointsId = 404, 96 | TE_BeamRingId = 405, 97 | TE_BSPDecalId = 407, 98 | TE_BubblesId = 408, 99 | TE_BubbleTrailId = 409, 100 | TE_DecalId = 410, 101 | TE_WorldDecalId = 411, 102 | TE_EnergySplashId = 412, 103 | TE_FizzId = 413, 104 | TE_ShatterSurfaceId = 414, 105 | TE_GlowSpriteId = 415, 106 | TE_ImpactId = 416, 107 | TE_MuzzleFlashId = 417, 108 | TE_BloodStreamId = 418, 109 | TE_ExplosionId = 419, 110 | TE_DustId = 420, 111 | TE_LargeFunnelId = 421, 112 | TE_SparksId = 422, 113 | TE_PhysicsPropId = 423, 114 | TE_PlayerDecalId = 424, 115 | TE_ProjectedDecalId = 425, 116 | TE_SmokeId = 426, 117 | 118 | // ReSharper enable InconsistentNaming 119 | } 120 | -------------------------------------------------------------------------------- /DemLock.Parser/Models/StringTable.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DemLock.Parser.Models.StringTableEntries; 3 | using DemLock.Utils; 4 | using Snappier; 5 | 6 | namespace DemLock.Parser.Models; 7 | 8 | public class StringTable 9 | { 10 | public string Name { get; set; } = string.Empty; 11 | public bool UserDataFixedSize { get; set; } 12 | public bool UsingVarintBitCounts { get; set; } 13 | public int UserDataSize { get; set; } 14 | public int Flags { get; set; } 15 | public int EntryCount => _data.Count; 16 | 17 | /// 18 | /// Dictionary for storing our entries, even though entries contain index number, this will 19 | /// make it easier to target specific indexes (can't be dicked to use a normal array) 20 | /// 21 | private readonly Dictionary _data = new(); 22 | 23 | /// 24 | /// Get the key for the entry provided it exists, otherwise return back an empty string 25 | /// NOTE: Should this be null return in that case? 26 | /// 27 | /// 28 | /// 29 | public string? KeyAtEntry(int i) 30 | { 31 | if (_data.ContainsKey(i)) 32 | return _data[i].Key; 33 | 34 | return string.Empty; 35 | } 36 | 37 | public void SetIndex(int index, string? key, byte[] data) 38 | { 39 | if (!_data.ContainsKey(index)) 40 | { 41 | _data.Add(index,StringTableEntry.CreateNewEntry(Name,index, key, data)); 42 | return; 43 | } 44 | 45 | var existingEntry = _data[index]; 46 | 47 | if(!String.IsNullOrWhiteSpace(key) && existingEntry.Key != key) 48 | existingEntry.Key = key; 49 | 50 | if(data.Length > 0) 51 | existingEntry.SetValue(data); 52 | } 53 | 54 | // TODO: update this to be a indexor override so it looks like I know what I'm doing 55 | public IEnumerable GetEntries() 56 | { 57 | foreach(var entry in _data) 58 | yield return entry.Value; 59 | } 60 | public void Update(byte[] rawData, int numberChanges) 61 | { 62 | ParseBuffer(rawData, numberChanges); 63 | } 64 | 65 | private void ParseBuffer(byte[] rawdata, int numberChanges) 66 | { 67 | var data = new BitStream(rawdata); 68 | List keys = new List(); 69 | int index = -1; 70 | if (data.BitsRemaining == 0) return; 71 | 72 | for (int i = 0; i < numberChanges; i++) 73 | { 74 | var key = ""; 75 | var value = new byte[0]; 76 | 77 | if (data.ReadBoolean()) index++; 78 | else index = (int)data.ReadVarUInt32() + 1; 79 | 80 | // Check if it has a key or not 81 | if (data.ReadBoolean()) 82 | { 83 | if (data.ReadBoolean()) 84 | { 85 | var pos = data.ReadBitsToUint(5); 86 | var size = data.ReadBitsToUint(5); 87 | 88 | if (pos >= keys.Count) 89 | key += data.ReadString(); 90 | else 91 | { 92 | var s = keys[(int)pos]; 93 | if (size > s.Length) 94 | { 95 | key += s + data.ReadString(); 96 | } 97 | else 98 | { 99 | key += (s?.Substring(0, (int)size) ?? "") + data.ReadString(); 100 | } 101 | } 102 | } 103 | else 104 | key = data.ReadString(); 105 | } 106 | else 107 | key = KeyAtEntry(index); 108 | 109 | if (!keys.Contains(key)) 110 | keys.Add(key); 111 | 112 | var hasValue = data.ReadBoolean(); 113 | if (hasValue) 114 | { 115 | uint bitSize = 0; 116 | var isCompressed = false; 117 | 118 | if (UserDataFixedSize) 119 | bitSize = (uint)UserDataSize; 120 | else 121 | { 122 | if ((Flags & 0x1) != 0) 123 | isCompressed = data.ReadBoolean(); 124 | if (UsingVarintBitCounts) 125 | bitSize = data.ReadUBit() * 8; 126 | else 127 | bitSize = data.ReadBitsToUint(17) * 8; 128 | } 129 | value = data.ReadToByteArray((uint)bitSize); 130 | if (isCompressed) 131 | value = Snappy.DecompressToArray(value); 132 | } 133 | SetIndex(index, key, value); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DemLock.Parser/Models/StringTableEntries/StringTableEntry.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser.Models.StringTableEntries; 2 | 3 | public abstract class StringTableEntry 4 | { 5 | public StringTableEntry() 6 | { 7 | } 8 | 9 | public StringTableEntry(int index, string key, byte[] value) 10 | { 11 | Index = index; 12 | Key = key; 13 | Value = value; 14 | } 15 | 16 | public int Index { get; set; } 17 | public string Key { get; set; } 18 | public byte[] Value { get; set; } 19 | 20 | /// 21 | /// Factory method that will create a new entry based on the table type since we want to be able to 22 | /// use abstraction to make it easier and tighter to identify entries specific data later 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | public static StringTableEntry CreateNewEntry(string tableName, int index, string key, byte[] value) 30 | { 31 | // Switch statement that will produce the correct entry based on the table name 32 | switch (tableName) 33 | { 34 | case "ActiveModifiers": 35 | return new ActiveModifierTableEntry(index, key, value); 36 | case "userinfo": 37 | return new UserInfoTableEntry(index, key, value); 38 | } 39 | 40 | // If we don't have a proper factory method, just return the generic structure 41 | return new GenericTableEntry(index, key, value); 42 | } 43 | 44 | public abstract void SetValue(byte[] data); 45 | } 46 | 47 | public class GenericTableEntry : StringTableEntry 48 | { 49 | public GenericTableEntry(int index, string key, byte[] value) 50 | { 51 | Index = index; 52 | Key = key; 53 | SetValue(value); 54 | } 55 | 56 | public sealed override void SetValue(byte[] data) 57 | { 58 | Value = data; 59 | } 60 | } 61 | 62 | public class ActiveModifierTableEntry : StringTableEntry 63 | { 64 | private CModifierTableEntry _parsedData; 65 | 66 | #region Mapped Values 67 | 68 | public MODIFIER_ENTRY_TYPE EntryType => _parsedData.EntryType; 69 | public uint Parent => _parsedData.Parent; 70 | public uint SerialNumber => _parsedData.SerialNumber; 71 | public uint ModifierSubclass => _parsedData.ModifierSubclass; 72 | public int StackCount => _parsedData.StackCount; 73 | public int MaxStackCount => _parsedData.MaxStackCount; 74 | public float LastAppliedTime => _parsedData.LastAppliedTime; 75 | public float Duration => _parsedData.Duration; 76 | public uint Caster => _parsedData.Caster; 77 | public uint Ability => _parsedData.Ability; 78 | public int AuraProviderSerialNumber => _parsedData.AuraProviderSerialNumber; 79 | public object AuraProviderEhandle => _parsedData.AuraProviderEhandle; 80 | public object AbilitySubclass => _parsedData.AbilitySubclass; 81 | public object Bool1 => _parsedData.Bool1; 82 | public object Bool2 => _parsedData.Bool2; 83 | public object Bool3 => _parsedData.Bool3; 84 | public object Bool4 => _parsedData.Bool4; 85 | public object Int1 => _parsedData.Int1; 86 | public object Int2 => _parsedData.Int2; 87 | public object Int3 => _parsedData.Int3; 88 | public object Int4 => _parsedData.Int4; 89 | public object Float1 => _parsedData.Float1; 90 | public object Float2 => _parsedData.Float2; 91 | public object Float3 => _parsedData.Float3; 92 | public object Float4 => _parsedData.Float4; 93 | public object Float5 => _parsedData.Float5; 94 | public object Float6 => _parsedData.Float6; 95 | public object Float7 => _parsedData.Float7; 96 | public object Float8 => _parsedData.Float8; 97 | public object Float9 => _parsedData.Float9; 98 | public object Float10 => _parsedData.Float10; 99 | public object Uint1 => _parsedData.Uint1; 100 | public object Uint2 => _parsedData.Uint2; 101 | public object Uint3 => _parsedData.Uint3; 102 | public object Uint4 => _parsedData.Uint4; 103 | public object Vec1 => _parsedData.Vec1; 104 | public object Vec2 => _parsedData.Vec2; 105 | public object Vec3 => _parsedData.Vec3; 106 | public object Vec4 => _parsedData.Vec4; 107 | public object String1 => _parsedData.String1; 108 | public object String2 => _parsedData.String2; 109 | public object String3 => _parsedData.String3; 110 | public object String4 => _parsedData.String4; 111 | 112 | #endregion 113 | 114 | public ActiveModifierTableEntry(int index, string key, byte[] value) : base(index, key, value) 115 | { 116 | _parsedData = CModifierTableEntry.Parser.ParseFrom(value); 117 | } 118 | 119 | public override void SetValue(byte[] data) 120 | { 121 | _parsedData = CModifierTableEntry.Parser.ParseFrom(data); 122 | Value = data; 123 | } 124 | } 125 | 126 | public class UserInfoTableEntry : StringTableEntry 127 | { 128 | private CMsgPlayerInfo _parsedData; 129 | public string Name => _parsedData.Name; 130 | public ulong Xuid => _parsedData.Xuid; 131 | public int Userid => _parsedData.Userid; 132 | public ulong Steamid => _parsedData.Steamid; 133 | public bool Fakeplayer => _parsedData.Fakeplayer; 134 | public bool Ishltv => _parsedData.Ishltv; 135 | 136 | public UserInfoTableEntry(int index, string key, byte[] value) : base(index, key, value) 137 | { 138 | _parsedData = CMsgPlayerInfo.Parser.ParseFrom(value); 139 | } 140 | 141 | public override void SetValue(byte[] data) 142 | { 143 | _parsedData = CMsgPlayerInfo.Parser.ParseFrom(data); 144 | } 145 | } -------------------------------------------------------------------------------- /DemLock.Parser/Monitoring.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Parser; 2 | 3 | public static class Monitoring 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /DemLock.Parser/proto/SteammessagesUnifiedBaseSteamworkssdk.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by the protocol buffer compiler. DO NOT EDIT! 3 | // source: steammessages_unified_base.steamworkssdk.proto 4 | // 5 | #pragma warning disable 1591, 0612, 3021, 8981 6 | #region Designer generated code 7 | 8 | using pb = global::Google.Protobuf; 9 | using pbc = global::Google.Protobuf.Collections; 10 | using pbr = global::Google.Protobuf.Reflection; 11 | using scg = global::System.Collections.Generic; 12 | /// Holder for reflection information generated from steammessages_unified_base.steamworkssdk.proto 13 | public static partial class SteammessagesUnifiedBaseSteamworkssdkReflection { 14 | 15 | #region Descriptor 16 | /// File descriptor for steammessages_unified_base.steamworkssdk.proto 17 | public static pbr::FileDescriptor Descriptor { 18 | get { return descriptor; } 19 | } 20 | private static pbr::FileDescriptor descriptor; 21 | 22 | static SteammessagesUnifiedBaseSteamworkssdkReflection() { 23 | byte[] descriptorData = global::System.Convert.FromBase64String( 24 | string.Concat( 25 | "Ci5zdGVhbW1lc3NhZ2VzX3VuaWZpZWRfYmFzZS5zdGVhbXdvcmtzc2RrLnBy", 26 | "b3RvGiBnb29nbGUvcHJvdG9idWYvZGVzY3JpcHRvci5wcm90bypdChNFUHJv", 27 | "dG9FeGVjdXRpb25TaXRlEiAKHGtfRVByb3RvRXhlY3V0aW9uU2l0ZVVua25v", 28 | "d24QABIkCiBrX0VQcm90b0V4ZWN1dGlvblNpdGVTdGVhbUNsaWVudBADOjQK", 29 | "C2Rlc2NyaXB0aW9uEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjQ", 30 | "hgMgASgJOj4KE3NlcnZpY2VfZGVzY3JpcHRpb24SHy5nb29nbGUucHJvdG9i", 31 | "dWYuU2VydmljZU9wdGlvbnMY0IYDIAEoCTp1ChZzZXJ2aWNlX2V4ZWN1dGlv", 32 | "bl9zaXRlEh8uZ29vZ2xlLnByb3RvYnVmLlNlcnZpY2VPcHRpb25zGNiGAyAB", 33 | "KA4yFC5FUHJvdG9FeGVjdXRpb25TaXRlOhxrX0VQcm90b0V4ZWN1dGlvblNp", 34 | "dGVVbmtub3duOjwKEm1ldGhvZF9kZXNjcmlwdGlvbhIeLmdvb2dsZS5wcm90", 35 | "b2J1Zi5NZXRob2RPcHRpb25zGNCGAyABKAk6OAoQZW51bV9kZXNjcmlwdGlv", 36 | "bhIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9ucxjQhgMgASgJOkMKFmVu", 37 | "dW1fdmFsdWVfZGVzY3JpcHRpb24SIS5nb29nbGUucHJvdG9idWYuRW51bVZh", 38 | "bHVlT3B0aW9ucxjQhgMgASgJQgVIAYABAA==")); 39 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, 40 | new pbr::FileDescriptor[] { global::Google.Protobuf.Reflection.DescriptorReflection.Descriptor, }, 41 | new pbr::GeneratedClrTypeInfo(new[] {typeof(global::EProtoExecutionSite), }, new pb::Extension[] { SteammessagesUnifiedBaseSteamworkssdkExtensions.Description, SteammessagesUnifiedBaseSteamworkssdkExtensions.ServiceDescription, SteammessagesUnifiedBaseSteamworkssdkExtensions.ServiceExecutionSite, SteammessagesUnifiedBaseSteamworkssdkExtensions.MethodDescription, SteammessagesUnifiedBaseSteamworkssdkExtensions.EnumDescription, SteammessagesUnifiedBaseSteamworkssdkExtensions.EnumValueDescription }, null)); 42 | } 43 | #endregion 44 | 45 | } 46 | /// Holder for extension identifiers generated from the top level of steammessages_unified_base.steamworkssdk.proto 47 | public static partial class SteammessagesUnifiedBaseSteamworkssdkExtensions { 48 | public static readonly pb::Extension Description = 49 | new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); 50 | public static readonly pb::Extension ServiceDescription = 51 | new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); 52 | public static readonly pb::Extension ServiceExecutionSite = 53 | new pb::Extension(50008, pb::FieldCodec.ForEnum(400064, x => (int) x, x => (global::EProtoExecutionSite) x, global::EProtoExecutionSite.KEprotoExecutionSiteUnknown)); 54 | public static readonly pb::Extension MethodDescription = 55 | new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); 56 | public static readonly pb::Extension EnumDescription = 57 | new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); 58 | public static readonly pb::Extension EnumValueDescription = 59 | new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); 60 | } 61 | 62 | #region Enums 63 | public enum EProtoExecutionSite { 64 | [pbr::OriginalName("k_EProtoExecutionSiteUnknown")] KEprotoExecutionSiteUnknown = 0, 65 | [pbr::OriginalName("k_EProtoExecutionSiteSteamClient")] KEprotoExecutionSiteSteamClient = 3, 66 | } 67 | 68 | #endregion 69 | 70 | 71 | #endregion Designer generated code 72 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/Valveextensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by the protocol buffer compiler. DO NOT EDIT! 3 | // source: valveextensions.proto 4 | // 5 | #pragma warning disable 1591, 0612, 3021, 8981 6 | #region Designer generated code 7 | 8 | using pb = global::Google.Protobuf; 9 | using pbc = global::Google.Protobuf.Collections; 10 | using pbr = global::Google.Protobuf.Reflection; 11 | using scg = global::System.Collections.Generic; 12 | /// Holder for reflection information generated from valveextensions.proto 13 | public static partial class ValveextensionsReflection { 14 | 15 | #region Descriptor 16 | /// File descriptor for valveextensions.proto 17 | public static pbr::FileDescriptor Descriptor { 18 | get { return descriptor; } 19 | } 20 | private static pbr::FileDescriptor descriptor; 21 | 22 | static ValveextensionsReflection() { 23 | byte[] descriptorData = global::System.Convert.FromBase64String( 24 | string.Concat( 25 | "ChV2YWx2ZWV4dGVuc2lvbnMucHJvdG8aIGdvb2dsZS9wcm90b2J1Zi9kZXNj", 26 | "cmlwdG9yLnByb3RvOj8KD3ZhbHZlX21hcF9maWVsZBIdLmdvb2dsZS5wcm90", 27 | "b2J1Zi5GaWVsZE9wdGlvbnMYyNwDIAEoCDoFZmFsc2U6PQoNdmFsdmVfbWFw", 28 | "X2tleRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMYydwDIAEoCDoF", 29 | "ZmFsc2U6PQoRZGlmZl9lbmNvZGVfZmllbGQSHS5nb29nbGUucHJvdG9idWYu", 30 | "RmllbGRPcHRpb25zGMrcAyABKAU6ATA6PAoMZGVsdGFfaWdub3JlEh0uZ29v", 31 | "Z2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjL3AMgASgIOgVmYWxzZTo/ChNz", 32 | "dGVhbW1sX21heF9lbnRyaWVzEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0", 33 | "aW9ucxjM3AMgASgNOgEwOkQKFHN0ZWFtbWxfaXNfdGltZXN0YW1wEh0uZ29v", 34 | "Z2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjN3AMgASgIOgVmYWxzZTo8ChBz", 35 | "dGVhbWxlYXJuX2NvdW50Eh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9u", 36 | "cxjO3AMgASgNOgEwOkAKFHNjaGVtYV9mcmllbmRseV9uYW1lEiEuZ29vZ2xl", 37 | "LnByb3RvYnVmLkVudW1WYWx1ZU9wdGlvbnMY6AcgASgJOj4KEnNjaGVtYV9k", 38 | "ZXNjcmlwdGlvbhIhLmdvb2dsZS5wcm90b2J1Zi5FbnVtVmFsdWVPcHRpb25z", 39 | "GOkHIAEoCTpGChpzY2hlbWFfc3VwcHJlc3NfZW51bWVyYXRvchIhLmdvb2ds", 40 | "ZS5wcm90b2J1Zi5FbnVtVmFsdWVPcHRpb25zGOoHIAEoCA==")); 41 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, 42 | new pbr::FileDescriptor[] { global::Google.Protobuf.Reflection.DescriptorReflection.Descriptor, }, 43 | new pbr::GeneratedClrTypeInfo(null, new pb::Extension[] { ValveextensionsExtensions.ValveMapField, ValveextensionsExtensions.ValveMapKey, ValveextensionsExtensions.DiffEncodeField, ValveextensionsExtensions.DeltaIgnore, ValveextensionsExtensions.SteammlMaxEntries, ValveextensionsExtensions.SteammlIsTimestamp, ValveextensionsExtensions.SteamlearnCount, ValveextensionsExtensions.SchemaFriendlyName, ValveextensionsExtensions.SchemaDescription, ValveextensionsExtensions.SchemaSuppressEnumerator }, null)); 44 | } 45 | #endregion 46 | 47 | } 48 | /// Holder for extension identifiers generated from the top level of valveextensions.proto 49 | public static partial class ValveextensionsExtensions { 50 | public static readonly pb::Extension ValveMapField = 51 | new pb::Extension(61000, pb::FieldCodec.ForBool(488000, false)); 52 | public static readonly pb::Extension ValveMapKey = 53 | new pb::Extension(61001, pb::FieldCodec.ForBool(488008, false)); 54 | public static readonly pb::Extension DiffEncodeField = 55 | new pb::Extension(61002, pb::FieldCodec.ForInt32(488016, 0)); 56 | public static readonly pb::Extension DeltaIgnore = 57 | new pb::Extension(61003, pb::FieldCodec.ForBool(488024, false)); 58 | public static readonly pb::Extension SteammlMaxEntries = 59 | new pb::Extension(61004, pb::FieldCodec.ForUInt32(488032, 0)); 60 | public static readonly pb::Extension SteammlIsTimestamp = 61 | new pb::Extension(61005, pb::FieldCodec.ForBool(488040, false)); 62 | public static readonly pb::Extension SteamlearnCount = 63 | new pb::Extension(61006, pb::FieldCodec.ForUInt32(488048, 0)); 64 | public static readonly pb::Extension SchemaFriendlyName = 65 | new pb::Extension(1000, pb::FieldCodec.ForString(8002, "")); 66 | public static readonly pb::Extension SchemaDescription = 67 | new pb::Extension(1001, pb::FieldCodec.ForString(8010, "")); 68 | public static readonly pb::Extension SchemaSuppressEnumerator = 69 | new pb::Extension(1002, pb::FieldCodec.ForBool(8016, false)); 70 | } 71 | 72 | 73 | #endregion Designer generated code 74 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/base_modifier.proto: -------------------------------------------------------------------------------- 1 | import "networkbasetypes.proto"; 2 | 3 | enum MODIFIER_ENTRY_TYPE { 4 | MODIFIER_ENTRY_TYPE_ACTIVE = 1; 5 | MODIFIER_ENTRY_TYPE_REMOVED = 2; 6 | } 7 | 8 | message CModifierTableEntry { 9 | required .MODIFIER_ENTRY_TYPE entry_type = 1 [default = MODIFIER_ENTRY_TYPE_ACTIVE]; 10 | required uint32 parent = 2 [default = 16777215]; 11 | required uint32 serial_number = 3; 12 | optional uint32 modifier_subclass = 4; 13 | optional int32 stack_count = 5; 14 | optional int32 max_stack_count = 6; 15 | optional float last_applied_time = 7; 16 | optional float duration = 8 [default = -1]; 17 | optional uint32 caster = 9 [default = 16777215]; 18 | optional uint32 ability = 10 [default = 16777215]; 19 | optional int32 aura_provider_serial_number = 11; 20 | optional uint32 aura_provider_ehandle = 12 [default = 16777215]; 21 | optional uint32 ability_subclass = 13; 22 | optional bool bool1_ = 20; 23 | optional bool bool2_ = 21; 24 | optional bool bool3_ = 22; 25 | optional bool bool4_ = 23; 26 | optional int32 int1_ = 25; 27 | optional int32 int2_ = 26; 28 | optional int32 int3_ = 27; 29 | optional int32 int4_ = 28; 30 | optional float float1_ = 30; 31 | optional float float2_ = 31; 32 | optional float float3_ = 32; 33 | optional float float4_ = 33; 34 | optional float float5_ = 49; 35 | optional float float6_ = 50; 36 | optional float float7_ = 51; 37 | optional float float8_ = 52; 38 | optional float float9_ = 53; 39 | optional float float10_ = 54; 40 | optional uint64 uint1_ = 35; 41 | optional uint64 uint2_ = 36; 42 | optional uint64 uint3_ = 37; 43 | optional uint64 uint4_ = 38; 44 | optional .CMsgVector vec1_ = 40; 45 | optional .CMsgVector vec2_ = 41; 46 | optional .CMsgVector vec3_ = 42; 47 | optional .CMsgVector vec4_ = 43; 48 | optional string string1_ = 45; 49 | optional string string2_ = 46; 50 | optional string string3_ = 47; 51 | optional string string4_ = 48; 52 | } 53 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/citadel_gameevents.proto: -------------------------------------------------------------------------------- 1 | import "networkbasetypes.proto"; 2 | 3 | enum ECitadelGameEvents { 4 | GE_FireBullets = 450; 5 | GE_PlayerAnimEvent = 451; 6 | GE_ParticleSystemManager = 458; 7 | GE_ScreenTextPretty = 459; 8 | GE_ServerRequestedTracer = 460; 9 | GE_BulletImpact = 461; 10 | GE_EnableSatVolumesEvent = 462; 11 | GE_PlaceSatVolumeEvent = 463; 12 | GE_DisableSatVolumesEvent = 464; 13 | GE_RemoveSatVolumeEvent = 465; 14 | } 15 | 16 | enum PARTICLE_SYSTEM_MANAGER_MESSAGE { 17 | PARTICLE_SYSTEM_MANAGER_EVENT_CREATE = 0; 18 | PARTICLE_SYSTEM_MANAGER_EVENT_DESTROY = 1; 19 | PARTICLE_SYSTEM_MANAGER_EVENT_DESTROY_INVOLVING = 2; 20 | PARTICLE_SYSTEM_MANAGER_EVENT_RELEASE = 3; 21 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE = 4; 22 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_FORWARD = 5; 23 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_ORIENTATION = 6; 24 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_FALLBACK = 7; 25 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_ENT = 8; 26 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_OFFSET = 9; 27 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_FROZEN = 10; 28 | PARTICLE_SYSTEM_MANAGER_EVENT_UPDATE_SHOULD_DRAW = 11; 29 | } 30 | 31 | message CMsgFireBullets { 32 | message TracerAssignment { 33 | optional uint64 tracer_resource_id = 1; 34 | optional uint32 bullet_indicies = 2; 35 | } 36 | 37 | optional .CMsgVector origin = 1; 38 | optional .CMsgQAngle angles = 2; 39 | optional uint32 seed = 4; 40 | optional int32 shooter_entity = 5 [default = -1]; 41 | optional int32 ability = 7 [default = -1]; 42 | optional float penetration_percent = 8; 43 | optional float spread = 9; 44 | optional bool fired_from_gun = 10 [default = true]; 45 | optional uint32 bullets_override = 11; 46 | optional .CMsgFireBullets.TracerAssignment tracer_replacement = 12; 47 | repeated .CMsgFireBullets.TracerAssignment tracer_additional = 13; 48 | optional .CMsgQAngle angles_original = 14; 49 | optional uint32 weapon_subclass_id = 15; 50 | optional uint32 shot_number = 16; 51 | optional int32 ignore_entity = 17 [default = -1]; 52 | optional float max_range = 18; 53 | } 54 | 55 | message CMsgBulletImpact { 56 | optional .CMsgVector trace_start = 1; 57 | optional .CMsgVector impact_origin = 2; 58 | optional .CMsgVector surface_normal = 3; 59 | optional uint32 damage = 4; 60 | optional uint32 surface_type = 5; 61 | optional int32 ability_entindex = 7 [default = -1]; 62 | optional int32 impacted_entindex = 8 [default = -1]; 63 | optional uint32 impacted_hitbox = 9; 64 | optional uint32 weapon_subclass_id = 10; 65 | optional int32 shooter_entindex = 11 [default = -1]; 66 | } 67 | 68 | message CMsgPlayerAnimEvent { 69 | optional fixed32 player = 1 [default = 16777215]; 70 | optional uint32 event = 2; 71 | optional int32 data = 3; 72 | } 73 | 74 | message CMsgParticleSystemManager { 75 | message CreateParticle { 76 | optional fixed64 particle_name_index = 1; 77 | optional int32 attach_type = 2; 78 | optional uint32 entity_handle = 3 [default = 16777215]; 79 | optional .CMsgVector position = 4; 80 | optional .CMsgQAngle angles = 5; 81 | } 82 | 83 | message DestroyParticle { 84 | optional bool destroy_immediately = 1; 85 | } 86 | 87 | message DestroyParticleInvolving { 88 | optional bool destroy_immediately = 1; 89 | optional uint32 entity_handle = 3 [default = 16777215]; 90 | } 91 | 92 | message ReleaseParticleIndex { 93 | } 94 | 95 | message UpdateParticle { 96 | optional int32 control_point = 1; 97 | optional .CMsgVector position = 2; 98 | } 99 | 100 | message UpdateParticleFwd { 101 | optional int32 control_point = 1; 102 | optional .CMsgVector forward = 2; 103 | } 104 | 105 | message UpdateParticleOrient { 106 | optional int32 control_point = 1; 107 | optional .CMsgVector forward = 2; 108 | optional .CMsgVector left = 3; 109 | optional .CMsgVector up = 4; 110 | } 111 | 112 | message UpdateParticleFallback { 113 | optional int32 control_point = 1; 114 | optional .CMsgVector position = 2; 115 | } 116 | 117 | message UpdateParticleEnt { 118 | optional int32 control_point = 1; 119 | optional uint32 entity_handle = 2 [default = 16777215]; 120 | optional int32 attach_type = 3; 121 | optional int32 attachment = 4; 122 | optional .CMsgVector fallback_position = 5; 123 | } 124 | 125 | message UpdateParticleOffset { 126 | optional int32 control_point = 1; 127 | optional .CMsgVector origin_offset = 2; 128 | } 129 | 130 | message UpdateParticleFrozen { 131 | optional bool set_frozen = 1; 132 | } 133 | 134 | message UpdateParticleShouldDraw { 135 | optional bool should_draw = 1; 136 | } 137 | 138 | required .PARTICLE_SYSTEM_MANAGER_MESSAGE type = 1 [default = PARTICLE_SYSTEM_MANAGER_EVENT_CREATE]; 139 | required uint32 index = 2; 140 | optional .CMsgParticleSystemManager.CreateParticle create_particle = 3; 141 | optional .CMsgParticleSystemManager.DestroyParticle destroy_particle = 4; 142 | optional .CMsgParticleSystemManager.DestroyParticleInvolving destroy_particle_involving = 5; 143 | optional .CMsgParticleSystemManager.ReleaseParticleIndex release_particle_index = 6; 144 | optional .CMsgParticleSystemManager.UpdateParticle update_particle = 7; 145 | optional .CMsgParticleSystemManager.UpdateParticleFwd update_particle_fwd = 8; 146 | optional .CMsgParticleSystemManager.UpdateParticleOrient update_particle_orient = 9; 147 | optional .CMsgParticleSystemManager.UpdateParticleFallback update_particle_fallback = 10; 148 | optional .CMsgParticleSystemManager.UpdateParticleOffset update_particle_offset = 11; 149 | optional .CMsgParticleSystemManager.UpdateParticleEnt update_particle_ent = 12; 150 | optional .CMsgParticleSystemManager.UpdateParticleFrozen update_particle_frozen = 13; 151 | optional .CMsgParticleSystemManager.UpdateParticleShouldDraw update_particle_should_draw = 14; 152 | } 153 | 154 | message CMsgScreenTextPretty { 155 | optional float x_pos = 1; 156 | optional float y_pos = 2; 157 | optional int32 line = 3; 158 | optional string text = 4; 159 | optional int32 r = 5; 160 | optional int32 g = 6; 161 | optional int32 b = 7; 162 | optional int32 a = 8; 163 | optional float duration = 9; 164 | optional string font_name = 10; 165 | optional int32 font_size = 11; 166 | optional bool bold_font = 12; 167 | } 168 | 169 | message CMsgServerRequestedTracer { 170 | optional .CMsgVector origin = 1; 171 | optional .CMsgVector end = 2; 172 | optional int32 weaponid = 3 [default = -1]; 173 | optional uint32 entity_handle = 4 [default = 16777215]; 174 | optional float dps = 5; 175 | } 176 | 177 | message CMsgEnableSatVolumesEvent { 178 | optional uint32 mode = 1; 179 | optional float desat_amount = 2; 180 | optional fixed32 sat_tint = 3; 181 | optional fixed32 desat_tint = 4; 182 | optional fixed32 outline_color = 5; 183 | } 184 | 185 | message CMsgPlaceSatVolumeEvent { 186 | optional .CMsgVector position = 1; 187 | optional .CMsgVector direction = 2; 188 | optional float radius = 3; 189 | optional float falloff_distance = 4; 190 | optional float theta_dot = 5; 191 | optional float phi_dot = 6; 192 | optional uint32 entity_handle = 7 [default = 16777215]; 193 | optional uint32 attachment_handle = 8; 194 | optional uint32 type = 9; 195 | optional int32 volume_id = 10; 196 | } 197 | 198 | message CMsgRemoveSatVolumeEvent { 199 | optional int32 volume_id = 1; 200 | } 201 | 202 | message CMsgDisableSatVolumesEvent { 203 | } 204 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/citadel_gamemessages.proto: -------------------------------------------------------------------------------- 1 | 2 | enum ECitadelGameMessages { 3 | k_EMsgGameServerToClientConnectionStatus = 10; 4 | k_EMsgGameServerToClientInitialGameState = 12; 5 | k_EMsgGameServerToClientGameCompleted = 13; 6 | k_EMsgGameServerToClientGoodbye = 15; 7 | } 8 | 9 | enum ECitadelDisconnectReason { 10 | k_ECitadelDisconnectReason_UserLeaveMatch = 1001; 11 | k_ECitadelDisconnectReason_UserQuitApp = 1002; 12 | k_ECitadelDisconnectReason_UserCancel = 1003; 13 | k_ECitadelDisconnectReason_Goodbye = 1004; 14 | k_ECitadelDisconnectReason_BadMessage = 2001; 15 | k_ECitadelDisconnectReason_GameDestroyedUnexpectedly = 2002; 16 | k_ECitadelDisconnectReason_ChangingServer = 2003; 17 | k_ECitadelDisconnectReason_OldConnection = 2004; 18 | k_ECitadelDisconnectReason_GoodbyeUnrecognizedGame = 2005; 19 | } 20 | 21 | message CMsgClientServerHeader { 22 | optional uint64 game_instance_id = 1; 23 | optional uint32 local_player_index = 2; 24 | optional bytes payload = 3; 25 | optional .ECitadelGameMessages msg_id = 4 [default = k_EMsgGameServerToClientConnectionStatus]; 26 | } 27 | 28 | message CMsgGameServerToClientGameCompleted { 29 | } 30 | 31 | message CMsgGameServerToClientGoodbye { 32 | } 33 | 34 | message CMsgGameServerToClientConnectionStatus { 35 | message Player { 36 | optional int32 player_slot = 1 [default = -1]; 37 | optional .CMsgGameServerToClientConnectionStatus.EStatus status = 2 [default = k_EConnected]; 38 | optional bool inactivity_ticking = 3; 39 | optional uint32 inactivity_ms_remaining = 4; 40 | optional uint32 inactivity_anim_ms_remaining = 5; 41 | } 42 | 43 | enum EStatus { 44 | k_EConnected = 1; 45 | k_EDisconnected = 2; 46 | } 47 | 48 | repeated .CMsgGameServerToClientConnectionStatus.Player players = 1; 49 | } 50 | 51 | message CClientReconnectInfo { 52 | optional fixed64 server_steam_id = 1; 53 | optional uint64 lobby_id = 2; 54 | optional uint32 time_updated = 3; 55 | optional uint32 udp_connect_ip = 4; 56 | optional uint32 udp_connect_port = 5; 57 | optional uint32 compatibility_version = 6; 58 | } 59 | 60 | message CMsgClientAccountSyncStorageFile { 61 | optional uint32 version = 1; 62 | repeated uint32 ids = 2; 63 | repeated uint32 values = 3; 64 | } 65 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/demo.proto: -------------------------------------------------------------------------------- 1 | enum EDemoCommands { 2 | DEM_Error = -1; 3 | DEM_Stop = 0; 4 | DEM_FileHeader = 1; 5 | DEM_FileInfo = 2; 6 | DEM_SyncTick = 3; 7 | DEM_SendTables = 4; 8 | DEM_ClassInfo = 5; 9 | DEM_StringTables = 6; 10 | DEM_Packet = 7; 11 | DEM_SignonPacket = 8; 12 | DEM_ConsoleCmd = 9; 13 | DEM_CustomData = 10; 14 | DEM_CustomDataCallbacks = 11; 15 | DEM_UserCmd = 12; 16 | DEM_FullPacket = 13; 17 | DEM_SaveGame = 14; 18 | DEM_SpawnGroups = 15; 19 | DEM_AnimationData = 16; 20 | DEM_AnimationHeader = 17; 21 | DEM_Max = 18; 22 | DEM_IsCompressed = 64; 23 | } 24 | 25 | message CDemoFileHeader { 26 | required string demo_file_stamp = 1; 27 | optional int32 network_protocol = 2; 28 | optional string server_name = 3; 29 | optional string client_name = 4; 30 | optional string map_name = 5; 31 | optional string game_directory = 6; 32 | optional int32 fullpackets_version = 7; 33 | optional bool allow_clientside_entities = 8; 34 | optional bool allow_clientside_particles = 9; 35 | optional string addons = 10; 36 | optional string demo_version_name = 11; 37 | optional string demo_version_guid = 12; 38 | optional int32 build_num = 13; 39 | optional string game = 14; 40 | optional int32 server_start_tick = 15; 41 | } 42 | 43 | message CGameInfo { 44 | message CDotaGameInfo { 45 | message CPlayerInfo { 46 | optional string hero_name = 1; 47 | optional string player_name = 2; 48 | optional bool is_fake_client = 3; 49 | optional uint64 steamid = 4; 50 | optional int32 game_team = 5; 51 | } 52 | 53 | message CHeroSelectEvent { 54 | optional bool is_pick = 1; 55 | optional uint32 team = 2; 56 | optional int32 hero_id = 3; 57 | } 58 | 59 | optional uint64 match_id = 1; 60 | optional int32 game_mode = 2; 61 | optional int32 game_winner = 3; 62 | repeated .CGameInfo.CDotaGameInfo.CPlayerInfo player_info = 4; 63 | optional uint32 leagueid = 5; 64 | repeated .CGameInfo.CDotaGameInfo.CHeroSelectEvent picks_bans = 6; 65 | optional uint32 radiant_team_id = 7; 66 | optional uint32 dire_team_id = 8; 67 | optional string radiant_team_tag = 9; 68 | optional string dire_team_tag = 10; 69 | optional uint32 end_time = 11; 70 | } 71 | 72 | message CCSGameInfo { 73 | repeated int32 round_start_ticks = 1; 74 | } 75 | 76 | optional .CGameInfo.CDotaGameInfo dota = 4; 77 | optional .CGameInfo.CCSGameInfo cs = 5; 78 | } 79 | 80 | message CDemoFileInfo { 81 | optional float playback_time = 1; 82 | optional int32 playback_ticks = 2; 83 | optional int32 playback_frames = 3; 84 | optional .CGameInfo game_info = 4; 85 | } 86 | 87 | message CDemoPacket { 88 | optional bytes data = 3; 89 | } 90 | 91 | message CDemoFullPacket { 92 | optional .CDemoStringTables string_table = 1; 93 | optional .CDemoPacket packet = 2; 94 | } 95 | 96 | message CDemoSaveGame { 97 | optional bytes data = 1; 98 | optional fixed64 steam_id = 2; 99 | optional fixed64 signature = 3; 100 | optional int32 version = 4; 101 | } 102 | 103 | message CDemoSyncTick { 104 | } 105 | 106 | message CDemoConsoleCmd { 107 | optional string cmdstring = 1; 108 | } 109 | 110 | message CDemoSendTables { 111 | optional bytes data = 1; 112 | } 113 | 114 | message CDemoClassInfo { 115 | message class_t { 116 | optional int32 class_id = 1; 117 | optional string network_name = 2; 118 | optional string table_name = 3; 119 | } 120 | 121 | repeated .CDemoClassInfo.class_t classes = 1; 122 | } 123 | 124 | message CDemoCustomData { 125 | optional int32 callback_index = 1; 126 | optional bytes data = 2; 127 | } 128 | 129 | message CDemoCustomDataCallbacks { 130 | repeated string save_id = 1; 131 | } 132 | 133 | message CDemoAnimationHeader { 134 | optional sint32 entity_id = 1; 135 | optional int32 tick = 2; 136 | optional bytes data = 3; 137 | } 138 | 139 | message CDemoAnimationData { 140 | optional sint32 entity_id = 1; 141 | optional int32 start_tick = 2; 142 | optional int32 end_tick = 3; 143 | optional bytes data = 4; 144 | optional int64 data_checksum = 5; 145 | } 146 | 147 | message CDemoStringTables { 148 | message items_t { 149 | optional string str = 1; 150 | optional bytes data = 2; 151 | } 152 | 153 | message table_t { 154 | optional string table_name = 1; 155 | repeated .CDemoStringTables.items_t items = 2; 156 | repeated .CDemoStringTables.items_t items_clientside = 3; 157 | optional int32 table_flags = 4; 158 | } 159 | 160 | repeated .CDemoStringTables.table_t tables = 1; 161 | } 162 | 163 | message CDemoStop { 164 | } 165 | 166 | message CDemoUserCmd { 167 | optional int32 cmd_number = 1; 168 | optional bytes data = 2; 169 | } 170 | 171 | message CDemoSpawnGroups { 172 | repeated bytes msgs = 3; 173 | } 174 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/gameevents.proto: -------------------------------------------------------------------------------- 1 | import "networkbasetypes.proto"; 2 | 3 | enum EBaseGameEvents { 4 | GE_VDebugGameSessionIDEvent = 200; 5 | GE_PlaceDecalEvent = 201; 6 | GE_ClearWorldDecalsEvent = 202; 7 | GE_ClearEntityDecalsEvent = 203; 8 | GE_ClearDecalsForSkeletonInstanceEvent = 204; 9 | GE_Source1LegacyGameEventList = 205; 10 | GE_Source1LegacyListenEvents = 206; 11 | GE_Source1LegacyGameEvent = 207; 12 | GE_SosStartSoundEvent = 208; 13 | GE_SosStopSoundEvent = 209; 14 | GE_SosSetSoundEventParams = 210; 15 | GE_SosSetLibraryStackFields = 211; 16 | GE_SosStopSoundEventHash = 212; 17 | } 18 | 19 | message CMsgVDebugGameSessionIDEvent { 20 | optional int32 clientid = 1; 21 | optional string gamesessionid = 2; 22 | } 23 | 24 | message CMsgPlaceDecalEvent { 25 | optional .CMsgVector position = 1; 26 | optional .CMsgVector normal = 2; 27 | optional .CMsgVector saxis = 3; 28 | optional uint32 decalmaterialindex = 4; 29 | optional uint32 flags = 5; 30 | optional fixed32 color = 6; 31 | optional float width = 7; 32 | optional float height = 8; 33 | optional float depth = 9; 34 | optional uint32 entityhandleindex = 10; 35 | optional fixed32 skeletoninstancehash = 11; 36 | optional int32 boneindex = 12; 37 | optional bool translucenthit = 13; 38 | optional bool is_adjacent = 14; 39 | } 40 | 41 | message CMsgClearWorldDecalsEvent { 42 | optional uint32 flagstoclear = 1; 43 | } 44 | 45 | message CMsgClearEntityDecalsEvent { 46 | optional uint32 flagstoclear = 1; 47 | } 48 | 49 | message CMsgClearDecalsForSkeletonInstanceEvent { 50 | optional uint32 flagstoclear = 1; 51 | optional uint32 entityhandleindex = 2; 52 | optional uint32 skeletoninstancehash = 3; 53 | } 54 | 55 | message CMsgSource1LegacyGameEventList { 56 | message key_t { 57 | optional int32 type = 1; 58 | optional string name = 2; 59 | } 60 | 61 | message descriptor_t { 62 | optional int32 eventid = 1; 63 | optional string name = 2; 64 | repeated .CMsgSource1LegacyGameEventList.key_t keys = 3; 65 | } 66 | 67 | repeated .CMsgSource1LegacyGameEventList.descriptor_t descriptors = 1; 68 | } 69 | 70 | message CMsgSource1LegacyListenEvents { 71 | optional int32 playerslot = 1; 72 | repeated uint32 eventarraybits = 2; 73 | } 74 | 75 | message CMsgSource1LegacyGameEvent { 76 | message key_t { 77 | optional int32 type = 1; 78 | optional string val_string = 2; 79 | optional float val_float = 3; 80 | optional int32 val_long = 4; 81 | optional int32 val_short = 5; 82 | optional int32 val_byte = 6; 83 | optional bool val_bool = 7; 84 | optional uint64 val_uint64 = 8; 85 | } 86 | 87 | optional string event_name = 1; 88 | optional int32 eventid = 2; 89 | repeated .CMsgSource1LegacyGameEvent.key_t keys = 3; 90 | optional int32 server_tick = 4; 91 | optional int32 passthrough = 5; 92 | } 93 | 94 | message CMsgSosStartSoundEvent { 95 | optional int32 soundevent_guid = 1; 96 | optional fixed32 soundevent_hash = 2; 97 | optional int32 source_entity_index = 3 [default = -1]; 98 | optional int32 seed = 4; 99 | optional bytes packed_params = 5; 100 | optional float start_time = 6; 101 | } 102 | 103 | message CMsgSosStopSoundEvent { 104 | optional int32 soundevent_guid = 1; 105 | } 106 | 107 | message CMsgSosStopSoundEventHash { 108 | optional fixed32 soundevent_hash = 1; 109 | optional int32 source_entity_index = 2 [default = -1]; 110 | } 111 | 112 | message CMsgSosSetSoundEventParams { 113 | optional int32 soundevent_guid = 1; 114 | optional bytes packed_params = 5; 115 | } 116 | 117 | message CMsgSosSetLibraryStackFields { 118 | optional fixed32 stack_hash = 1; 119 | optional bytes packed_fields = 5; 120 | } 121 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/networkbasetypes.proto: -------------------------------------------------------------------------------- 1 | 2 | enum SignonState_t { 3 | SIGNONSTATE_NONE = 0; 4 | SIGNONSTATE_CHALLENGE = 1; 5 | SIGNONSTATE_CONNECTED = 2; 6 | SIGNONSTATE_NEW = 3; 7 | SIGNONSTATE_PRESPAWN = 4; 8 | SIGNONSTATE_SPAWN = 5; 9 | SIGNONSTATE_FULL = 6; 10 | SIGNONSTATE_CHANGELEVEL = 7; 11 | } 12 | 13 | enum NET_Messages { 14 | net_NOP = 0; 15 | net_Disconnect_Legacy = 1; 16 | net_SplitScreenUser = 3; 17 | net_Tick = 4; 18 | net_StringCmd = 5; 19 | net_SetConVar = 6; 20 | net_SignonState = 7; 21 | net_SpawnGroup_Load = 8; 22 | net_SpawnGroup_ManifestUpdate = 9; 23 | net_SpawnGroup_SetCreationTick = 11; 24 | net_SpawnGroup_Unload = 12; 25 | net_SpawnGroup_LoadCompleted = 13; 26 | net_DebugOverlay = 15; 27 | } 28 | 29 | enum SpawnGroupFlags_t { 30 | SPAWN_GROUP_LOAD_ENTITIES_FROM_SAVE = 1; 31 | SPAWN_GROUP_DONT_SPAWN_ENTITIES = 2; 32 | SPAWN_GROUP_SYNCHRONOUS_SPAWN = 4; 33 | SPAWN_GROUP_IS_INITIAL_SPAWN_GROUP = 8; 34 | SPAWN_GROUP_CREATE_CLIENT_ONLY_ENTITIES = 16; 35 | SPAWN_GROUP_BLOCK_UNTIL_LOADED = 64; 36 | SPAWN_GROUP_LOAD_STREAMING_DATA = 128; 37 | SPAWN_GROUP_CREATE_NEW_SCENE_WORLD = 256; 38 | } 39 | 40 | message CMsgVector { 41 | optional float x = 1; 42 | optional float y = 2; 43 | optional float z = 3; 44 | optional float w = 4; 45 | } 46 | 47 | message CMsgVector2D { 48 | optional float x = 1; 49 | optional float y = 2; 50 | } 51 | 52 | message CMsgQAngle { 53 | optional float x = 1; 54 | optional float y = 2; 55 | optional float z = 3; 56 | } 57 | 58 | message CMsgQuaternion { 59 | optional float x = 1; 60 | optional float y = 2; 61 | optional float z = 3; 62 | optional float w = 4; 63 | } 64 | 65 | message CMsgTransform { 66 | optional .CMsgVector position = 1; 67 | optional float scale = 2; 68 | optional .CMsgQuaternion orientation = 3; 69 | } 70 | 71 | message CMsgRGBA { 72 | optional int32 r = 1; 73 | optional int32 g = 2; 74 | optional int32 b = 3; 75 | optional int32 a = 4; 76 | } 77 | 78 | message CMsgPlayerInfo { 79 | optional string name = 1; 80 | optional fixed64 xuid = 2; 81 | optional int32 userid = 3; 82 | optional fixed64 steamid = 4; 83 | optional bool fakeplayer = 5; 84 | optional bool ishltv = 6; 85 | } 86 | 87 | message CEntityMsg { 88 | optional uint32 target_entity = 1 [default = 16777215]; 89 | } 90 | 91 | message CMsg_CVars { 92 | message CVar { 93 | optional string name = 1; 94 | optional string value = 2; 95 | } 96 | 97 | repeated .CMsg_CVars.CVar cvars = 1; 98 | } 99 | 100 | message CNETMsg_NOP { 101 | } 102 | 103 | message CNETMsg_SplitScreenUser { 104 | optional int32 slot = 1; 105 | } 106 | 107 | message CNETMsg_Tick { 108 | optional uint32 tick = 1; 109 | optional uint32 host_frametime = 2; 110 | optional uint32 host_frametime_std_deviation = 3; 111 | optional uint32 host_computationtime = 4; 112 | optional uint32 host_computationtime_std_deviation = 5; 113 | optional uint32 host_framestarttime_std_deviation = 6; 114 | optional uint32 host_loss = 7; 115 | optional uint32 host_unfiltered_frametime = 8; 116 | optional uint32 hltv_replay_flags = 9; 117 | optional uint32 expected_long_tick = 10; 118 | optional string expected_long_tick_reason = 11; 119 | optional uint32 jitter = 12; 120 | } 121 | 122 | message CNETMsg_StringCmd { 123 | optional string command = 1; 124 | optional uint32 prediction_sync = 2; 125 | } 126 | 127 | message CNETMsg_SetConVar { 128 | optional .CMsg_CVars convars = 1; 129 | } 130 | 131 | message CNETMsg_SignonState { 132 | optional .SignonState_t signon_state = 1 [default = SIGNONSTATE_NONE]; 133 | optional uint32 spawn_count = 2; 134 | optional uint32 num_server_players = 3; 135 | repeated string players_networkids = 4; 136 | optional string map_name = 5; 137 | optional string addons = 6; 138 | } 139 | 140 | message CSVCMsg_GameEvent { 141 | message key_t { 142 | optional int32 type = 1; 143 | optional string val_string = 2; 144 | optional float val_float = 3; 145 | optional int32 val_long = 4; 146 | optional int32 val_short = 5; 147 | optional int32 val_byte = 6; 148 | optional bool val_bool = 7; 149 | optional uint64 val_uint64 = 8; 150 | } 151 | 152 | optional string event_name = 1; 153 | optional int32 eventid = 2; 154 | repeated .CSVCMsg_GameEvent.key_t keys = 3; 155 | } 156 | 157 | message CSVCMsgList_GameEvents { 158 | message event_t { 159 | optional int32 tick = 1; 160 | optional .CSVCMsg_GameEvent event = 2; 161 | } 162 | 163 | repeated .CSVCMsgList_GameEvents.event_t events = 1; 164 | } 165 | 166 | message CNETMsg_SpawnGroup_Load { 167 | optional string worldname = 1; 168 | optional string entitylumpname = 2; 169 | optional string entityfiltername = 3; 170 | optional uint32 spawngrouphandle = 4; 171 | optional uint32 spawngroupownerhandle = 5; 172 | optional .CMsgVector world_offset_pos = 6; 173 | optional .CMsgQAngle world_offset_angle = 7; 174 | optional bytes spawngroupmanifest = 8; 175 | optional uint32 flags = 9; 176 | optional int32 tickcount = 10; 177 | optional bool manifestincomplete = 11; 178 | optional string localnamefixup = 12; 179 | optional string parentnamefixup = 13; 180 | optional int32 manifestloadpriority = 14; 181 | optional uint32 worldgroupid = 15; 182 | optional uint32 creationsequence = 16; 183 | optional string savegamefilename = 17; 184 | optional uint32 spawngroupparenthandle = 18; 185 | optional bool leveltransition = 19; 186 | optional string worldgroupname = 20; 187 | } 188 | 189 | message CNETMsg_SpawnGroup_ManifestUpdate { 190 | optional uint32 spawngrouphandle = 1; 191 | optional bytes spawngroupmanifest = 2; 192 | optional bool manifestincomplete = 3; 193 | } 194 | 195 | message CNETMsg_SpawnGroup_SetCreationTick { 196 | optional uint32 spawngrouphandle = 1; 197 | optional int32 tickcount = 2; 198 | optional uint32 creationsequence = 3; 199 | } 200 | 201 | message CNETMsg_SpawnGroup_Unload { 202 | optional uint32 spawngrouphandle = 1; 203 | optional uint32 flags = 2; 204 | optional int32 tickcount = 3; 205 | } 206 | 207 | message CNETMsg_SpawnGroup_LoadCompleted { 208 | optional uint32 spawngrouphandle = 1; 209 | } 210 | 211 | message CSVCMsg_GameSessionConfiguration { 212 | optional bool is_multiplayer = 1; 213 | optional bool is_loadsavegame = 2; 214 | optional bool is_background_map = 3; 215 | optional bool is_headless = 4; 216 | optional uint32 min_client_limit = 5; 217 | optional uint32 max_client_limit = 6; 218 | optional uint32 max_clients = 7; 219 | optional fixed32 tick_interval = 8; 220 | optional string hostname = 9; 221 | optional string savegamename = 10; 222 | optional string s1_mapname = 11; 223 | optional string gamemode = 12; 224 | optional string server_ip_address = 13; 225 | optional bytes data = 14; 226 | optional bool is_localonly = 15; 227 | optional bool no_steam_server = 19; 228 | optional bool is_transition = 16; 229 | optional string previouslevel = 17; 230 | optional string landmarkname = 18; 231 | } 232 | 233 | message CNETMsg_DebugOverlay { 234 | optional int32 etype = 1; 235 | repeated .CMsgVector vectors = 2; 236 | repeated .CMsgRGBA colors = 3; 237 | repeated float dimensions = 4; 238 | repeated float times = 5; 239 | repeated bool bools = 6; 240 | repeated uint64 uint64s = 7; 241 | repeated string strings = 8; 242 | } 243 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/steammessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | extend .google.protobuf.FieldOptions { 4 | optional bool key_field = 60000 [default = false]; 5 | } 6 | 7 | extend .google.protobuf.MessageOptions { 8 | optional int32 msgpool_soft_limit = 60000 [default = 32]; 9 | optional int32 msgpool_hard_limit = 60001 [default = 384]; 10 | } 11 | 12 | enum EGCPlatform { 13 | k_eGCPlatform_None = 0; 14 | k_eGCPlatform_PC = 1; 15 | k_eGCPlatform_Mac = 2; 16 | k_eGCPlatform_Linux = 3; 17 | k_eGCPlatform_Android = 4; 18 | k_eGCPlatform_iOS = 5; 19 | } 20 | 21 | enum GCProtoBufMsgSrc { 22 | GCProtoBufMsgSrc_Unspecified = 0; 23 | GCProtoBufMsgSrc_FromSystem = 1; 24 | GCProtoBufMsgSrc_FromSteamID = 2; 25 | GCProtoBufMsgSrc_FromGC = 3; 26 | GCProtoBufMsgSrc_ReplySystem = 4; 27 | GCProtoBufMsgSrc_SpoofedSteamID = 5; 28 | } 29 | 30 | message CMsgProtoBufHeader { 31 | option (msgpool_soft_limit) = 256; 32 | option (msgpool_hard_limit) = 1024; 33 | 34 | optional fixed64 client_steam_id = 1; 35 | optional int32 client_session_id = 2; 36 | optional uint32 source_app_id = 3; 37 | optional fixed64 job_id_source = 10 [default = 18446744073709551615]; 38 | optional fixed64 job_id_target = 11 [default = 18446744073709551615]; 39 | optional string target_job_name = 12; 40 | optional int32 eresult = 13 [default = 2]; 41 | optional string error_message = 14; 42 | optional .GCProtoBufMsgSrc gc_msg_src = 200 [default = GCProtoBufMsgSrc_Unspecified]; 43 | optional int32 gc_dir_index_source = 201 [default = -1]; 44 | } 45 | 46 | message CGCSystemMsg_GetAccountDetails { 47 | option (msgpool_soft_limit) = 128; 48 | option (msgpool_hard_limit) = 512; 49 | 50 | optional fixed64 steamid = 1; 51 | optional uint32 appid = 2; 52 | } 53 | 54 | message CGCSystemMsg_GetAccountDetails_Response { 55 | option (msgpool_soft_limit) = 128; 56 | option (msgpool_hard_limit) = 512; 57 | 58 | optional uint32 eresult_deprecated = 1 [default = 2]; 59 | optional string account_name = 2; 60 | optional string persona_name = 3; 61 | optional bool is_profile_created = 26; 62 | optional bool is_profile_public = 4; 63 | optional bool is_inventory_public = 5; 64 | optional bool is_vac_banned = 7; 65 | optional bool is_cyber_cafe = 8; 66 | optional bool is_school_account = 9; 67 | optional bool is_limited = 10; 68 | optional bool is_subscribed = 11; 69 | optional uint32 package = 12; 70 | optional bool is_free_trial_account = 13; 71 | optional uint32 free_trial_expiration = 14; 72 | optional bool is_low_violence = 15; 73 | optional bool is_account_locked_down = 16; 74 | optional bool is_community_banned = 17; 75 | optional bool is_trade_banned = 18; 76 | optional uint32 trade_ban_expiration = 19; 77 | optional uint32 accountid = 20; 78 | optional uint32 suspension_end_time = 21; 79 | optional string currency = 22; 80 | optional uint32 steam_level = 23; 81 | optional uint32 friend_count = 24; 82 | optional uint32 account_creation_time = 25; 83 | optional bool is_steamguard_enabled = 27; 84 | optional bool is_phone_verified = 28; 85 | optional bool is_two_factor_auth_enabled = 29; 86 | optional uint32 two_factor_enabled_time = 30; 87 | optional uint32 phone_verification_time = 31; 88 | optional uint64 phone_id = 33; 89 | optional bool is_phone_identifying = 34; 90 | optional uint32 rt_identity_linked = 35; 91 | optional uint32 rt_birth_date = 36; 92 | optional string txn_country_code = 37; 93 | optional bool has_accepted_china_ssa = 38; 94 | optional bool is_banned_steam_china = 39; 95 | } 96 | 97 | message CIPLocationInfo { 98 | optional uint32 ip = 1; 99 | optional float latitude = 2; 100 | optional float longitude = 3; 101 | optional string country = 4; 102 | optional string state = 5; 103 | optional string city = 6; 104 | } 105 | 106 | message CGCMsgGetIPLocationResponse { 107 | repeated .CIPLocationInfo infos = 1; 108 | } 109 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/steammessages_unified_base.steamworkssdk.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option optimize_for = SPEED; 4 | option cc_generic_services = false; 5 | 6 | extend .google.protobuf.FieldOptions { 7 | optional string description = 50000; 8 | } 9 | 10 | extend .google.protobuf.ServiceOptions { 11 | optional string service_description = 50000; 12 | optional .EProtoExecutionSite service_execution_site = 50008 [default = k_EProtoExecutionSiteUnknown]; 13 | } 14 | 15 | extend .google.protobuf.MethodOptions { 16 | optional string method_description = 50000; 17 | } 18 | 19 | extend .google.protobuf.EnumOptions { 20 | optional string enum_description = 50000; 21 | } 22 | 23 | extend .google.protobuf.EnumValueOptions { 24 | optional string enum_value_description = 50000; 25 | } 26 | 27 | enum EProtoExecutionSite { 28 | k_EProtoExecutionSiteUnknown = 0; 29 | k_EProtoExecutionSiteSteamClient = 3; 30 | } 31 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/te.proto: -------------------------------------------------------------------------------- 1 | import "networkbasetypes.proto"; 2 | 3 | enum ETEProtobufIds { 4 | TE_EffectDispatchId = 400; 5 | TE_ArmorRicochetId = 401; 6 | TE_BeamEntPointId = 402; 7 | TE_BeamEntsId = 403; 8 | TE_BeamPointsId = 404; 9 | TE_BeamRingId = 405; 10 | TE_BSPDecalId = 407; 11 | TE_BubblesId = 408; 12 | TE_BubbleTrailId = 409; 13 | TE_DecalId = 410; 14 | TE_WorldDecalId = 411; 15 | TE_EnergySplashId = 412; 16 | TE_FizzId = 413; 17 | TE_ShatterSurfaceId = 414; 18 | TE_GlowSpriteId = 415; 19 | TE_ImpactId = 416; 20 | TE_MuzzleFlashId = 417; 21 | TE_BloodStreamId = 418; 22 | TE_ExplosionId = 419; 23 | TE_DustId = 420; 24 | TE_LargeFunnelId = 421; 25 | TE_SparksId = 422; 26 | TE_PhysicsPropId = 423; 27 | TE_PlayerDecalId = 424; 28 | TE_ProjectedDecalId = 425; 29 | TE_SmokeId = 426; 30 | } 31 | 32 | message CMsgTEArmorRicochet { 33 | optional .CMsgVector pos = 1; 34 | optional .CMsgVector dir = 2; 35 | } 36 | 37 | message CMsgTEBaseBeam { 38 | optional fixed64 modelindex = 1; 39 | optional fixed64 haloindex = 2; 40 | optional uint32 startframe = 3; 41 | optional uint32 framerate = 4; 42 | optional float life = 5; 43 | optional float width = 6; 44 | optional float endwidth = 7; 45 | optional uint32 fadelength = 8; 46 | optional float amplitude = 9; 47 | optional fixed32 color = 10; 48 | optional uint32 speed = 11; 49 | optional uint32 flags = 12; 50 | } 51 | 52 | message CMsgTEBeamEntPoint { 53 | optional .CMsgTEBaseBeam base = 1; 54 | optional uint32 startentity = 2; 55 | optional uint32 endentity = 3; 56 | optional .CMsgVector start = 4; 57 | optional .CMsgVector end = 5; 58 | } 59 | 60 | message CMsgTEBeamEnts { 61 | optional .CMsgTEBaseBeam base = 1; 62 | optional uint32 startentity = 2; 63 | optional uint32 endentity = 3; 64 | } 65 | 66 | message CMsgTEBeamPoints { 67 | optional .CMsgTEBaseBeam base = 1; 68 | optional .CMsgVector start = 2; 69 | optional .CMsgVector end = 3; 70 | } 71 | 72 | message CMsgTEBeamRing { 73 | optional .CMsgTEBaseBeam base = 1; 74 | optional uint32 startentity = 2; 75 | optional uint32 endentity = 3; 76 | } 77 | 78 | message CMsgTEBSPDecal { 79 | optional .CMsgVector origin = 1; 80 | optional .CMsgVector normal = 2; 81 | optional .CMsgVector saxis = 3; 82 | optional int32 entity = 4 [default = -1]; 83 | optional uint32 index = 5; 84 | } 85 | 86 | message CMsgTEBubbles { 87 | optional .CMsgVector mins = 1; 88 | optional .CMsgVector maxs = 2; 89 | optional float height = 3; 90 | optional uint32 count = 4; 91 | optional float speed = 5; 92 | } 93 | 94 | message CMsgTEBubbleTrail { 95 | optional .CMsgVector mins = 1; 96 | optional .CMsgVector maxs = 2; 97 | optional float waterz = 3; 98 | optional uint32 count = 4; 99 | optional float speed = 5; 100 | } 101 | 102 | message CMsgTEDecal { 103 | optional .CMsgVector origin = 1; 104 | optional .CMsgVector start = 2; 105 | optional int32 entity = 3 [default = -1]; 106 | optional uint32 hitbox = 4; 107 | optional uint32 index = 5; 108 | } 109 | 110 | message CMsgEffectData { 111 | optional .CMsgVector origin = 1; 112 | optional .CMsgVector start = 2; 113 | optional .CMsgVector normal = 3; 114 | optional .CMsgQAngle angles = 4; 115 | optional fixed32 entity = 5 [default = 16777215]; 116 | optional fixed32 otherentity = 6 [default = 16777215]; 117 | optional float scale = 7; 118 | optional float magnitude = 8; 119 | optional float radius = 9; 120 | optional fixed32 surfaceprop = 10; 121 | optional fixed64 effectindex = 11; 122 | optional uint32 damagetype = 12; 123 | optional uint32 material = 13; 124 | optional uint32 hitbox = 14; 125 | optional uint32 color = 15; 126 | optional uint32 flags = 16; 127 | optional int32 attachmentindex = 17; 128 | optional uint32 effectname = 18; 129 | optional uint32 attachmentname = 19; 130 | } 131 | 132 | message CMsgTEEffectDispatch { 133 | optional .CMsgEffectData effectdata = 1; 134 | } 135 | 136 | message CMsgTEEnergySplash { 137 | optional .CMsgVector pos = 1; 138 | optional .CMsgVector dir = 2; 139 | optional bool explosive = 3; 140 | } 141 | 142 | message CMsgTEFizz { 143 | optional int32 entity = 1 [default = -1]; 144 | optional uint32 density = 2; 145 | optional int32 current = 3; 146 | } 147 | 148 | message CMsgTEShatterSurface { 149 | optional .CMsgVector origin = 1; 150 | optional .CMsgQAngle angles = 2; 151 | optional .CMsgVector force = 3; 152 | optional .CMsgVector forcepos = 4; 153 | optional float width = 5; 154 | optional float height = 6; 155 | optional float shardsize = 7; 156 | optional uint32 surfacetype = 8; 157 | optional fixed32 frontcolor = 9; 158 | optional fixed32 backcolor = 10; 159 | } 160 | 161 | message CMsgTEGlowSprite { 162 | optional .CMsgVector origin = 1; 163 | optional float scale = 2; 164 | optional float life = 3; 165 | optional uint32 brightness = 4; 166 | } 167 | 168 | message CMsgTEImpact { 169 | optional .CMsgVector origin = 1; 170 | optional .CMsgVector normal = 2; 171 | optional uint32 type = 3; 172 | } 173 | 174 | message CMsgTEMuzzleFlash { 175 | optional .CMsgVector origin = 1; 176 | optional .CMsgQAngle angles = 2; 177 | optional float scale = 3; 178 | optional uint32 type = 4; 179 | } 180 | 181 | message CMsgTEBloodStream { 182 | optional .CMsgVector origin = 1; 183 | optional .CMsgVector direction = 2; 184 | optional fixed32 color = 3; 185 | optional uint32 amount = 4; 186 | } 187 | 188 | message CMsgTEExplosion { 189 | optional .CMsgVector origin = 1; 190 | optional uint32 framerate = 2; 191 | optional uint32 flags = 3; 192 | optional .CMsgVector normal = 4; 193 | optional uint32 materialtype = 5; 194 | optional uint32 radius = 6; 195 | optional uint32 magnitude = 7; 196 | optional float scale = 8; 197 | optional bool affect_ragdolls = 9; 198 | optional string effect_name = 10; 199 | optional uint32 explosion_type = 11; 200 | optional bool create_debris = 12; 201 | optional .CMsgVector debris_origin = 13; 202 | optional fixed32 debris_surfaceprop = 14; 203 | } 204 | 205 | message CMsgTEDust { 206 | optional .CMsgVector origin = 1; 207 | optional float size = 2; 208 | optional float speed = 3; 209 | optional .CMsgVector direction = 4; 210 | } 211 | 212 | message CMsgTELargeFunnel { 213 | optional .CMsgVector origin = 1; 214 | optional uint32 reversed = 2; 215 | } 216 | 217 | message CMsgTESparks { 218 | optional .CMsgVector origin = 1; 219 | optional uint32 magnitude = 2; 220 | optional uint32 length = 3; 221 | optional .CMsgVector direction = 4; 222 | } 223 | 224 | message CMsgTEPhysicsProp { 225 | optional .CMsgVector origin = 1; 226 | optional .CMsgVector velocity = 2; 227 | optional .CMsgQAngle angles = 3; 228 | optional fixed32 skin = 4; 229 | optional uint32 flags = 5; 230 | optional uint32 effects = 6; 231 | optional fixed32 color = 7; 232 | optional fixed64 modelindex = 8; 233 | optional uint32 unused_breakmodelsnottomake = 9; 234 | optional float scale = 10; 235 | optional .CMsgVector dmgpos = 11; 236 | optional .CMsgVector dmgdir = 12; 237 | optional int32 dmgtype = 13; 238 | } 239 | 240 | message CMsgTEPlayerDecal { 241 | optional .CMsgVector origin = 1; 242 | optional int32 player = 2 [default = -1]; 243 | optional int32 entity = 3 [default = -1]; 244 | } 245 | 246 | message CMsgTEProjectedDecal { 247 | optional .CMsgVector origin = 1; 248 | optional .CMsgQAngle angles = 2; 249 | optional uint32 index = 3; 250 | optional float distance = 4; 251 | } 252 | 253 | message CMsgTESmoke { 254 | optional .CMsgVector origin = 1; 255 | optional float scale = 2; 256 | } 257 | 258 | message CMsgTEWorldDecal { 259 | optional .CMsgVector origin = 1; 260 | optional .CMsgVector normal = 2; 261 | optional uint32 index = 3; 262 | } 263 | -------------------------------------------------------------------------------- /DemLock.Parser/proto/valveextensions.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | extend .google.protobuf.FieldOptions { 4 | optional bool valve_map_field = 61000 [default = false]; 5 | optional bool valve_map_key = 61001 [default = false]; 6 | optional int32 diff_encode_field = 61002 [default = 0]; 7 | optional bool delta_ignore = 61003 [default = false]; 8 | optional uint32 steamml_max_entries = 61004 [default = 0]; 9 | optional bool steamml_is_timestamp = 61005 [default = false]; 10 | optional uint32 steamlearn_count = 61006 [default = 0]; 11 | } 12 | 13 | extend .google.protobuf.EnumValueOptions { 14 | optional string schema_friendly_name = 1000; 15 | optional string schema_description = 1001; 16 | optional bool schema_suppress_enumerator = 1002; 17 | } 18 | -------------------------------------------------------------------------------- /DemLock.Tests/DemLock.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DemLock.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using DemLock.Parser.Models; 2 | 3 | namespace DemLock.Tests; 4 | 5 | public class Tests 6 | { 7 | [SetUp] 8 | public void Setup() 9 | { 10 | } 11 | 12 | [Test] 13 | public void Test1() 14 | { 15 | DClass dclass = new(); 16 | } 17 | } -------------------------------------------------------------------------------- /DemLock.Utils/DemLock.Utils.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DemLock.Utils/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DemLock.Utils; 2 | 3 | /// 4 | /// Extension functions for the stream interface that will allow us to easily 5 | /// read and process wire format types for standard byte streams 6 | /// 7 | public static class StreamExtensions 8 | { 9 | public static uint ReadVarUInt32(this Stream stream) 10 | { 11 | uint result = 0; 12 | int count = 0; 13 | int b = 0; 14 | while (count < 5) 15 | { 16 | b = stream.ReadByte(); 17 | result |= (uint)(b & 127) << (7*count); 18 | count++; 19 | if ((b & 0x80) == 0) break; 20 | } 21 | return result; 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /DemLock.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemLock", "DemLock\DemLock.csproj", "{EFA01E70-EDFC-4D2A-8164-3649D7457522}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemLock.Parser", "DemLock.Parser\DemLock.Parser.csproj", "{232BB601-75AB-46D2-8743-97C807EF4DB2}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemLock.Utils", "DemLock.Utils\DemLock.Utils.csproj", "{934FFD57-891A-4364-ACE3-971E323F1946}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemLock.Tests", "DemLock.Tests\DemLock.Tests.csproj", "{C671B37E-C720-4E05-8245-75267A8C4CFD}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemLock.Entities", "DemLock.Entities\DemLock.Entities.csproj", "{A7D589F7-728E-48F4-8F02-23CC9728A86F}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {EFA01E70-EDFC-4D2A-8164-3649D7457522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {EFA01E70-EDFC-4D2A-8164-3649D7457522}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {EFA01E70-EDFC-4D2A-8164-3649D7457522}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {EFA01E70-EDFC-4D2A-8164-3649D7457522}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {232BB601-75AB-46D2-8743-97C807EF4DB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {232BB601-75AB-46D2-8743-97C807EF4DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {232BB601-75AB-46D2-8743-97C807EF4DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {232BB601-75AB-46D2-8743-97C807EF4DB2}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {934FFD57-891A-4364-ACE3-971E323F1946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {934FFD57-891A-4364-ACE3-971E323F1946}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {934FFD57-891A-4364-ACE3-971E323F1946}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {934FFD57-891A-4364-ACE3-971E323F1946}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {C671B37E-C720-4E05-8245-75267A8C4CFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {C671B37E-C720-4E05-8245-75267A8C4CFD}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {C671B37E-C720-4E05-8245-75267A8C4CFD}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {C671B37E-C720-4E05-8245-75267A8C4CFD}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {A7D589F7-728E-48F4-8F02-23CC9728A86F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {A7D589F7-728E-48F4-8F02-23CC9728A86F}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {A7D589F7-728E-48F4-8F02-23CC9728A86F}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {A7D589F7-728E-48F4-8F02-23CC9728A86F}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /DemLock/DemLock.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DemLock/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using DemLock.Entities; 3 | using DemLock.Parser; 4 | using Newtonsoft.Json; 5 | 6 | namespace DemLock; 7 | 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | 13 | TestParseDemo(); 14 | 15 | } 16 | 17 | static void TestParseDemo() 18 | { 19 | 20 | Stopwatch sw = Stopwatch.StartNew(); 21 | DemoParserConfig config = new DemoParserConfig(); 22 | 23 | config.LogMessageReads = false; 24 | config.LogReadFrames = false; 25 | 26 | DemoParser parser = new DemoParser(config); 27 | 28 | Dictionary eventIdCounts = new Dictionary(); 29 | 30 | parser.GameEvents.OnSource1LegacyGameEvent += (e) => 31 | { 32 | if(!eventIdCounts.TryAdd(e.Eventid, 1)) eventIdCounts[e.Eventid]++; 33 | }; 34 | 35 | Dictionary eventNames = new Dictionary(); 36 | parser.GameEvents.OnSource1LegacyGameEventList += (e) => 37 | { 38 | foreach (var v in e.Descriptors) 39 | { 40 | eventNames[v.Eventid] = v.Name; 41 | } 42 | }; 43 | 44 | 45 | 46 | parser.ProcessDemo("C:\\tmp\\DeadlockDemos\\534870CS.dem"); 47 | 48 | foreach (var ev in eventIdCounts) 49 | { 50 | Console.WriteLine($"{eventNames[ev.Key]} :: {ev.Value}"); 51 | 52 | } 53 | Console.WriteLine($"Processed demo in {sw.Elapsed.TotalSeconds} seconds"); 54 | // 14011DEMLOCK.dem 55 | 56 | //parser.ProcessDemo("C:/tmp/DeadlockDemos/534870CS.dem"); 57 | } 58 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SED=sed 2 | 3 | ifeq ($(shell uname), Darwin) 4 | SED=gsed 5 | endif 6 | 7 | proto: 8 | rm -rf DemLock.Parser/proto 9 | mkdir -p ./DemLock.Parser/proto/tmp && \ 10 | curl -L -o - https://github.com/SteamDatabase/Protobufs/archive/master.tar.gz | tar -xz --strip-components=1 -C ./DemLock.Parser/proto/tmp && \ 11 | cp -a ./DemLock.Parser/proto/tmp/deadlock/* ./DemLock.Parser/proto/ && \ 12 | cp -a ./DemLock.Parser/proto/tmp/google/ ./DemLock.Parser/proto/ && \ 13 | rm -rf ./DemLock.Parser/proto/tmp 14 | rm -rf DemLock.Parser/proto/base_gcmessages.proto DemLock.Parser/proto/c_peer2peer_netmessages.proto DemLock.Parser/proto/citadel_clientmessages.proto DemLock.Parser/proto/citadel_gcmessages_client.proto DemLock.Parser/proto/citadel_gcmessages_server.proto DemLock.Parser/proto/citadel_usercmd.proto DemLock.Parser/proto/clientmessages.proto DemLock.Parser/proto/connectionless_netmessages.proto DemLock.Parser/proto/econ_gcmessages.proto DemLock.Parser/proto/econ_shared_enums.proto DemLock.Parser/proto/engine_gcmessages.proto DemLock.Parser/proto/enums_clientserver.proto DemLock.Parser/proto/gcsystemmsgs.proto DemLock.Parser/proto/network_connection.proto DemLock.Parser/proto/networksystem_protomessages.proto DemLock.Parser/proto/steamdatagram_messages_auth.proto DemLock.Parser/proto/steamdatagram_messages_sdr.proto DemLock.Parser/proto/steammessages_base.proto DemLock.Parser/proto/steammessages_cloud.steamworkssdk.proto DemLock.Parser/proto/steammessages_gamenetworkingui.proto DemLock.Parser/proto/steammessages_helprequest.steamworkssdk.proto DemLock.Parser/proto/steammessages_int.proto DemLock.Parser/proto/steammessages_oauth.steamworkssdk.proto DemLock.Parser/proto/steammessages_player.steamworkssdk.proto DemLock.Parser/proto/steammessages_publishedfile.steamworkssdk.proto DemLock.Parser/proto/steamnetworkingsockets_messages.proto DemLock.Parser/proto/steamnetworkingsockets_messages_certs.proto DemLock.Parser/proto/steamnetworkingsockets_messages_udp.proto DemLock.Parser/proto/uifontfile_format.proto DemLock.Parser/proto/usercmd.proto DemLock.Parser/proto/google/protobuf/any.proto DemLock.Parser/proto/google/protobuf/source_context.proto DemLock.Parser/proto/google/protobuf/type.proto DemLock.Parser/proto/google/protobuf/wrappers.proto 15 | $(SED) -i '/^import "network_connection\.proto"/d' DemLock.Parser/proto/networkbasetypes.proto 16 | $(SED) -i '/^import "google\/protobuf\/descriptor\.proto"/d' DemLock.Parser/proto/citadel_gameevents.proto 17 | $(SED) -i '/^import "citadel_gcmessages_common.proto"/d' DemLock.Parser/proto/citadel_gamemessages.proto 18 | protoc -I DemLock.Parser/proto --csharp_out=DemLock.Parser/proto DemLock.Parser/proto/*.proto 19 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Demlock: Demo Parser for Deadlock 2 | 3 | ## NOTE: THIS IS HEAVILY WIP RIGHT NOW 4 | 5 | This is a demo parser the is being created in C# for the express purposes of taking dead lock demo files (.dem) and parsing them to extract useful stats and information. 6 | 7 | Right now this project is at the very start, but is quickly being brought up with reference to parsers for other valve games. 8 | 9 | ## First release build 10 | This commit marks the first build I can say is stable enough to be a release. 11 | 12 | It is still lacking a lot of features, however it is able to fully parse the sample demo (more integration testing to come) and seems stable enough. 13 | 14 | The interface to consume the results is not defined fully yet, but that is being worked on. 15 | 16 | This project comes with absolutely zero assurance of stability, and is to be used at your own risk. 17 | 18 | There has also been near zero optimization done to the running speed of the application, so please expect that it will be very slow, as there is still a lot of stuff left in that was only useful for debugging that will be removed as benchmarks are started. 19 | 20 | If you identify anything you think you might be able to fix or upgrade please feel free to reach out as any help would be appreciated! its been a long journey to get here. 21 | 22 | 23 | 24 | ### Resources 25 | This project is being built and maintained by the Devlock open source community, if you have questions or would like to join in on learning about demo files, you can find our discord below 26 | 27 | https://discord.gg/m9wNg4Ak47 28 | 29 | 30 | List of parsers that I have been basing parts of my implementation on 31 | - https://github.com/LaihoE/demoparser/ 32 | - https://github.com/saul/demofile-net 33 | - https://github.com/dotabuff --------------------------------------------------------------------------------