private int readRawData(byte[] rawData) { // unpack the packet header manually before attempting to load into a struct uint packetNumber = BitConverter.ToUInt32(rawData, 0); uint categoryPacketNumber = BitConverter.ToUInt32(rawData, 4); int partialPacketIndexBytes = rawData[8]; int partialPacketNumber = rawData[9]; EUDPStreamerPacketHandlerType packetType = (EUDPStreamerPacketHandlerType)rawData[10]; int packetVersion = rawData[11]; int frameLength = 0; switch (packetType) { case EUDPStreamerPacketHandlerType.eCarPhysics: telemPacketCount++; if (telemPacketCount > packetCountAtStartOfNextRateCheck) { lastPacketRateEstimate = (int)((float)TimeSpan.TicksPerSecond * (float)(telemPacketCount - packetCountAtStartOfCurrentRateCheck) / (float)(DateTime.UtcNow.Ticks - ticksAtStartOfCurrentPacketRateCheck)); Console.WriteLine("Packet rate = " + lastPacketRateEstimate + "Hz, totals:" + "\rtelem = " + telemPacketCount + "\rraceDefinition = " + raceDefinitionPacketCount + "\rparticipants = " + participantsPacketCount + "\rtimings = " + timingsPacketCount + "\rgameState = " + gameStatePacketCount + "\rweather = " + weatherStatePacketCount + "\rvehicleNames = " + vehicleNamesPacketCount + "\rtimeStats = " + timeStatsPacketCount + "\rparticipantVehicleNames = " + participantVehicleNamesPacketCount + "\rin sequence = " + inSequenceTelemCount + " oos accepted = " + acceptedOutOfSequenceTelemCount + " oos rejected = " + discardedTelemCount); packetCountAtStartOfCurrentRateCheck = telemPacketCount; packetCountAtStartOfNextRateCheck = packetCountAtStartOfCurrentRateCheck + packetRateCheckInterval; ticksAtStartOfCurrentPacketRateCheck = DateTime.UtcNow.Ticks; } frameLength = UDPPacketSizes.telemetryPacketSize; Boolean sequenceCheckOK = isNextInSequence(packetNumber); if (sequenceCheckOK) { inSequenceTelemCount++; } if (strictPacketOrdering && !sequenceCheckOK) { discardedTelemCount++; } else { GCHandle telemHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sTelemetryData telem = (sTelemetryData)Marshal.PtrToStructure(telemHandle.AddrOfPinnedObject(), typeof(sTelemetryData)); if (sequenceCheckOK || !telemIsOutOfSequence(telem)) { buttonsState = ConvertBytesToBoolArray(telem.sDPad, telem.sJoyPad1, telem.sJoyPad2); lastSequenceNumberForTelemPacket = (int)packetNumber; workingGameState = StructHelper.MergeWithExistingState(workingGameState, telem); newSpotterData = true; } } finally { telemHandle.Free(); } } break; case EUDPStreamerPacketHandlerType.eRaceDefinition: raceDefinitionPacketCount++; frameLength = UDPPacketSizes.raceDataPacketSize; GCHandle raceDefHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sRaceData raceDefinition = (sRaceData)Marshal.PtrToStructure(raceDefHandle.AddrOfPinnedObject(), typeof(sRaceData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, raceDefinition); } finally { raceDefHandle.Free(); } break; case EUDPStreamerPacketHandlerType.eParticipants: participantsPacketCount++; frameLength = UDPPacketSizes.participantsDataPacketSize; GCHandle participantsHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sParticipantsData participants = (sParticipantsData)Marshal.PtrToStructure(participantsHandle.AddrOfPinnedObject(), typeof(sParticipantsData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, participants); } finally { participantsHandle.Free(); } break; case EUDPStreamerPacketHandlerType.eTimings: timingsPacketCount++; frameLength = UDPPacketSizes.timingsDataPacketSize; GCHandle timingsHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sTimingsData timings = (sTimingsData)Marshal.PtrToStructure(timingsHandle.AddrOfPinnedObject(), typeof(sTimingsData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, timings); } finally { timingsHandle.Free(); } break; case EUDPStreamerPacketHandlerType.eGameState: gameStatePacketCount++; frameLength = UDPPacketSizes.gameStateDataPacketSize; GCHandle gameStateHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sGameStateData gameState = (sGameStateData)Marshal.PtrToStructure(gameStateHandle.AddrOfPinnedObject(), typeof(sGameStateData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, gameState); } finally { gameStateHandle.Free(); } break; case EUDPStreamerPacketHandlerType.eWeatherState: weatherStatePacketCount++; Console.WriteLine("Got an undocumented and unsupported weather packet"); break; case EUDPStreamerPacketHandlerType.eVehicleNames: weatherStatePacketCount++; Console.WriteLine("Got an undocumented and unsupported vehicle names packet"); break; case EUDPStreamerPacketHandlerType.eTimeStats: participantsPacketCount++; frameLength = UDPPacketSizes.timeStatsPacketSize; GCHandle timeStatsHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sTimeStatsData timeStatsData = (sTimeStatsData)Marshal.PtrToStructure(timeStatsHandle.AddrOfPinnedObject(), typeof(sTimeStatsData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, timeStatsData); } finally { timeStatsHandle.Free(); } break; case EUDPStreamerPacketHandlerType.eParticipantVehicleNames: participantsPacketCount++; frameLength = UDPPacketSizes.participantVehicleNamesPacketSize; GCHandle vehNamesHandle = GCHandle.Alloc(rawData.Take(frameLength).ToArray(), GCHandleType.Pinned); try { sParticipantVehicleNamesData participantVehicleNames = (sParticipantVehicleNamesData)Marshal.PtrToStructure(vehNamesHandle.AddrOfPinnedObject(), typeof(sParticipantVehicleNamesData)); workingGameState = StructHelper.MergeWithExistingState(workingGameState, participantVehicleNames); } finally { vehNamesHandle.Free(); } break; } return(frameLength); }
public static pCars2APIStruct MergeWithExistingState(pCars2APIStruct existingState, sTimingsData timingsData) { existingState.mNumParticipants = timingsData.sNumParticipants; existingState.mEventTimeRemaining = timingsData.sEventTimeRemaining;// time remaining, -1 for invalid time, -1 - laps remaining in lap based races -- existingState.mSplitTimeAhead = timingsData.sSplitTimeAhead; existingState.mSplitTimeBehind = timingsData.sSplitTimeBehind; existingState.mSplitTime = timingsData.sSplitTime; // what's this? if (existingState.mParticipantData == null) { existingState.mParticipantData = new pCars2APIParticipantStruct[32]; } if (existingState.mRaceStates == null) { existingState.mRaceStates = new uint[32]; } if (existingState.mLapsInvalidated == null) { existingState.mLapsInvalidated = new byte[32]; } if (existingState.mPitModes == null) { existingState.mPitModes = new uint[32]; } if (existingState.mCurrentSector1Times == null) { existingState.mCurrentSector1Times = new float[32]; } if (existingState.mCurrentSector2Times == null) { existingState.mCurrentSector2Times = new float[32]; } if (existingState.mCurrentSector3Times == null) { existingState.mCurrentSector3Times = new float[32]; } for (int i = 0; i < existingState.mParticipantData.Length; i++) { sParticipantInfo newParticipantInfo = timingsData.sParticipants[i]; Boolean isHuman = (newParticipantInfo.sCarIndex >> 7) == 1; uint carIndex = (uint)newParticipantInfo.sCarIndex & 127; Boolean isActive = (newParticipantInfo.sRacePosition >> 7) == 1; pCars2APIParticipantStruct existingPartInfo = existingState.mParticipantData[i]; if (isActive) { existingPartInfo.mIsActive = i < existingState.mNumParticipants; existingPartInfo.mCurrentLap = newParticipantInfo.sCurrentLap; // is this safe?: existingPartInfo.mLapsCompleted = existingPartInfo.mCurrentLap - 1; existingPartInfo.mCurrentLapDistance = newParticipantInfo.sCurrentLapDistance; existingPartInfo.mRacePosition = (uint)newParticipantInfo.sRacePosition & 127; existingPartInfo.mCurrentSector = newParticipantInfo.sSector & 7; // err... laps completed is missing? // existingPartInfo.mLapsCompleted = (uint)newParticipantInfo.sLapsCompleted & 127; byte lapInvalidated = (byte)(newParticipantInfo.sRaceState >> 7); existingState.mRaceStates[i] = (uint)newParticipantInfo.sRaceState & 127; if (i == existingState.mViewedParticipantIndex) { existingState.mRaceState = existingState.mRaceStates[i]; } existingState.mLapsInvalidated[i] = lapInvalidated; existingState.mPitModes[i] = (uint)newParticipantInfo.sPitModeSchedule & 28; existingState.mPitSchedules[i] = (uint)newParticipantInfo.sPitModeSchedule & 3; existingState.mHighestFlagColours[i] = (uint)newParticipantInfo.sHighestFlag & 28; existingState.mHighestFlagReasons[i] = (uint)newParticipantInfo.sHighestFlag & 3; // no obvious slot in MMF for currentTime - do we need it if we have currentsectortime for S3? if (existingPartInfo.mCurrentSector == 1) { existingState.mCurrentSector1Times[i] = newParticipantInfo.sCurrentSectorTime; } if (existingPartInfo.mCurrentSector == 2) { existingState.mCurrentSector2Times[i] = newParticipantInfo.sCurrentSectorTime; } if (existingPartInfo.mCurrentSector == 3) { existingState.mCurrentSector3Times[i] = newParticipantInfo.sCurrentSectorTime; } // and now the bit magic for the extra position precision... float[] newWorldPositions = toFloatArray(newParticipantInfo.sWorldPosition, 1); float xAdjustment = ((float)((uint)newParticipantInfo.sSector >> 6 & 3)) / 4f; float zAdjustment = ((float)((uint)newParticipantInfo.sSector >> 4 & 3)) / 4f; newWorldPositions[0] = newWorldPositions[0] + xAdjustment; newWorldPositions[2] = newWorldPositions[2] + zAdjustment; existingPartInfo.mWorldPosition = newWorldPositions; if (i == existingState.mViewedParticipantIndex) { existingState.mLapInvalidated = lapInvalidated == 1; existingState.mHighestFlagColour = existingState.mHighestFlagColours[i]; existingState.mHighestFlagReason = existingState.mHighestFlagReasons[i]; existingState.mPitMode = existingState.mPitModes[i]; existingState.mPitSchedule = existingState.mPitSchedules[i]; } } else { existingPartInfo.mWorldPosition = new float[] { 0, 0, 0 }; existingPartInfo.mIsActive = false; } existingState.mParticipantData[i] = existingPartInfo; } return(existingState); }